@@ -27,22 +27,29 @@ import { SchemaVersions } from '../yamlTypes';
2727
2828import { parse } from 'yaml' ;
2929import * as Json from 'jsonc-parser' ;
30- import Ajv , { DefinedError } from 'ajv' ;
30+ import Ajv , { DefinedError , type AnySchemaObject , type ValidateFunction } from 'ajv' ;
3131import Ajv4 from 'ajv-draft-04' ;
32- import { getSchemaTitle } from '../utils/schemaUtils' ;
32+ import Ajv2019 from 'ajv/dist/2019' ;
33+ import Ajv2020 from 'ajv/dist/2020' ;
3334
34- const ajv = new Ajv ( ) ;
35- const ajv4 = new Ajv4 ( ) ;
35+ const ajv4 = new Ajv4 ( { allErrors : true } ) ;
36+ const ajv7 = new Ajv ( { allErrors : true } ) ;
37+ const ajv2019 = new Ajv2019 ( { allErrors : true } ) ;
38+ const ajv2020 = new Ajv2020 ( { allErrors : true } ) ;
3639
37- // load JSON Schema 07 def to validate loaded schemas
40+ // eslint-disable-next-line @typescript-eslint/no-var-requires
41+ const jsonSchema04 = require ( 'ajv-draft-04/dist/refs/json-schema-draft-04.json' ) ;
3842// eslint-disable-next-line @typescript-eslint/no-var-requires
3943const jsonSchema07 = require ( 'ajv/dist/refs/json-schema-draft-07.json' ) ;
40- const schema07Validator = ajv . compile ( jsonSchema07 ) ;
41-
4244// eslint-disable-next-line @typescript-eslint/no-var-requires
43- const jsonSchema04 = require ( 'ajv-draft-04/dist/refs/json-schema-draft-04.json' ) ;
45+ const jsonSchema2019 = require ( 'ajv/dist/refs/json-schema-2019-09/schema.json' ) ;
46+ // eslint-disable-next-line @typescript-eslint/no-var-requires
47+ const jsonSchema2020 = require ( 'ajv/dist/refs/json-schema-2020-12/schema.json' ) ;
48+
4449const schema04Validator = ajv4 . compile ( jsonSchema04 ) ;
45- const SCHEMA_04_URI_WITH_HTTPS = ajv4 . defaultMeta ( ) . replace ( 'http://' , 'https://' ) ;
50+ const schema07Validator = ajv7 . compile ( jsonSchema07 ) ;
51+ const schema2019Validator = ajv2019 . compile ( jsonSchema2019 ) ;
52+ const schema2020Validator = ajv2020 . compile ( jsonSchema2020 ) ;
4653
4754export declare type CustomSchemaProvider = ( uri : string ) => Promise < string | string [ ] > ;
4855
@@ -166,19 +173,24 @@ export class YAMLSchemaService extends JSONSchemaService {
166173 dependencies : SchemaDependencies
167174 ) : Promise < ResolvedSchema > {
168175 const resolveErrors : string [ ] = schemaToResolve . errors . slice ( 0 ) ;
169- let schema : JSONSchema = schemaToResolve . schema ;
170- const contextService = this . contextService ;
176+ const loc = toDisplayString ( schemaURL ) ;
177+
178+ const raw : unknown = schemaToResolve . schema ;
179+ if ( raw === null || Array . isArray ( raw ) || ( typeof raw !== 'object' && typeof raw !== 'boolean' ) ) {
180+ const got = raw === null ? 'null' : Array . isArray ( raw ) ? 'array' : typeof raw ;
181+ resolveErrors . push ( l10n . t ( 'json.schema.invalidSchema' , loc , `expected a JSON Schema object or boolean, got ${ got } ` ) ) ;
182+ return new ResolvedSchema ( { } , resolveErrors ) ;
183+ }
171184
172- const validator =
173- this . normalizeId ( schema . $schema ) === ajv4 . defaultMeta ( ) || this . normalizeId ( schema . $schema ) === SCHEMA_04_URI_WITH_HTTPS
174- ? schema04Validator
175- : schema07Validator ;
176- if ( ! validator ( schema ) ) {
185+ const contextService = this . contextService ;
186+ let schema = raw as JSONSchema ;
187+ const validator = pickMetaValidator ( schema . $schema ) ;
188+ if ( validator && ! validator ( schema ) ) {
177189 const errs : string [ ] = [ ] ;
178190 for ( const err of validator . errors as DefinedError [ ] ) {
179191 errs . push ( `${ err . instancePath } : ${ err . message } ` ) ;
180192 }
181- resolveErrors . push ( `Schema ' ${ getSchemaTitle ( schemaToResolve . schema , schemaURL ) } ' is not valid: \n${ errs . join ( '\n' ) } `) ;
193+ resolveErrors . push ( l10n . t ( 'json .schema.invalidSchema' , loc , ` \n${ errs . join ( '\n' ) } `) ) ;
182194 }
183195
184196 const findSection = ( schema : JSONSchema , path : string ) : JSONSchema => {
@@ -764,3 +776,38 @@ function getLineAndColumnFromOffset(text: string, offset: number): { line: numbe
764776 const column = lines [ lines . length - 1 ] . length + 1 ; // 1-based column number
765777 return { line, column } ;
766778}
779+
780+ function normalizeSchemaUri ( uri : string | AnySchemaObject ) : string {
781+ if ( ! uri ) return '' ;
782+
783+ let s : string ;
784+ if ( typeof uri === 'string' ) {
785+ s = uri ;
786+ } else {
787+ s = uri . $id || uri . id || '' ;
788+ }
789+ s = s . trim ( ) ;
790+
791+ // strips fragment (# or #/something)
792+ const hash = s . indexOf ( '#' ) ;
793+
794+ s = hash === - 1 ? s : s . slice ( 0 , hash ) ;
795+
796+ // normalize http to https (don't normalize custom dialects)
797+ s = s . replace ( / ^ h t t p : \/ \/ j s o n - s c h e m a \. o r g \/ / i, 'https://json-schema.org/' ) ;
798+
799+ // normalize to no trailing slash
800+ s = s . replace ( / \/ + $ / g, '' ) ;
801+ return s ;
802+ }
803+
804+ function pickMetaValidator ( schema : string ) : ValidateFunction | undefined {
805+ const s = normalizeSchemaUri ( schema ) ;
806+ if ( s === normalizeSchemaUri ( ajv4 . defaultMeta ( ) ) ) return schema04Validator ;
807+ if ( s === normalizeSchemaUri ( ajv7 . defaultMeta ( ) ) ) return schema07Validator ;
808+ if ( s === normalizeSchemaUri ( ajv2019 . defaultMeta ( ) ) ) return schema2019Validator ;
809+ if ( s === normalizeSchemaUri ( ajv2020 . defaultMeta ( ) ) ) return schema2020Validator ;
810+
811+ // don't meta-validate unknown schema URI
812+ return undefined ;
813+ }
0 commit comments