@@ -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' ;
@@ -30,6 +29,7 @@ import * as Json from 'jsonc-parser';
30
29
import Ajv , { DefinedError } from 'ajv' ;
31
30
import Ajv4 from 'ajv-draft-04' ;
32
31
import { getSchemaTitle } from '../utils/schemaUtils' ;
32
+ import { SchemaConfiguration } from 'vscode-json-languageservice' ;
33
33
34
34
const ajv = new Ajv ( ) ;
35
35
const ajv4 = new Ajv4 ( ) ;
@@ -160,11 +160,9 @@ export class YAMLSchemaService extends JSONSchemaService {
160
160
return result ;
161
161
}
162
162
163
- async resolveSchemaContent (
164
- schemaToResolve : UnresolvedSchema ,
165
- schemaURL : string ,
166
- dependencies : SchemaDependencies
167
- ) : Promise < ResolvedSchema > {
163
+ async resolveSchemaContent ( schemaToResolve : UnresolvedSchema , schemaHandle : SchemaHandle ) : Promise < ResolvedSchema > {
164
+ const schemaURL : string = normalizeId ( schemaHandle . uri ) ;
165
+ const dependencies : SchemaDependencies = schemaHandle . dependencies ;
168
166
const resolveErrors : string [ ] = schemaToResolve . errors . slice ( 0 ) ;
169
167
let schema : JSONSchema = schemaToResolve . schema ;
170
168
const contextService = this . contextService ;
@@ -381,7 +379,7 @@ export class YAMLSchemaService extends JSONSchemaService {
381
379
const schemaHandle = super . createCombinedSchema ( resource , schemas ) ;
382
380
return schemaHandle . getResolvedSchema ( ) . then ( ( schema ) => {
383
381
if ( schema . schema && typeof schema . schema === 'object' ) {
384
- schema . schema . url = schemaHandle . url ;
382
+ schema . schema . url = schemaHandle . uri ;
385
383
}
386
384
387
385
if (
@@ -438,6 +436,7 @@ export class YAMLSchemaService extends JSONSchemaService {
438
436
( schemas ) => {
439
437
return {
440
438
errors : [ ] ,
439
+ warnings : [ ] ,
441
440
schema : {
442
441
allOf : schemas . map ( ( schemaObj ) => {
443
442
return schemaObj . schema ;
@@ -510,7 +509,7 @@ export class YAMLSchemaService extends JSONSchemaService {
510
509
511
510
private async resolveCustomSchema ( schemaUri , doc ) : ResolvedSchema {
512
511
const unresolvedSchema = await this . loadSchema ( schemaUri ) ;
513
- const schema = await this . resolveSchemaContent ( unresolvedSchema , schemaUri , [ ] ) ;
512
+ const schema = await this . resolveSchemaContent ( unresolvedSchema , new SchemaHandle ( this , schemaUri ) ) ;
514
513
if ( schema . schema && typeof schema . schema === 'object' ) {
515
514
schema . schema . url = schemaUri ;
516
515
}
@@ -621,8 +620,18 @@ export class YAMLSchemaService extends JSONSchemaService {
621
620
622
621
normalizeId ( id : string ) : string {
623
622
// The parent's `super.normalizeId(id)` isn't visible, so duplicated the code here
623
+ if ( ! id . includes ( ':' ) ) {
624
+ return id ;
625
+ }
624
626
try {
625
- return URI . parse ( id ) . toString ( ) ;
627
+ const uri = URI . parse ( id ) ;
628
+ if ( ! id . includes ( '#' ) ) {
629
+ return uri . toString ( ) ;
630
+ }
631
+ // fragment should be verbatim, but vscode-uri converts `/` to the escaped version (annoyingly, needlessly)
632
+ const [ first , second ] = uri . toString ( ) . split ( '#' , 2 ) ;
633
+ const secondCleaned = second . replace ( '%2F' , '/' ) ;
634
+ return first + '#' + secondCleaned ;
626
635
} catch ( e ) {
627
636
return id ;
628
637
}
@@ -711,25 +720,44 @@ export class YAMLSchemaService extends JSONSchemaService {
711
720
}
712
721
713
722
registerExternalSchema (
714
- uri : string ,
715
- filePatterns ?: string [ ] ,
716
- unresolvedSchema ?: JSONSchema ,
723
+ schemaConfig : SchemaConfiguration ,
717
724
name ?: string ,
718
725
description ?: string ,
719
726
versions ?: SchemaVersions
720
727
) : SchemaHandle {
721
728
if ( name || description ) {
722
- this . schemaUriToNameAndDescription . set ( uri , { name, description, versions } ) ;
729
+ this . schemaUriToNameAndDescription . set ( schemaConfig . uri , { name, description, versions } ) ;
723
730
}
724
- return super . registerExternalSchema ( uri , filePatterns , unresolvedSchema ) ;
731
+ this . registeredSchemasIds [ schemaConfig . uri ] = true ;
732
+ this . cachedSchemaForResource = undefined ;
733
+ if ( schemaConfig . fileMatch && schemaConfig . fileMatch . length ) {
734
+ this . addFilePatternAssociation ( schemaConfig . fileMatch , schemaConfig . folderUri , [ schemaConfig . uri ] ) ;
735
+ }
736
+ return schemaConfig . schema
737
+ ? this . addSchemaHandle ( schemaConfig . uri , schemaConfig . schema )
738
+ : this . getOrAddSchemaHandle ( schemaConfig . uri ) ;
725
739
}
726
740
727
741
clearExternalSchemas ( ) : void {
728
742
super . clearExternalSchemas ( ) ;
729
743
}
730
744
731
745
setSchemaContributions ( schemaContributions : ISchemaContributions ) : void {
732
- super . setSchemaContributions ( schemaContributions ) ;
746
+ if ( schemaContributions . schemas ) {
747
+ const schemas = schemaContributions . schemas ;
748
+ for ( const id in schemas ) {
749
+ const normalizedId = normalizeId ( id ) ;
750
+ this . contributionSchemas [ normalizedId ] = this . addSchemaHandle ( normalizedId , schemas [ id ] ) ;
751
+ }
752
+ }
753
+ if ( Array . isArray ( schemaContributions . schemaAssociations ) ) {
754
+ const schemaAssociations = schemaContributions . schemaAssociations ;
755
+ for ( const schemaAssociation of schemaAssociations ) {
756
+ const uris = schemaAssociation . uris . map ( normalizeId ) ;
757
+ const association = this . addFilePatternAssociation ( schemaAssociation . pattern , schemaAssociation . folderUri , uris ) ;
758
+ this . contributionAssociations . push ( association ) ;
759
+ }
760
+ }
733
761
}
734
762
735
763
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -738,14 +766,62 @@ export class YAMLSchemaService extends JSONSchemaService {
738
766
}
739
767
740
768
getResolvedSchema ( schemaId : string ) : Promise < ResolvedSchema > {
741
- return super . getResolvedSchema ( schemaId ) ;
769
+ const id = normalizeId ( schemaId ) ;
770
+ const schemaHandle = this . schemasById [ id ] ;
771
+ if ( schemaHandle ) {
772
+ return schemaHandle . getResolvedSchema ( ) ;
773
+ }
774
+ return this . promise . resolve ( undefined ) ;
742
775
}
743
776
744
777
onResourceChange ( uri : string ) : boolean {
745
- return super . onResourceChange ( uri ) ;
778
+ // always clear this local cache when a resource changes
779
+ this . cachedSchemaForResource = undefined ;
780
+ let hasChanges = false ;
781
+ uri = normalizeId ( uri ) ;
782
+ const toWalk = [ uri ] ;
783
+ const all = Object . keys ( this . schemasById ) . map ( ( key ) => this . schemasById [ key ] ) ;
784
+ while ( toWalk . length ) {
785
+ const curr = toWalk . pop ( ) ;
786
+ for ( let i = 0 ; i < all . length ; i ++ ) {
787
+ const handle = all [ i ] ;
788
+ if ( handle && ( handle . uri === curr || handle . dependencies . has ( curr ) ) ) {
789
+ if ( handle . uri !== curr ) {
790
+ toWalk . push ( handle . uri ) ;
791
+ }
792
+ if ( handle . clearSchema ( ) ) {
793
+ hasChanges = true ;
794
+ }
795
+ all [ i ] = undefined ;
796
+ }
797
+ }
798
+ }
799
+ return hasChanges ;
746
800
}
747
801
}
748
802
803
+ /**
804
+ * Our version of normalize id, which doesn't prepend `file:///` to anything without a scheme.
805
+ *
806
+ * @param id the id to normalize
807
+ * @returns the normalized id.
808
+ */
809
+ function normalizeId ( id : string ) : string {
810
+ if ( id . includes ( ':' ) ) {
811
+ try {
812
+ if ( id . includes ( '#' ) ) {
813
+ const [ mostOfIt , fragment ] = id . split ( '#' , 2 ) ;
814
+ return URI . parse ( mostOfIt ) + '#' + fragment ;
815
+ } else {
816
+ return URI . parse ( id ) . toString ( ) ;
817
+ }
818
+ } catch {
819
+ return id ;
820
+ }
821
+ }
822
+ return id ;
823
+ }
824
+
749
825
function toDisplayString ( url : string ) : string {
750
826
try {
751
827
const uri = URI . parse ( url ) ;
@@ -764,3 +840,47 @@ function getLineAndColumnFromOffset(text: string, offset: number): { line: numbe
764
840
const column = lines [ lines . length - 1 ] . length + 1 ; // 1-based column number
765
841
return { line, column } ;
766
842
}
843
+
844
+ class SchemaHandle {
845
+ public readonly uri : string ;
846
+ public readonly dependencies : SchemaDependencies ;
847
+ public anchors : Map < string , JSONSchema > | undefined ;
848
+ private resolvedSchema : Promise < ResolvedSchema > | undefined ;
849
+ private unresolvedSchema : Promise < UnresolvedSchema > | undefined ;
850
+ private readonly service : JSONSchemaService ;
851
+
852
+ constructor ( service : JSONSchemaService , uri : string , unresolvedSchemaContent ?: JSONSchema ) {
853
+ this . service = service ;
854
+ this . uri = uri ;
855
+ this . dependencies = new Set ( ) ;
856
+ this . anchors = undefined ;
857
+ if ( unresolvedSchemaContent ) {
858
+ this . unresolvedSchema = this . service . promise . resolve ( new UnresolvedSchema ( unresolvedSchemaContent ) ) ;
859
+ }
860
+ }
861
+
862
+ public getUnresolvedSchema ( ) : Promise < UnresolvedSchema > {
863
+ if ( ! this . unresolvedSchema ) {
864
+ this . unresolvedSchema = this . service . loadSchema ( this . uri ) ;
865
+ }
866
+ return this . unresolvedSchema ;
867
+ }
868
+
869
+ public getResolvedSchema ( ) : Promise < ResolvedSchema > {
870
+ if ( ! this . resolvedSchema ) {
871
+ this . resolvedSchema = this . getUnresolvedSchema ( ) . then ( ( unresolved ) => {
872
+ return this . service . resolveSchemaContent ( unresolved , this ) ;
873
+ } ) ;
874
+ }
875
+ return this . resolvedSchema ;
876
+ }
877
+
878
+ public clearSchema ( ) : boolean {
879
+ const hasChanges = ! ! this . unresolvedSchema ;
880
+ this . resolvedSchema = undefined ;
881
+ this . unresolvedSchema = undefined ;
882
+ this . dependencies . clear ( ) ;
883
+ this . anchors = undefined ;
884
+ return hasChanges ;
885
+ }
886
+ }
0 commit comments