From 66e2c7c16075b991dafa019d61acc7ff34e601d7 Mon Sep 17 00:00:00 2001 From: Jakub Korzeniowski Date: Fri, 6 Mar 2026 17:41:41 +0400 Subject: [PATCH 1/3] chore: upgrade vscode-json-languageservice from 4.1.8 to 5.7.2 This is a major version bump that required adapting to several breaking changes in the upstream JSON language service: - registerExternalSchema now takes a config object instead of positional args - resolveSchemaContent signature changed from (schema, url, deps) to (schema, handle) - SchemaDependencies type removed in favor of Set - SchemaHandle.url renamed to .uri - FilePatternAssociation.uris replaced by .getURIs() method - UnresolvedSchema/ResolvedSchema errors changed from string[] to diagnostic objects - ResolvedSchema now requires warnings field (multi-schema allOf combination updated to use proper ResolvedSchema constructor) - normalizeId now uses URI.parse().toString() adding trailing slashes to bare HTTP URIs - Default JSON Schema draft changed from draft-07 to draft-2020-12 Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 27 +++----- package.json | 2 +- .../services/yamlSchemaService.ts | 64 +++++++++++-------- test/autoCompletion.test.ts | 4 +- test/jsonParser.test.ts | 5 +- test/schemaValidation.test.ts | 1 + test/yamlLanguageService.test.ts | 4 +- 7 files changed, 56 insertions(+), 51 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5039c87b3..b07d85d0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "ajv-draft-04": "^1.0.0", "prettier": "^3.8.1", "request-light": "^0.5.7", - "vscode-json-languageservice": "4.1.8", + "vscode-json-languageservice": "5.7.2", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", @@ -6400,19 +6400,16 @@ "license": "MIT" }, "node_modules/vscode-json-languageservice": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.1.8.tgz", - "integrity": "sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.7.2.tgz", + "integrity": "sha512-WtKRDtJfFEmLrgtu+ODexOHm/6/krRF0k6t+uvkKIKW1Jh9ZIyxZQwJJwB3qhrEgvAxa37zbUg+vn+UyUK/U2w==", "license": "MIT", "dependencies": { - "jsonc-parser": "^3.0.0", - "vscode-languageserver-textdocument": "^1.0.1", - "vscode-languageserver-types": "^3.16.0", - "vscode-nls": "^5.0.0", - "vscode-uri": "^3.0.2" - }, - "engines": { - "npm": ">=7.0.0" + "@vscode/l10n": "^0.0.18", + "jsonc-parser": "^3.3.1", + "vscode-languageserver-textdocument": "^1.0.12", + "vscode-languageserver-types": "^3.17.5", + "vscode-uri": "^3.1.0" } }, "node_modules/vscode-jsonrpc": { @@ -6458,12 +6455,6 @@ "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", "license": "MIT" }, - "node_modules/vscode-nls": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz", - "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", - "license": "MIT" - }, "node_modules/vscode-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", diff --git a/package.json b/package.json index 99c9167e4..f4e32c450 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "ajv-draft-04": "^1.0.0", "prettier": "^3.8.1", "request-light": "^0.5.7", - "vscode-json-languageservice": "4.1.8", + "vscode-json-languageservice": "5.7.2", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index ce3b9a293..3875d4e00 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -11,7 +11,6 @@ import { UnresolvedSchema, ResolvedSchema, JSONSchemaService, - SchemaDependencies, ISchemaContributions, SchemaHandle, } from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService'; @@ -153,7 +152,7 @@ export class YAMLSchemaService extends JSONSchemaService { const result: JSONSchemaDescriptionExt[] = []; const schemaUris = new Set(); for (const filePattern of this.filePatternAssociations) { - const schemaUri = filePattern.uris[0]; + const schemaUri = filePattern.getURIs()[0]; if (schemaUris.has(schemaUri)) { continue; } @@ -200,10 +199,16 @@ export class YAMLSchemaService extends JSONSchemaService { async resolveSchemaContent( schemaToResolve: UnresolvedSchema, - schemaURL: string, - dependencies: SchemaDependencies + handle: SchemaHandle ): Promise { - const resolveErrors: string[] = schemaToResolve.errors.slice(0); + const schemaURL: string = handle.uri; + const dependencies: Set = handle.dependencies; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const resolveErrors: any[] = schemaToResolve.errors.slice(0); + // Normalize errors: upstream v5.x expects { message, code } objects, but our code pushes strings + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const _toDiagErrors = (errors: any[]): { message: string; code: number }[] => + errors.map((e) => (typeof e === 'string' ? { message: e, code: 0x10000 } : e)); const loc = toDisplayString(schemaURL); const raw: unknown = schemaToResolve.schema; @@ -212,7 +217,7 @@ export class YAMLSchemaService extends JSONSchemaService { resolveErrors.push( l10n.t("Schema '{0}' is not valid: {1}", loc, `expected a JSON Schema object or boolean, got "${got}".`) ); - return new ResolvedSchema({}, resolveErrors); + return new ResolvedSchema({}, _toDiagErrors(resolveErrors)); } const _cloneSchema = ( @@ -532,7 +537,7 @@ export class YAMLSchemaService extends JSONSchemaService { uri: string, linkPath: string, parentSchemaURL: string, - parentSchemaDependencies: SchemaDependencies, + parentSchemaDependencies: Set, resolutionStack: Set, recursiveAnchorBase: string, inheritedDynamicScope: Map @@ -543,14 +548,14 @@ export class YAMLSchemaService extends JSONSchemaService { schemaRoot: JSONSchema, schemaUri: string, linkPath: string, - parentSchemaDependencies: SchemaDependencies, - resolveRefDependencies: SchemaDependencies, + parentSchemaDependencies: Set, + resolveRefDependencies: Set, resolutionStack: Set, recursiveAnchorBase: string, inheritedDynamicScope: Map // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise => { - parentSchemaDependencies[schemaUri] = true; + parentSchemaDependencies.add(schemaUri); _merge(node, schemaRoot, schemaUri, linkPath, !!inheritedDynamicScope || !!recursiveAnchorBase); if (!recursiveAnchorBase || !node._baseUrl) node._baseUrl = schemaUri; node.url = schemaUri; @@ -590,7 +595,10 @@ export class YAMLSchemaService extends JSONSchemaService { const referencedHandle = this.getOrAddSchemaHandle(targetUri); return referencedHandle.getUnresolvedSchema().then(async (unresolvedSchema) => { if ( - unresolvedSchema.errors?.some((error) => error.toLowerCase().includes('unable to load schema from')) && + unresolvedSchema.errors?.some((error) => { + const msg = typeof error === 'string' ? error : error?.message ?? ''; + return msg.toLowerCase().includes('unable to load schema from'); + }) && index + 1 < targetUris.length ) { return _resolveByUri(targetUris, index + 1); @@ -598,7 +606,9 @@ export class YAMLSchemaService extends JSONSchemaService { if (unresolvedSchema.errors.length) { const loc = linkPath ? targetUri + '#' + linkPath : targetUri; - resolveErrors.push(l10n.t("Problems loading reference '{0}': {1}", loc, unresolvedSchema.errors[0])); + const firstError = unresolvedSchema.errors[0]; + const errorMsg = typeof firstError === 'string' ? firstError : firstError?.message ?? String(firstError); + resolveErrors.push(l10n.t("Problems loading reference '{0}': {1}", loc, errorMsg)); } // index resources for the newly loaded schema await _indexSchemaResources(unresolvedSchema.schema, targetUri); @@ -626,7 +636,7 @@ export class YAMLSchemaService extends JSONSchemaService { node: JSONSchema, parentSchema: JSONSchema, parentSchemaURL: string, - parentSchemaDependencies: SchemaDependencies, + parentSchemaDependencies: Set, resolutionStack: Set, recursiveAnchorBase?: string, inheritedDynamicScope?: Map @@ -939,7 +949,7 @@ export class YAMLSchemaService extends JSONSchemaService { const rootResource = schema._baseUrl || schemaURL; if (rootResource) resolutionStack.add(rootResource); await resolveRefs(schema, schema, schemaURL, dependencies, resolutionStack); - return new ResolvedSchema(schema, resolveErrors); + return new ResolvedSchema(schema, _toDiagErrors(resolveErrors)); } public getSchemaForResource(resource: string, doc: JSONDocument): Promise { @@ -973,7 +983,7 @@ export class YAMLSchemaService extends JSONSchemaService { const resolveSchemaForResource = (schemas: string[]): Promise => { const schemaHandle = super.createCombinedSchema(resource, schemas); return schemaHandle.getResolvedSchema().then((schema) => { - return this.finalizeResolvedSchema(schema, schemaHandle.url, doc, false); + return this.finalizeResolvedSchema(schema, schemaHandle.uri, doc, false); }); }; @@ -1038,14 +1048,11 @@ export class YAMLSchemaService extends JSONSchemaService { }) ).then( (schemas) => { - return { - errors: [], - schema: { - allOf: schemas.map((schemaObj) => { - return schemaObj.schema; - }), - }, - }; + return new ResolvedSchema({ + allOf: schemas.map((schemaObj) => { + return schemaObj.schema; + }), + }); }, () => { return resolveSchema(); @@ -1130,7 +1137,8 @@ export class YAMLSchemaService extends JSONSchemaService { private async resolveCustomSchema(schemaUri, doc): ResolvedSchema { const unresolvedSchema = await this.loadSchema(schemaUri); - const schema = await this.resolveSchemaContent(unresolvedSchema, schemaUri, []); + const schemaHandle = this.getOrAddSchemaHandle(schemaUri); + const schema = await this.resolveSchemaContent(unresolvedSchema, schemaHandle); return this.finalizeResolvedSchema(schema, schemaUri, doc, true); } @@ -1259,10 +1267,11 @@ export class YAMLSchemaService extends JSONSchemaService { return requestService(schemaUri).then( (content) => { if (!content) { + const errorDetails = unresolvedJsonSchema.errors.map((e) => (typeof e === 'string' ? e : e?.message ?? String(e))).join(', '); const errorMessage = l10n.t( "Unable to load schema from '{0}': No content. {1}", toDisplayString(schemaUri), - unresolvedJsonSchema.errors + errorDetails ); return new UnresolvedSchema({}, [errorMessage]); } @@ -1294,7 +1303,8 @@ export class YAMLSchemaService extends JSONSchemaService { unresolvedJsonSchema.schema.description = description ?? unresolvedJsonSchema.schema.description; unresolvedJsonSchema.schema.versions = versions ?? unresolvedJsonSchema.schema.versions; } else if (unresolvedJsonSchema.errors && unresolvedJsonSchema.errors.length > 0) { - let errorMessage: string = unresolvedJsonSchema.errors[0]; + const firstError = unresolvedJsonSchema.errors[0]; + let errorMessage: string = typeof firstError === 'string' ? firstError : firstError?.message ?? String(firstError); if (errorMessage.toLowerCase().indexOf('load') !== -1) { errorMessage = l10n.t("Unable to load schema from '{0}': No content.", toDisplayString(schemaUri)); } else if (errorMessage.toLowerCase().indexOf('parse') !== -1) { @@ -1329,7 +1339,7 @@ export class YAMLSchemaService extends JSONSchemaService { if (name || description) { this.schemaUriToNameAndDescription.set(uri, { name, description, versions }); } - return super.registerExternalSchema(uri, filePatterns, unresolvedSchema); + return super.registerExternalSchema({ uri, fileMatch: filePatterns, schema: unresolvedSchema }); } clearExternalSchemas(): void { diff --git a/test/autoCompletion.test.ts b/test/autoCompletion.test.ts index aa138ff57..b4024b911 100644 --- a/test/autoCompletion.test.ts +++ b/test/autoCompletion.test.ts @@ -2131,7 +2131,7 @@ describe('Auto Completion Tests', () => { textDocument: testTextDocument, }); assert.strictEqual(result.items.length, 1, `Expecting 1 item in completion but found ${result.items.length}`); - assert.strictEqual(result.items[0].label, 'http://google.com'); + assert.strictEqual(result.items[0].label, 'http://google.com/'); }); it('should provide schema id completion in modeline for any line', async () => { @@ -2144,7 +2144,7 @@ describe('Auto Completion Tests', () => { textDocument: testTextDocument, }); assert.strictEqual(result.items.length, 1, `Expecting 1 item in completion but found ${result.items.length}`); - assert.strictEqual(result.items[0].label, 'http://google.com'); + assert.strictEqual(result.items[0].label, 'http://google.com/'); }); }); diff --git a/test/jsonParser.test.ts b/test/jsonParser.test.ts index 6eafda74d..2deb8b817 100644 --- a/test/jsonParser.test.ts +++ b/test/jsonParser.test.ts @@ -1422,6 +1422,7 @@ describe('JSON Parser', () => { it('items as array', function () { const schema: JsonSchema.JSONSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', type: 'array', items: [ { @@ -1457,6 +1458,7 @@ describe('JSON Parser', () => { it('additionalItems', function () { let schema: JsonSchema.JSONSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', type: 'array', items: [ { @@ -1484,6 +1486,7 @@ describe('JSON Parser', () => { assert.strictEqual(semanticErrors.length, 1); } schema = { + $schema: 'http://json-schema.org/draft-07/schema#', type: 'array', items: [ { @@ -1928,7 +1931,7 @@ describe('JSON Parser', () => { assert.strictEqual(res.length, 0); const schema: JsonSchema.JSONSchema = { type: 'object', required: ['foo'] }; - res = await ls.doValidation(textDoc, jsonDoc, { trailingCommas: 'ignore' }, schema); + res = await ls.doValidation(textDoc, jsonDoc, { trailingCommas: 'ignore' }, schema as any); assert.strictEqual(res.length, 1); assert.strictEqual(res[0].message, 'Missing property "foo".'); }); diff --git a/test/schemaValidation.test.ts b/test/schemaValidation.test.ts index 34e23b5a6..01849a6f5 100644 --- a/test/schemaValidation.test.ts +++ b/test/schemaValidation.test.ts @@ -71,6 +71,7 @@ describe('Validation Tests', () => { afterEach(() => { schemaProvider.deleteSchema(SCHEMA_ID); + telemetry.clearMessages(); }); describe('Boolean tests', () => { diff --git a/test/yamlLanguageService.test.ts b/test/yamlLanguageService.test.ts index 1806a4c49..238c98564 100644 --- a/test/yamlLanguageService.test.ts +++ b/test/yamlLanguageService.test.ts @@ -27,7 +27,7 @@ describe('getLanguageService()', () => { }); describe('minimal language service hover happy path', () => { - const schemaUri = 'my.schema.uri'; + const schemaUri = 'https://example.com/my.schema.json'; const schemaContentMap: { [uri: string]: string } = {}; let schemaRequestService: SchemaRequestService; @@ -79,7 +79,7 @@ describe('getLanguageService()', () => { assert.deepEqual(result, { contents: { kind: 'markdown', - value: "The person's first name.\n\nSource: [my.schema.uri](my.schema.uri)", + value: "The person's first name.\n\nSource: [my.schema.json](https://example.com/my.schema.json)", }, range: { start: { From fb8dedb25a9e0bc4a0a84cbc0b8e69a6110a2bce Mon Sep 17 00:00:00 2001 From: Jakub Korzeniowski Date: Fri, 6 Mar 2026 20:45:03 +0400 Subject: [PATCH 2/3] fix: resolve JSONSchema type mismatch and apply prettier formatting Use vscode-json-languageservice's JSONSchema type for doValidation call to fix TS2345 without casting to any. Prettier reformatted yamlSchemaService.ts. Co-Authored-By: Claude Opus 4.6 --- src/languageservice/services/yamlSchemaService.ts | 15 +++++++-------- test/jsonParser.test.ts | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index 3875d4e00..6050b9c58 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -197,10 +197,7 @@ export class YAMLSchemaService extends JSONSchemaService { return Object.values(map); } - async resolveSchemaContent( - schemaToResolve: UnresolvedSchema, - handle: SchemaHandle - ): Promise { + async resolveSchemaContent(schemaToResolve: UnresolvedSchema, handle: SchemaHandle): Promise { const schemaURL: string = handle.uri; const dependencies: Set = handle.dependencies; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -596,7 +593,7 @@ export class YAMLSchemaService extends JSONSchemaService { return referencedHandle.getUnresolvedSchema().then(async (unresolvedSchema) => { if ( unresolvedSchema.errors?.some((error) => { - const msg = typeof error === 'string' ? error : error?.message ?? ''; + const msg = typeof error === 'string' ? error : (error?.message ?? ''); return msg.toLowerCase().includes('unable to load schema from'); }) && index + 1 < targetUris.length @@ -607,7 +604,7 @@ export class YAMLSchemaService extends JSONSchemaService { if (unresolvedSchema.errors.length) { const loc = linkPath ? targetUri + '#' + linkPath : targetUri; const firstError = unresolvedSchema.errors[0]; - const errorMsg = typeof firstError === 'string' ? firstError : firstError?.message ?? String(firstError); + const errorMsg = typeof firstError === 'string' ? firstError : (firstError?.message ?? String(firstError)); resolveErrors.push(l10n.t("Problems loading reference '{0}': {1}", loc, errorMsg)); } // index resources for the newly loaded schema @@ -1267,7 +1264,9 @@ export class YAMLSchemaService extends JSONSchemaService { return requestService(schemaUri).then( (content) => { if (!content) { - const errorDetails = unresolvedJsonSchema.errors.map((e) => (typeof e === 'string' ? e : e?.message ?? String(e))).join(', '); + const errorDetails = unresolvedJsonSchema.errors + .map((e) => (typeof e === 'string' ? e : (e?.message ?? String(e)))) + .join(', '); const errorMessage = l10n.t( "Unable to load schema from '{0}': No content. {1}", toDisplayString(schemaUri), @@ -1304,7 +1303,7 @@ export class YAMLSchemaService extends JSONSchemaService { unresolvedJsonSchema.schema.versions = versions ?? unresolvedJsonSchema.schema.versions; } else if (unresolvedJsonSchema.errors && unresolvedJsonSchema.errors.length > 0) { const firstError = unresolvedJsonSchema.errors[0]; - let errorMessage: string = typeof firstError === 'string' ? firstError : firstError?.message ?? String(firstError); + let errorMessage: string = typeof firstError === 'string' ? firstError : (firstError?.message ?? String(firstError)); if (errorMessage.toLowerCase().indexOf('load') !== -1) { errorMessage = l10n.t("Unable to load schema from '{0}': No content.", toDisplayString(schemaUri)); } else if (errorMessage.toLowerCase().indexOf('parse') !== -1) { diff --git a/test/jsonParser.test.ts b/test/jsonParser.test.ts index 2deb8b817..5c68130cb 100644 --- a/test/jsonParser.test.ts +++ b/test/jsonParser.test.ts @@ -9,7 +9,7 @@ import { JSONDocument } from '../src/languageservice/parser/jsonDocument'; import { getNodeValue } from '../src/languageservice/parser/astNodeUtils'; import * as JsonSchema from './../src/languageservice/jsonSchema'; import { ASTNode, ObjectASTNode } from './../src/languageservice/jsonASTTypes'; -import { ErrorCode, getLanguageService } from 'vscode-json-languageservice'; +import { ErrorCode, getLanguageService, type JSONSchema } from 'vscode-json-languageservice'; import { Diagnostic, TextDocument, Range } from 'vscode-languageserver-types'; describe('JSON Parser', () => { @@ -1930,8 +1930,8 @@ describe('JSON Parser', () => { res = await ls.doValidation(textDoc, jsonDoc, { trailingCommas: 'ignore' }); assert.strictEqual(res.length, 0); - const schema: JsonSchema.JSONSchema = { type: 'object', required: ['foo'] }; - res = await ls.doValidation(textDoc, jsonDoc, { trailingCommas: 'ignore' }, schema as any); + const schema: JSONSchema = { type: 'object', required: ['foo'] }; + res = await ls.doValidation(textDoc, jsonDoc, { trailingCommas: 'ignore' }, schema); assert.strictEqual(res.length, 1); assert.strictEqual(res[0].message, 'Missing property "foo".'); }); From fb42e616f7dd7e3b31dc4bed0da6ce756ad16f90 Mon Sep 17 00:00:00 2001 From: Jakub Korzeniowski Date: Fri, 6 Mar 2026 21:01:03 +0400 Subject: [PATCH 3/3] refactor: replace explicit any with union type for schema resolve errors Co-Authored-By: Claude Opus 4.6 --- src/languageservice/services/yamlSchemaService.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/languageservice/services/yamlSchemaService.ts b/src/languageservice/services/yamlSchemaService.ts index 6050b9c58..dabd17b54 100644 --- a/src/languageservice/services/yamlSchemaService.ts +++ b/src/languageservice/services/yamlSchemaService.ts @@ -200,11 +200,9 @@ export class YAMLSchemaService extends JSONSchemaService { async resolveSchemaContent(schemaToResolve: UnresolvedSchema, handle: SchemaHandle): Promise { const schemaURL: string = handle.uri; const dependencies: Set = handle.dependencies; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const resolveErrors: any[] = schemaToResolve.errors.slice(0); + const resolveErrors: (string | { message: string; code: number })[] = schemaToResolve.errors.slice(0); // Normalize errors: upstream v5.x expects { message, code } objects, but our code pushes strings - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const _toDiagErrors = (errors: any[]): { message: string; code: number }[] => + const _toDiagErrors = (errors: (string | { message: string; code: number })[]): { message: string; code: number }[] => errors.map((e) => (typeof e === 'string' ? { message: e, code: 0x10000 } : e)); const loc = toDisplayString(schemaURL);