@@ -21,7 +21,19 @@ import { TextDocument } from "vscode-languageserver-textdocument";
2121import { errors , transformer } from "@openfga/syntax-transformer" ;
2222import { defaultDocumentationMap } from "./documentation" ;
2323import { getDuplicationFix , getMissingDefinitionFix , getReservedTypeNameFix } from "./code-action" ;
24- import { LineCounter , YAMLSeq , parseDocument } from "yaml" ;
24+ import {
25+ LineCounter ,
26+ YAMLSeq ,
27+ parseDocument ,
28+ Range as TokenRange ,
29+ isScalar ,
30+ visitAsync ,
31+ Scalar ,
32+ Pair ,
33+ Document ,
34+ visit ,
35+ } from "yaml" ;
36+ import { stringify } from "json-to-pretty-yaml" ;
2537import {
2638 YAMLSourceMap ,
2739 YamlStoreValidateResults ,
@@ -31,6 +43,7 @@ import {
3143 validateYamlStore ,
3244 getFieldPosition ,
3345 getRangeFromToken ,
46+ DocumentLoc ,
3447} from "./yaml-utils" ;
3548import { getRangeOfWord } from "./helpers" ;
3649import { getDiagnosticsForDsl as validateDSL } from "./dsl-utils" ;
@@ -100,16 +113,109 @@ export function startServer(connection: _Connection) {
100113 connection . languages . diagnostics . refresh ( ) ;
101114 } ) ;
102115
103- async function validateYamlSyntaxAndModel ( textDocument : TextDocument ) : Promise < YamlStoreValidateResults > {
104- const diagnostics : Diagnostic [ ] = [ ] ;
105- const modelDiagnostics : Diagnostic [ ] = [ ] ;
106-
116+ async function parseYamlStore (
117+ textDocument : TextDocument ,
118+ ) : Promise < { yamlDoc : Document ; lineCounter : LineCounter ; parsedDiagnostics : Diagnostic [ ] } > {
107119 const lineCounter = new LineCounter ( ) ;
108120 const yamlDoc = parseDocument ( textDocument . getText ( ) , {
109121 lineCounter,
110122 keepSourceTokens : true ,
111123 } ) ;
112124
125+ const parsedDiagnostics : Diagnostic [ ] = [ ] ;
126+
127+ // Basic syntax errors
128+ for ( const err of yamlDoc . errors ) {
129+ parsedDiagnostics . push ( { message : err . message , range : rangeFromLinePos ( err . linePos ) } ) ;
130+ }
131+
132+ const importedDocs = new Map < string , DocumentLoc > ( ) ;
133+
134+ await visitAsync ( yamlDoc , {
135+ async Pair ( _ , pair ) {
136+ if ( ! isScalar ( pair . key ) || ! isScalar ( pair . value ) || pair . key . value !== "tuple_file" || ! pair . value . source ) {
137+ return ;
138+ }
139+
140+ const originalRange = pair . key . range ;
141+ try {
142+ const result = await getFileContents ( URI . parse ( textDocument . uri ) , pair . value . source ) ;
143+ if ( pair . value . source . match ( / .y a m l $ / ) ) {
144+ const file = parseDocument ( result . contents ) ;
145+
146+ const diagnosticFromInclusion : Diagnostic [ ] = [ ] ;
147+
148+ diagnosticFromInclusion . push (
149+ ...file . errors . map ( ( err ) => {
150+ return {
151+ source : "ParseError" ,
152+ message : "error with external file: " + err . message ,
153+ range : getRangeFromToken ( originalRange , textDocument ) ,
154+ } ;
155+ } ) ,
156+ ) ;
157+
158+ if ( diagnosticFromInclusion . length ) {
159+ parsedDiagnostics . push ( ...diagnosticFromInclusion ) ;
160+ return undefined ;
161+ }
162+
163+ if ( originalRange ) {
164+ importedDocs . set ( pair . value . source , { range : originalRange , doc : file } ) ;
165+ }
166+ return visit . SKIP ;
167+ }
168+ } catch ( err ) {
169+ parsedDiagnostics . push ( {
170+ range : getRangeFromToken ( originalRange , textDocument ) ,
171+ message : "error with external file: " + ( err as Error ) . message ,
172+ source : "ParseError" ,
173+ } ) ;
174+ }
175+ } ,
176+ } ) ;
177+
178+ // Override all tuples with new location
179+ for ( const p of importedDocs . entries ( ) ) {
180+ visit ( p [ 1 ] . doc . contents , {
181+ Scalar ( key , node ) {
182+ node . range = p [ 1 ] . range ;
183+ } ,
184+ } ) ;
185+ }
186+
187+ // Prepare final virtual doc
188+ visit ( yamlDoc , {
189+ Pair ( _ , pair ) {
190+ if ( ! isScalar ( pair . key ) || ! isScalar ( pair . value ) || pair . key . value !== "tuple_file" || ! pair . value . source ) {
191+ return ;
192+ }
193+
194+ const value = importedDocs . get ( pair . value . source ) ;
195+
196+ if ( value ) {
197+ // Import tuples, and point range at where file field used to exist
198+ const scalar = new Scalar ( "tuples" ) ;
199+ scalar . source = "tuples" ;
200+ scalar . range = value ?. range ;
201+
202+ return new Pair ( scalar , value ?. doc . contents ) ;
203+ }
204+ } ,
205+ } ) ;
206+ return { yamlDoc, lineCounter, parsedDiagnostics } ;
207+ }
208+
209+ async function validateYamlSyntaxAndModel ( textDocument : TextDocument ) : Promise < YamlStoreValidateResults > {
210+ const diagnostics : Diagnostic [ ] = [ ] ;
211+ const modelDiagnostics : Diagnostic [ ] = [ ] ;
212+
213+ const { yamlDoc, lineCounter, parsedDiagnostics } = await parseYamlStore ( textDocument ) ;
214+
215+ if ( parsedDiagnostics . length ) {
216+ return { diagnostics : parsedDiagnostics } ;
217+ }
218+
113219 const map = new YAMLSourceMap ( ) ;
114220 map . doMap ( yamlDoc . contents ) ;
115221
@@ -119,25 +225,6 @@ export function startServer(connection: _Connection) {
119225 return { diagnostics } ;
120226 }
121227
122- // Basic syntax errors
123- for ( const err of yamlDoc . errors ) {
124- diagnostics . push ( { message : err . message , range : rangeFromLinePos ( err . linePos ) } ) ;
125- }
126-
127- const keys = [ ...map . nodes . keys ( ) ] . filter ( ( key ) => key . includes ( "tuple_file" ) ) ;
128- for ( const fileField of keys ) {
129- const fileName = yamlDoc . getIn ( fileField . split ( "." ) ) as string ;
130- try {
131- await getFileContents ( URI . parse ( textDocument . uri ) , fileName ) ;
132- } catch ( err ) {
133- diagnostics . push ( {
134- range : getRangeFromToken ( map . nodes . get ( fileField ) , textDocument ) ,
135- message : "error with external file: " + ( err as Error ) . message ,
136- source : "ParseError" ,
137- } ) ;
138- }
139- }
140-
141228 let model ,
142229 modelUri = undefined ;
143230
@@ -147,7 +234,7 @@ export function startServer(connection: _Connection) {
147234 diagnostics . push ( ...parseYamlModel ( yamlDoc , lineCounter ) ) ;
148235 diagnostics . push ( ...validateYamlStore ( yamlDoc . get ( "model" ) as string , yamlDoc , textDocument , map ) ) ;
149236 } else if ( yamlDoc . has ( "model_file" ) ) {
150- const position = getFieldPosition ( yamlDoc , lineCounter , "model_file" ) ;
237+ const position = getFieldPosition ( yamlDoc , lineCounter , "model_file" ) [ 0 ] ;
151238 const modelFile = yamlDoc . get ( "model_file" ) as string ;
152239
153240 try {
0 commit comments