@@ -12,7 +12,6 @@ import {
12
12
JSONSchemaService ,
13
13
SchemaDependencies ,
14
14
ISchemaContributions ,
15
- SchemaHandle ,
16
15
} from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService' ;
17
16
18
17
import { URI } from 'vscode-uri' ;
@@ -29,6 +28,7 @@ import { SchemaVersions } from '../yamlTypes';
29
28
30
29
import Ajv , { DefinedError } from 'ajv' ;
31
30
import { getSchemaTitle } from '../utils/schemaUtils' ;
31
+ import { SchemaConfiguration } from 'vscode-json-languageservice' ;
32
32
33
33
const ajv = new Ajv ( ) ;
34
34
@@ -155,11 +155,9 @@ export class YAMLSchemaService extends JSONSchemaService {
155
155
return result ;
156
156
}
157
157
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 ;
163
161
const resolveErrors : string [ ] = schemaToResolve . errors . slice ( 0 ) ;
164
162
let schema : JSONSchema = schemaToResolve . schema ;
165
163
const contextService = this . contextService ;
@@ -374,7 +372,7 @@ export class YAMLSchemaService extends JSONSchemaService {
374
372
const schemaHandle = super . createCombinedSchema ( resource , schemas ) ;
375
373
return schemaHandle . getResolvedSchema ( ) . then ( ( schema ) => {
376
374
if ( schema . schema && typeof schema . schema === 'object' ) {
377
- schema . schema . url = schemaHandle . url ;
375
+ schema . schema . url = schemaHandle . uri ;
378
376
}
379
377
380
378
if (
@@ -431,6 +429,7 @@ export class YAMLSchemaService extends JSONSchemaService {
431
429
( schemas ) => {
432
430
return {
433
431
errors : [ ] ,
432
+ warnings : [ ] ,
434
433
schema : {
435
434
allOf : schemas . map ( ( schemaObj ) => {
436
435
return schemaObj . schema ;
@@ -503,7 +502,7 @@ export class YAMLSchemaService extends JSONSchemaService {
503
502
504
503
private async resolveCustomSchema ( schemaUri , doc ) : ResolvedSchema {
505
504
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 ) ) ;
507
506
if ( schema . schema && typeof schema . schema === 'object' ) {
508
507
schema . schema . url = schemaUri ;
509
508
}
@@ -614,8 +613,18 @@ export class YAMLSchemaService extends JSONSchemaService {
614
613
615
614
normalizeId ( id : string ) : string {
616
615
// The parent's `super.normalizeId(id)` isn't visible, so duplicated the code here
616
+ if ( ! id . includes ( ':' ) ) {
617
+ return id ;
618
+ }
617
619
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 ;
619
628
} catch ( e ) {
620
629
return id ;
621
630
}
@@ -684,25 +693,44 @@ export class YAMLSchemaService extends JSONSchemaService {
684
693
}
685
694
686
695
registerExternalSchema (
687
- uri : string ,
688
- filePatterns ?: string [ ] ,
689
- unresolvedSchema ?: JSONSchema ,
696
+ schemaConfig : SchemaConfiguration ,
690
697
name ?: string ,
691
698
description ?: string ,
692
699
versions ?: SchemaVersions
693
700
) : SchemaHandle {
694
701
if ( name || description ) {
695
- this . schemaUriToNameAndDescription . set ( uri , { name, description, versions } ) ;
702
+ this . schemaUriToNameAndDescription . set ( schemaConfig . uri , { name, description, versions } ) ;
696
703
}
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 ) ;
698
712
}
699
713
700
714
clearExternalSchemas ( ) : void {
701
715
super . clearExternalSchemas ( ) ;
702
716
}
703
717
704
718
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
+ }
706
734
}
707
735
708
736
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -711,14 +739,62 @@ export class YAMLSchemaService extends JSONSchemaService {
711
739
}
712
740
713
741
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 ) ;
715
748
}
716
749
717
750
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 ;
719
773
}
720
774
}
721
775
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
+
722
798
function toDisplayString ( url : string ) : string {
723
799
try {
724
800
const uri = URI . parse ( url ) ;
@@ -730,3 +806,47 @@ function toDisplayString(url: string): string {
730
806
}
731
807
return url ;
732
808
}
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