@@ -21,7 +21,7 @@ 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 { LineCounter , YAMLSeq , parseDocument , isScalar , visitAsync , Scalar , Pair , Document , visit } from "yaml" ;
2525import {
2626 YAMLSourceMap ,
2727 YamlStoreValidateResults ,
@@ -31,6 +31,7 @@ import {
3131 validateYamlStore ,
3232 getFieldPosition ,
3333 getRangeFromToken ,
34+ DocumentLoc ,
3435} from "./yaml-utils" ;
3536import { getRangeOfWord } from "./helpers" ;
3637import { getDiagnosticsForDsl as validateDSL } from "./dsl-utils" ;
@@ -100,16 +101,109 @@ export function startServer(connection: _Connection) {
100101 connection . languages . diagnostics . refresh ( ) ;
101102 } ) ;
102103
103- async function validateYamlSyntaxAndModel ( textDocument : TextDocument ) : Promise < YamlStoreValidateResults > {
104- const diagnostics : Diagnostic [ ] = [ ] ;
105- const modelDiagnostics : Diagnostic [ ] = [ ] ;
106-
104+ async function parseYamlStore (
105+ textDocument : TextDocument ,
106+ ) : Promise < { yamlDoc : Document ; lineCounter : LineCounter ; parsedDiagnostics : Diagnostic [ ] } > {
107107 const lineCounter = new LineCounter ( ) ;
108108 const yamlDoc = parseDocument ( textDocument . getText ( ) , {
109109 lineCounter,
110110 keepSourceTokens : true ,
111111 } ) ;
112112
113+ const parsedDiagnostics : Diagnostic [ ] = [ ] ;
114+
115+ // Basic syntax errors
116+ for ( const err of yamlDoc . errors ) {
117+ parsedDiagnostics . push ( { message : err . message , range : rangeFromLinePos ( err . linePos ) } ) ;
118+ }
119+
120+ const importedDocs = new Map < string , DocumentLoc > ( ) ;
121+
122+ await visitAsync ( yamlDoc , {
123+ async Pair ( _ , pair ) {
124+ if ( ! isScalar ( pair . key ) || ! isScalar ( pair . value ) || pair . key . value !== "tuple_file" || ! pair . value . source ) {
125+ return ;
126+ }
127+
128+ const originalRange = pair . key . range ;
129+ try {
130+ const result = await getFileContents ( URI . parse ( textDocument . uri ) , pair . value . source ) ;
131+ if ( pair . value . source . match ( / .y a m l $ / ) ) {
132+ const file = parseDocument ( result . contents ) ;
133+
134+ const diagnosticFromInclusion : Diagnostic [ ] = [ ] ;
135+
136+ diagnosticFromInclusion . push (
137+ ...file . errors . map ( ( err ) => {
138+ return {
139+ source : "ParseError" ,
140+ message : "error with external file: " + err . message ,
141+ range : getRangeFromToken ( originalRange , textDocument ) ,
142+ } ;
143+ } ) ,
144+ ) ;
145+
146+ if ( diagnosticFromInclusion . length ) {
147+ parsedDiagnostics . push ( ...diagnosticFromInclusion ) ;
148+ return undefined ;
149+ }
150+
151+ if ( originalRange ) {
152+ importedDocs . set ( pair . value . source , { range : originalRange , doc : file } ) ;
153+ }
154+ return visit . SKIP ;
155+ }
156+ } catch ( err ) {
157+ parsedDiagnostics . push ( {
158+ range : getRangeFromToken ( originalRange , textDocument ) ,
159+ message : "error with external file: " + ( err as Error ) . message ,
160+ source : "ParseError" ,
161+ } ) ;
162+ }
163+ } ,
164+ } ) ;
165+
166+ // Override all tuples with new location
167+ for ( const p of importedDocs . entries ( ) ) {
168+ visit ( p [ 1 ] . doc . contents , {
169+ Scalar ( key , node ) {
170+ node . range = p [ 1 ] . range ;
171+ } ,
172+ } ) ;
173+ }
174+
175+ // Prepare final virtual doc
176+ visit ( yamlDoc , {
177+ Pair ( _ , pair ) {
178+ if ( ! isScalar ( pair . key ) || ! isScalar ( pair . value ) || pair . key . value !== "tuple_file" || ! pair . value . source ) {
179+ return ;
180+ }
181+
182+ const value = importedDocs . get ( pair . value . source ) ;
183+
184+ if ( value ) {
185+ // Import tuples, and point range at where file field used to exist
186+ const scalar = new Scalar ( "tuples" ) ;
187+ scalar . source = "tuples" ;
188+ scalar . range = value ?. range ;
189+
190+ return new Pair ( scalar , value ?. doc . contents ) ;
191+ }
192+ } ,
193+ } ) ;
194+ return { yamlDoc, lineCounter, parsedDiagnostics } ;
195+ }
196+
197+ async function validateYamlSyntaxAndModel ( textDocument : TextDocument ) : Promise < YamlStoreValidateResults > {
198+ const diagnostics : Diagnostic [ ] = [ ] ;
199+ const modelDiagnostics : Diagnostic [ ] = [ ] ;
200+
201+ const { yamlDoc, lineCounter, parsedDiagnostics } = await parseYamlStore ( textDocument ) ;
202+
203+ if ( parsedDiagnostics . length ) {
204+ return { diagnostics : parsedDiagnostics } ;
205+ }
206+
113207 const map = new YAMLSourceMap ( ) ;
114208 map . doMap ( yamlDoc . contents ) ;
115209
@@ -119,25 +213,6 @@ export function startServer(connection: _Connection) {
119213 return { diagnostics } ;
120214 }
121215
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-
141216 let model ,
142217 modelUri = undefined ;
143218
@@ -147,7 +222,7 @@ export function startServer(connection: _Connection) {
147222 diagnostics . push ( ...parseYamlModel ( yamlDoc , lineCounter ) ) ;
148223 diagnostics . push ( ...validateYamlStore ( yamlDoc . get ( "model" ) as string , yamlDoc , textDocument , map ) ) ;
149224 } else if ( yamlDoc . has ( "model_file" ) ) {
150- const position = getFieldPosition ( yamlDoc , lineCounter , "model_file" ) ;
225+ const position = getFieldPosition ( yamlDoc , lineCounter , "model_file" ) [ 0 ] ;
151226 const modelFile = yamlDoc . get ( "model_file" ) as string ;
152227
153228 try {
0 commit comments