@@ -12,7 +12,6 @@ import {
1212 JSONSchemaService ,
1313 SchemaDependencies ,
1414 ISchemaContributions ,
15- SchemaHandle ,
1615} from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService' ;
1716
1817import { URI } from 'vscode-uri' ;
@@ -29,6 +28,7 @@ import { SchemaVersions } from '../yamlTypes';
2928
3029import Ajv , { DefinedError } from 'ajv' ;
3130import { getSchemaTitle } from '../utils/schemaUtils' ;
31+ import { SchemaConfiguration } from 'vscode-json-languageservice' ;
3232
3333const ajv = new Ajv ( ) ;
3434
@@ -155,11 +155,9 @@ export class YAMLSchemaService extends JSONSchemaService {
155155 return result ;
156156 }
157157
158- async resolveSchemaContent (
159- schemaToResolve : UnresolvedSchema ,
160- schemaURL : string ,
161- dependencies : SchemaDependencies
162- ) : Promise < ResolvedSchema > {
158+ async resolveSchemaContent ( schemaToResolve : UnresolvedSchema , schemaHandle : SchemaHandle ) : Promise < ResolvedSchema > {
159+ const schemaURL : string = normalizeId ( schemaHandle . uri ) ;
160+ const dependencies : SchemaDependencies = schemaHandle . dependencies ;
163161 const resolveErrors : string [ ] = schemaToResolve . errors . slice ( 0 ) ;
164162 let schema : JSONSchema = schemaToResolve . schema ;
165163 const contextService = this . contextService ;
@@ -374,7 +372,7 @@ export class YAMLSchemaService extends JSONSchemaService {
374372 const schemaHandle = super . createCombinedSchema ( resource , schemas ) ;
375373 return schemaHandle . getResolvedSchema ( ) . then ( ( schema ) => {
376374 if ( schema . schema && typeof schema . schema === 'object' ) {
377- schema . schema . url = schemaHandle . url ;
375+ schema . schema . url = schemaHandle . uri ;
378376 }
379377
380378 if (
@@ -431,6 +429,7 @@ export class YAMLSchemaService extends JSONSchemaService {
431429 ( schemas ) => {
432430 return {
433431 errors : [ ] ,
432+ warnings : [ ] ,
434433 schema : {
435434 allOf : schemas . map ( ( schemaObj ) => {
436435 return schemaObj . schema ;
@@ -503,7 +502,7 @@ export class YAMLSchemaService extends JSONSchemaService {
503502
504503 private async resolveCustomSchema ( schemaUri , doc ) : ResolvedSchema {
505504 const unresolvedSchema = await this . loadSchema ( schemaUri ) ;
506- const schema = await this . resolveSchemaContent ( unresolvedSchema , schemaUri , [ ] ) ;
505+ const schema = await this . resolveSchemaContent ( unresolvedSchema , new SchemaHandle ( this , schemaUri ) ) ;
507506 if ( schema . schema && typeof schema . schema === 'object' ) {
508507 schema . schema . url = schemaUri ;
509508 }
@@ -614,8 +613,18 @@ export class YAMLSchemaService extends JSONSchemaService {
614613
615614 normalizeId ( id : string ) : string {
616615 // The parent's `super.normalizeId(id)` isn't visible, so duplicated the code here
616+ if ( ! id . includes ( ':' ) ) {
617+ return id ;
618+ }
617619 try {
618- return URI . parse ( id ) . toString ( ) ;
620+ const uri = URI . parse ( id ) ;
621+ if ( ! id . includes ( '#' ) ) {
622+ return uri . toString ( ) ;
623+ }
624+ // fragment should be verbatim, but vscode-uri converts `/` to the escaped version (annoyingly, needlessly)
625+ const [ first , second ] = uri . toString ( ) . split ( '#' , 2 ) ;
626+ const secondCleaned = second . replace ( '%2F' , '/' ) ;
627+ return first + '#' + secondCleaned ;
619628 } catch ( e ) {
620629 return id ;
621630 }
@@ -684,25 +693,44 @@ export class YAMLSchemaService extends JSONSchemaService {
684693 }
685694
686695 registerExternalSchema (
687- uri : string ,
688- filePatterns ?: string [ ] ,
689- unresolvedSchema ?: JSONSchema ,
696+ schemaConfig : SchemaConfiguration ,
690697 name ?: string ,
691698 description ?: string ,
692699 versions ?: SchemaVersions
693700 ) : SchemaHandle {
694701 if ( name || description ) {
695- this . schemaUriToNameAndDescription . set ( uri , { name, description, versions } ) ;
702+ this . schemaUriToNameAndDescription . set ( schemaConfig . uri , { name, description, versions } ) ;
696703 }
697- return super . registerExternalSchema ( uri , filePatterns , unresolvedSchema ) ;
704+ this . registeredSchemasIds [ schemaConfig . uri ] = true ;
705+ this . cachedSchemaForResource = undefined ;
706+ if ( schemaConfig . fileMatch && schemaConfig . fileMatch . length ) {
707+ this . addFilePatternAssociation ( schemaConfig . fileMatch , schemaConfig . folderUri , [ schemaConfig . uri ] ) ;
708+ }
709+ return schemaConfig . schema
710+ ? this . addSchemaHandle ( schemaConfig . uri , schemaConfig . schema )
711+ : this . getOrAddSchemaHandle ( schemaConfig . uri ) ;
698712 }
699713
700714 clearExternalSchemas ( ) : void {
701715 super . clearExternalSchemas ( ) ;
702716 }
703717
704718 setSchemaContributions ( schemaContributions : ISchemaContributions ) : void {
705- super . setSchemaContributions ( schemaContributions ) ;
719+ if ( schemaContributions . schemas ) {
720+ const schemas = schemaContributions . schemas ;
721+ for ( const id in schemas ) {
722+ const normalizedId = normalizeId ( id ) ;
723+ this . contributionSchemas [ normalizedId ] = this . addSchemaHandle ( normalizedId , schemas [ id ] ) ;
724+ }
725+ }
726+ if ( Array . isArray ( schemaContributions . schemaAssociations ) ) {
727+ const schemaAssociations = schemaContributions . schemaAssociations ;
728+ for ( const schemaAssociation of schemaAssociations ) {
729+ const uris = schemaAssociation . uris . map ( normalizeId ) ;
730+ const association = this . addFilePatternAssociation ( schemaAssociation . pattern , schemaAssociation . folderUri , uris ) ;
731+ this . contributionAssociations . push ( association ) ;
732+ }
733+ }
706734 }
707735
708736 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -711,14 +739,62 @@ export class YAMLSchemaService extends JSONSchemaService {
711739 }
712740
713741 getResolvedSchema ( schemaId : string ) : Promise < ResolvedSchema > {
714- return super . getResolvedSchema ( schemaId ) ;
742+ const id = normalizeId ( schemaId ) ;
743+ const schemaHandle = this . schemasById [ id ] ;
744+ if ( schemaHandle ) {
745+ return schemaHandle . getResolvedSchema ( ) ;
746+ }
747+ return this . promise . resolve ( undefined ) ;
715748 }
716749
717750 onResourceChange ( uri : string ) : boolean {
718- return super . onResourceChange ( uri ) ;
751+ // always clear this local cache when a resource changes
752+ this . cachedSchemaForResource = undefined ;
753+ let hasChanges = false ;
754+ uri = normalizeId ( uri ) ;
755+ const toWalk = [ uri ] ;
756+ const all = Object . keys ( this . schemasById ) . map ( ( key ) => this . schemasById [ key ] ) ;
757+ while ( toWalk . length ) {
758+ const curr = toWalk . pop ( ) ;
759+ for ( let i = 0 ; i < all . length ; i ++ ) {
760+ const handle = all [ i ] ;
761+ if ( handle && ( handle . uri === curr || handle . dependencies . has ( curr ) ) ) {
762+ if ( handle . uri !== curr ) {
763+ toWalk . push ( handle . uri ) ;
764+ }
765+ if ( handle . clearSchema ( ) ) {
766+ hasChanges = true ;
767+ }
768+ all [ i ] = undefined ;
769+ }
770+ }
771+ }
772+ return hasChanges ;
719773 }
720774}
721775
776+ /**
777+ * Our version of normalize id, which doesn't prepend `file:///` to anything without a scheme.
778+ *
779+ * @param id the id to normalize
780+ * @returns the normalized id.
781+ */
782+ function normalizeId ( id : string ) : string {
783+ if ( id . includes ( ':' ) ) {
784+ try {
785+ if ( id . includes ( '#' ) ) {
786+ const [ mostOfIt , fragment ] = id . split ( '#' , 2 ) ;
787+ return URI . parse ( mostOfIt ) + '#' + fragment ;
788+ } else {
789+ return URI . parse ( id ) . toString ( ) ;
790+ }
791+ } catch {
792+ return id ;
793+ }
794+ }
795+ return id ;
796+ }
797+
722798function toDisplayString ( url : string ) : string {
723799 try {
724800 const uri = URI . parse ( url ) ;
@@ -730,3 +806,47 @@ function toDisplayString(url: string): string {
730806 }
731807 return url ;
732808}
809+
810+ class SchemaHandle {
811+ public readonly uri : string ;
812+ public readonly dependencies : SchemaDependencies ;
813+ public anchors : Map < string , JSONSchema > | undefined ;
814+ private resolvedSchema : Promise < ResolvedSchema > | undefined ;
815+ private unresolvedSchema : Promise < UnresolvedSchema > | undefined ;
816+ private readonly service : JSONSchemaService ;
817+
818+ constructor ( service : JSONSchemaService , uri : string , unresolvedSchemaContent ?: JSONSchema ) {
819+ this . service = service ;
820+ this . uri = uri ;
821+ this . dependencies = new Set ( ) ;
822+ this . anchors = undefined ;
823+ if ( unresolvedSchemaContent ) {
824+ this . unresolvedSchema = this . service . promise . resolve ( new UnresolvedSchema ( unresolvedSchemaContent ) ) ;
825+ }
826+ }
827+
828+ public getUnresolvedSchema ( ) : Promise < UnresolvedSchema > {
829+ if ( ! this . unresolvedSchema ) {
830+ this . unresolvedSchema = this . service . loadSchema ( this . uri ) ;
831+ }
832+ return this . unresolvedSchema ;
833+ }
834+
835+ public getResolvedSchema ( ) : Promise < ResolvedSchema > {
836+ if ( ! this . resolvedSchema ) {
837+ this . resolvedSchema = this . getUnresolvedSchema ( ) . then ( ( unresolved ) => {
838+ return this . service . resolveSchemaContent ( unresolved , this ) ;
839+ } ) ;
840+ }
841+ return this . resolvedSchema ;
842+ }
843+
844+ public clearSchema ( ) : boolean {
845+ const hasChanges = ! ! this . unresolvedSchema ;
846+ this . resolvedSchema = undefined ;
847+ this . unresolvedSchema = undefined ;
848+ this . dependencies . clear ( ) ;
849+ this . anchors = undefined ;
850+ return hasChanges ;
851+ }
852+ }
0 commit comments