Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
5.7.0 / 2026-01-12
================
* the error code for `SchemaResolveError` is now 0x10000 plus the error code returned by the request service.

5.6.0 / 2025-05-28
================
* added `Schema.enumSortTexts` and `Schema.enumDetails` to control the sort order and presentation of suggestions for enums
Expand Down
566 changes: 261 additions & 305 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vscode-json-languageservice",
"version": "5.6.4",
"version": "5.7.0",
"description": "Language service for JSON",
"main": "./lib/umd/jsonLanguageService.js",
"typings": "./lib/umd/jsonLanguageService",
Expand All @@ -17,12 +17,12 @@
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "22.x",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"eslint": "^9.39.1",
"@typescript-eslint/eslint-plugin": "^8.53.0",
"@typescript-eslint/parser": "^8.53.0",
"eslint": "^9.39.2",
"json-schema-test-suite": "https://github.com/json-schema-org/JSON-Schema-Test-Suite.git#69acf52990b004240839ae19b4bec8fb01d50876",
"mocha": "^11.7.4",
"rimraf": "^6.1.0",
"mocha": "^11.7.5",
"rimraf": "^6.1.2",
"typescript": "^5.9.3"
},
"dependencies": {
Expand Down
11 changes: 8 additions & 3 deletions src/jsonLanguageTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,12 @@ export enum ErrorCode {
DuplicateKey = 0x208,
CommentNotPermitted = 0x209,
PropertyKeysMustBeDoublequoted = 0x210,
SchemaResolveError = 0x300,
SchemaUnsupportedFeature = 0x301
SchemaUnsupportedFeature = 0x301,
SchemaResolveError = 0x10000,
}

export function isSchemaResolveError(code: number): boolean {
return code >= ErrorCode.SchemaResolveError;
}

export type ASTNode = ObjectASTNode | PropertyASTNode | ArrayASTNode | StringASTNode | NumberASTNode | BooleanASTNode | NullASTNode;
Expand Down Expand Up @@ -203,7 +207,8 @@ export interface WorkspaceContextService {
}
/**
* The schema request service is used to fetch schemas. If successful, returns a resolved promise with the content of the schema.
* In case of an error, returns a rejected promise with a displayable error string.
* In case of an error, returns a rejected promise with an Error object. If the type is of form { message: string, code: number }, the
* error code will be used for diagnostics.
*/
export interface SchemaRequestService {
(uri: string): PromiseLike<string>;
Expand Down
67 changes: 42 additions & 25 deletions src/services/jsonSchemaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { JSONSchema, JSONSchemaMap, JSONSchemaRef } from '../jsonSchema';
import { URI } from 'vscode-uri';
import * as Strings from '../utils/strings';
import { asSchema, getSchemaDraftFromId, JSONDocument, normalizeId } from '../parser/jsonParser';
import { SchemaRequestService, WorkspaceContextService, PromiseConstructor, MatchingSchema, TextDocument, SchemaConfiguration, SchemaDraft } from '../jsonLanguageTypes';
import { SchemaRequestService, WorkspaceContextService, PromiseConstructor, MatchingSchema, TextDocument, SchemaConfiguration, SchemaDraft, ErrorCode } from '../jsonLanguageTypes';

import * as l10n from '@vscode/l10n';
import { createRegex } from '../utils/glob';
Expand Down Expand Up @@ -181,21 +181,27 @@ class SchemaHandle implements ISchemaHandle {

export class UnresolvedSchema {
public schema: JSONSchema;
public errors: string[];
public errors: SchemaDiagnostic[];

constructor(schema: JSONSchema, errors: string[] = []) {
constructor(schema: JSONSchema, errors: SchemaDiagnostic[] = []) {
this.schema = schema;
this.errors = errors;
}
}

export type SchemaDiagnostic = { message: string; code: ErrorCode }

function toDiagnostic(message: string, code: ErrorCode): SchemaDiagnostic {
return { message, code };
}

export class ResolvedSchema {
public readonly schema: JSONSchema;
public readonly errors: string[];
public readonly warnings: string[];
public readonly errors: SchemaDiagnostic[];
public readonly warnings: SchemaDiagnostic[];
public readonly schemaDraft: SchemaDraft | undefined;

constructor(schema: JSONSchema, errors: string[] = [], warnings: string[] = [], schemaDraft: SchemaDraft | undefined) {
constructor(schema: JSONSchema, errors: SchemaDiagnostic[] = [], warnings: SchemaDiagnostic[] = [], schemaDraft: SchemaDraft | undefined) {
this.schema = schema;
this.errors = errors;
this.warnings = warnings;
Expand Down Expand Up @@ -388,51 +394,60 @@ export class JSONSchemaService implements IJSONSchemaService {
public loadSchema(url: string): PromiseLike<UnresolvedSchema> {
if (!this.requestService) {
const errorMessage = l10n.t('Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
return this.promise.resolve(new UnresolvedSchema(<JSONSchema>{}, [errorMessage]));
return this.promise.resolve(new UnresolvedSchema(<JSONSchema>{}, [toDiagnostic(errorMessage, ErrorCode.SchemaResolveError)]));
}
return this.requestService(url).then(
content => {
if (!content) {
const errorMessage = l10n.t('Unable to load schema from \'{0}\': No content.', toDisplayString(url));
return new UnresolvedSchema(<JSONSchema>{}, [errorMessage]);
return new UnresolvedSchema(<JSONSchema>{}, [toDiagnostic(errorMessage, ErrorCode.SchemaResolveError)]);
}
const errors = [];
if (content.charCodeAt(0) === 65279) {
errors.push(l10n.t('Problem reading content from \'{0}\': UTF-8 with BOM detected, only UTF 8 is allowed.', toDisplayString(url)));
errors.push(toDiagnostic(l10n.t('Problem reading content from \'{0}\': UTF-8 with BOM detected, only UTF 8 is allowed.', toDisplayString(url)), ErrorCode.SchemaResolveError));
content = content.trimStart();
}

let schemaContent: JSONSchema = {};
const jsonErrors: Json.ParseError[] = [];
schemaContent = Json.parse(content, jsonErrors);
if (jsonErrors.length) {
errors.push(l10n.t('Unable to parse content from \'{0}\': Parse error at offset {1}.', toDisplayString(url), jsonErrors[0].offset));
errors.push(toDiagnostic(l10n.t('Unable to parse content from \'{0}\': Parse error at offset {1}.', toDisplayString(url), jsonErrors[0].offset), ErrorCode.SchemaResolveError));
}
return new UnresolvedSchema(schemaContent, errors);
},
(error: any) => {
let errorMessage = error.toString() as string;
const errorSplit = error.toString().split('Error: ');
if (errorSplit.length > 1) {
// more concise error message, URL and context are attached by caller anyways
errorMessage = errorSplit[1];
let { message, code } = error;
if (typeof message !== 'string') {
let errorMessage = error.toString() as string;
const errorSplit = error.toString().split('Error: ');
if (errorSplit.length > 1) {
// more concise error message, URL and context are attached by caller anyways
errorMessage = errorSplit[1];
}
if (Strings.endsWith(errorMessage, '.')) {
errorMessage = errorMessage.substr(0, errorMessage.length - 1);
}
message = errorMessage;
}
if (Strings.endsWith(errorMessage, '.')) {
errorMessage = errorMessage.substr(0, errorMessage.length - 1);
let errorCode = ErrorCode.SchemaResolveError;
if (typeof code === 'number' && code < 0x10000) {
errorCode += code;
}
return new UnresolvedSchema(<JSONSchema>{}, [l10n.t('Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
const errorMessage = l10n.t('Unable to load schema from \'{0}\': {1}.', toDisplayString(url), message);
return new UnresolvedSchema(<JSONSchema>{}, [toDiagnostic(errorMessage, errorCode)]);
}
);
}

public resolveSchemaContent(schemaToResolve: UnresolvedSchema, handle: SchemaHandle): PromiseLike<ResolvedSchema> {

const resolveErrors: string[] = schemaToResolve.errors.slice(0);
const resolveErrors: SchemaDiagnostic[] = schemaToResolve.errors.slice(0);
const schema = schemaToResolve.schema;

const schemaDraft = schema.$schema ? getSchemaDraftFromId(schema.$schema) : undefined;
if (schemaDraft === SchemaDraft.v3) {
return this.promise.resolve(new ResolvedSchema({}, [l10n.t("Draft-03 schemas are not supported.")], [], schemaDraft));
return this.promise.resolve(new ResolvedSchema({}, [toDiagnostic(l10n.t("Draft-03 schemas are not supported."), ErrorCode.SchemaUnsupportedFeature)], [], schemaDraft));
}

let usesUnsupportedFeatures = new Set();
Expand Down Expand Up @@ -482,7 +497,8 @@ export class JSONSchemaService implements IJSONSchemaService {
if (section) {
merge(target, section);
} else {
resolveErrors.push(l10n.t('$ref \'{0}\' in \'{1}\' can not be resolved.', refSegment || '', sourceHandle.uri));
const message = l10n.t('$ref \'{0}\' in \'{1}\' can not be resolved.', refSegment || '', sourceHandle.uri)
resolveErrors.push(toDiagnostic(message, ErrorCode.SchemaResolveError));
}
};

Expand All @@ -496,7 +512,8 @@ export class JSONSchemaService implements IJSONSchemaService {
parentHandle.dependencies.add(uri);
if (unresolvedSchema.errors.length) {
const loc = refSegment ? uri + '#' + refSegment : uri;
resolveErrors.push(l10n.t('Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
const errorMessage = l10n.t('Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0].message);
resolveErrors.push(toDiagnostic(errorMessage, unresolvedSchema.errors[0].code));
}
mergeRef(node, unresolvedSchema.schema, referencedHandle, refSegment);
return resolveRefs(node, unresolvedSchema.schema, referencedHandle);
Expand Down Expand Up @@ -543,7 +560,7 @@ export class JSONSchemaService implements IJSONSchemaService {
const anchor = isString(id) && id.charAt(0) === '#' ? id.substring(1) : next.$anchor;
if (anchor) {
if (result.has(anchor)) {
resolveErrors.push(l10n.t('Duplicate anchor declaration: \'{0}\'', anchor));
resolveErrors.push(toDiagnostic(l10n.t('Duplicate anchor declaration: \'{0}\'', anchor), ErrorCode.SchemaResolveError));
} else {
result.set(anchor, next);
}
Expand All @@ -558,9 +575,9 @@ export class JSONSchemaService implements IJSONSchemaService {
return result;
};
return resolveRefs(schema, schema, handle).then(_ => {
let resolveWarnings: string[] = [];
let resolveWarnings: SchemaDiagnostic[] = [];
if (usesUnsupportedFeatures.size) {
resolveWarnings.push(l10n.t('The schema uses meta-schema features ({0}) that are not yet supported by the validator.', Array.from(usesUnsupportedFeatures.keys()).join(', ')));
resolveWarnings.push(toDiagnostic(l10n.t('The schema uses meta-schema features ({0}) that are not yet supported by the validator.', Array.from(usesUnsupportedFeatures.keys()).join(', ')), ErrorCode.SchemaUnsupportedFeature));
}
return new ResolvedSchema(schema, resolveErrors, resolveWarnings, schemaDraft);
});
Expand Down
6 changes: 3 additions & 3 deletions src/services/jsonValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class JSONValidation {
this.validationEnabled = true;
}

public configure(raw: LanguageSettings) {
public configure(raw: LanguageSettings | undefined): void {
if (raw) {
this.validationEnabled = raw.validate !== false;
this.commentSeverity = raw.allowComments ? undefined : DiagnosticSeverity.Error;
Expand Down Expand Up @@ -68,10 +68,10 @@ export class JSONValidation {
}
};
if (schema.errors.length) {
addSchemaProblem(schema.errors[0], ErrorCode.SchemaResolveError);
addSchemaProblem(schema.errors[0].message, schema.errors[0].code);
} else if (schemaValidation) {
for (const warning of schema.warnings) {
addSchemaProblem(warning, ErrorCode.SchemaUnsupportedFeature);
addSchemaProblem(warning.message, warning.code);
}
const semanticErrors = jsonDocument.validate(textDocument, schema.schema, schemaValidation, documentSettings?.schemaDraft);
if (semanticErrors) {
Expand Down
5 changes: 2 additions & 3 deletions src/test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { promises as fs } from 'fs';
import * as url from 'url';
import * as path from 'path';
import { getLanguageService, JSONSchema, SchemaRequestService, TextDocument, MatchingSchema, LanguageService } from '../jsonLanguageService';
import { DiagnosticSeverity, SchemaConfiguration } from '../jsonLanguageTypes';
import { DiagnosticSeverity, ErrorCode, SchemaConfiguration } from '../jsonLanguageTypes';

function toDocument(text: string, config?: Parser.JSONDocumentConfig, uri = 'foo://bar/file.json'): { textDoc: TextDocument, jsonDoc: Parser.JSONDocument } {

Expand Down Expand Up @@ -1079,8 +1079,7 @@ suite('JSON Schema', () => {
service.clearExternalSchemas();

resolvedSchema = await service.getSchemaForResource('main.bar');
assert.strictEqual(resolvedSchema?.errors.length, 1);
assert.strictEqual(resolvedSchema?.errors[0], "Problems loading reference 'http://myschemastore/myschemafoo': Unable to load schema from 'http://myschemastore/myschemafoo': Resource not found.");
assert.deepStrictEqual(resolvedSchema?.errors, [{ message: "Problems loading reference 'http://myschemastore/myschemafoo': Unable to load schema from 'http://myschemastore/myschemafoo': Resource not found.", code: ErrorCode.SchemaResolveError }]);

service.clearExternalSchemas();
service.registerExternalSchema({ uri: id2, schema: schema2 });
Expand Down