77 treatmentFileSchema ,
88 TreatmentFileType ,
99} from "../zod-validators/validateTreatmentFile" ;
10+ import { detectPromptMarkdown } from '../detectFile' ;
1011import { handleError , offsetToPosition , findPositionFromPath } from "../errorPosition" ;
1112import { parse } from 'path' ;
1213import { off } from 'process' ;
@@ -668,6 +669,124 @@ export async function parseYaml(document: vscode.TextDocument) {
668669
669670 runReferenceStageOrderChecks ( document , parsedData , parsedData . toJS ( ) , diagnostics ) ;
670671
672+ async function validateReferencedPromptFiles (
673+ document : vscode . TextDocument ,
674+ parsedDoc : YAML . Document . Parsed ,
675+ root : any ,
676+ diagnostics : vscode . Diagnostic [ ] ,
677+ fileExistsInWorkspace : ( relativePath : string ) => Promise < { uri : vscode . Uri ; exists : boolean } >
678+ ) {
679+ try {
680+ // Collect all prompt file references from the YAML
681+ const promptFileRefs : Array < {
682+ path : ( string | number ) [ ] ;
683+ file : string ;
684+ } > = [ ] ;
685+
686+ function walkYaml ( node : any , path : ( string | number ) [ ] = [ ] ) {
687+ if ( ! node ) return ;
688+ if ( Array . isArray ( node ) ) {
689+ node . forEach ( ( item , idx ) => walkYaml ( item , [ ...path , idx ] ) ) ;
690+ } else if ( typeof node === 'object' ) {
691+ // Check if this is a prompt element with a file reference
692+ if ( node . type === 'prompt' && typeof node . file === 'string' ) {
693+ promptFileRefs . push ( {
694+ path : [ ...path , 'file' ] ,
695+ file : node . file
696+ } ) ;
697+ }
698+ Object . entries ( node ) ?. forEach ( ( [ key , value ] ) => {
699+ walkYaml ( value , [ ...path , key ] ) ;
700+ } ) ;
701+ }
702+ }
703+
704+ walkYaml ( root ) ;
705+
706+ // Validate each referenced prompt file
707+ for ( const ref of promptFileRefs ) {
708+ try {
709+ const fileData = await fileExistsInWorkspace ( ref . file ) ;
710+
711+ if ( ! fileData . exists ) {
712+ // File doesn't exist - already handled by existing validation
713+ continue ;
714+ }
715+
716+ // Open the document to get its URI
717+ let promptDoc : vscode . TextDocument ;
718+ try {
719+ promptDoc = await vscode . workspace . openTextDocument ( fileData . uri ) ;
720+ } catch ( err ) {
721+ console . error ( `Failed to open prompt file: ${ ref . file } ` , err ) ;
722+ continue ;
723+ }
724+
725+ const isPromptMarkdown = detectPromptMarkdown ( promptDoc ) ;
726+ const pos = findPositionFromPath ( ref . path , parsedDoc , document ) ;
727+
728+ if ( ! isPromptMarkdown ) {
729+ if ( pos ) {
730+ diagnostics . push ( new vscode . Diagnostic (
731+ new vscode . Range ( pos . start , pos . end ?? pos . start ) ,
732+ `File "${ ref . file } " is not a valid prompt markdown file. Prompt markdown files must have YAML metadata with 'type' and 'name' fields between '---' separators.` ,
733+ vscode . DiagnosticSeverity . Warning
734+ ) ) ;
735+ }
736+ continue ;
737+ }
738+
739+ // Check if the prompt file has any diagnostics
740+ const promptDiagnostics = vscode . languages . getDiagnostics ( promptDoc . uri ) ;
741+
742+ if ( promptDiagnostics && promptDiagnostics . length > 0 ) {
743+ // Find the position of the 'file:' line in the treatments YAML
744+ const pos = findPositionFromPath ( ref . path , parsedDoc , document ) ;
745+
746+ if ( pos ) {
747+ // Count errors vs warnings
748+ const errorCount = promptDiagnostics . filter (
749+ d => d . severity === vscode . DiagnosticSeverity . Error
750+ ) . length ;
751+ const warningCount = promptDiagnostics . filter (
752+ d => d . severity === vscode . DiagnosticSeverity . Warning
753+ ) . length ;
754+
755+ let message = `Referenced prompt file "${ ref . file } " has validation issues: ` ;
756+ const issues : string [ ] = [ ] ;
757+ if ( errorCount > 0 ) issues . push ( `${ errorCount } error(s)` ) ;
758+ if ( warningCount > 0 ) issues . push ( `${ warningCount } warning(s)` ) ;
759+ message += issues . join ( ', ' ) ;
760+
761+ // Use Error severity if the prompt has errors, Warning otherwise
762+ const severity = errorCount > 0
763+ ? vscode . DiagnosticSeverity . Error
764+ : vscode . DiagnosticSeverity . Warning ;
765+
766+ diagnostics . push ( new vscode . Diagnostic (
767+ new vscode . Range ( pos . start , pos . end ?? pos . start ) ,
768+ message ,
769+ severity
770+ ) ) ;
771+ }
772+ }
773+ } catch ( err ) {
774+ console . error ( `Error validating prompt file ${ ref . file } :` , err ) ;
775+ }
776+ }
777+ } catch ( err ) {
778+ console . error ( 'Error in validateReferencedPromptFiles:' , err ) ;
779+ }
780+ }
781+
782+ await validateReferencedPromptFiles (
783+ document ,
784+ parsedData ,
785+ parsedData . toJS ( ) ,
786+ diagnostics ,
787+ fileExistsInWorkspace
788+ ) ;
789+
671790 const missingFiles = asyncValidateFilesToIssues (
672791 parsedData . toJS ( ) as TreatmentFileType
673792 ) . then ( ( issues : ZodIssue [ ] ) => {
@@ -708,47 +827,13 @@ export async function parseYaml(document: vscode.TextDocument) {
708827 if ( Array . isArray ( node ) ) {
709828 node . forEach ( ( item , idx ) => walkYaml ( item , [ ...path , idx ] ) ) ;
710829 } else if ( typeof node === 'object' ) {
711- // Only track elements of type "survey" for now
712- // if (node.type === 'survey' && typeof node.name === 'string') {
713- // // Find the path to the 'name' property
714- // const namePath = [...path, 'name'];
715- // const range = findPositionFromPath(namePath, parsedData, document);
716- // // Default to line 1 if range is not found
717- // const line = range ? range.start.line + 1 : 1;
718- // if (!referenceTypeMap['survey']) {
719- // referenceTypeMap['survey'] = [];
720- // }
721- // referenceTypeMap['survey'].push({ name: node.name, line });
722- // }
723- // if (node.type === 'prompt' && typeof node.name === 'string') {
724- // // Find the path to the 'name' property
725- // const namePath = [...path, 'name'];
726- // const range = findPositionFromPath(namePath, parsedData, document);
727- // // Default to line 1 if range is not found
728- // const line = range ? range.start.line + 1 : 1;
729- // if (!referenceTypeMap['prompt']) {
730- // referenceTypeMap['prompt'] = [];
731- // }
732- // referenceTypeMap['prompt'].push({ name: node.name, line });
733- // }
734- // if (node.type === 'submitButton' && typeof node.name === 'string') {
735- // // Find the path to the 'name' property
736- // const namePath = [...path, 'name'];
737- // const range = findPositionFromPath(namePath, parsedData, document);
738- // // Default to line 1 if range is not found
739- // const line = range ? range.start.line + 1 : 1;
740- // if (!referenceTypeMap['submitButton']) {
741- // referenceTypeMap['submitButton'] = [];
742- // }
743- // referenceTypeMap['submitButton'].push({ name: node.name, line });
744- // }
745830 // Track references for any type (future extensibility)
746831 if ( typeof node . reference === 'string' && node . reference . includes ( '.' ) ) {
747832 const refPath = [ ...path , 'reference' ] ;
748833 const range = findPositionFromPath ( refPath , parsedData , document ) ;
749834 const line = range ? range . start . line + 1 : 1 ;
750835 const [ type ] = node . reference . split ( '.' , 1 ) ;
751- referenceChecks . push ( { type, line, fullRef : node . reference } ) ;
836+ referenceChecks . push ( { type, line, fullRef : node . reference } ) ;
752837 }
753838 Object . entries ( node ) ?. forEach ( ( [ key , value ] ) => {
754839 walkYaml ( value , [ ...path , key ] ) ;
@@ -761,22 +846,6 @@ export async function parseYaml(document: vscode.TextDocument) {
761846
762847 // Now check references for each type
763848 referenceChecks . forEach ( ( { type, line, fullRef } ) => {
764- // if (type === 'survey') {
765- // // Only 'survey' type is currently supported
766- // const name = fullRef.split('.', 2)[1];
767- // if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
768- // diagnostics.push(
769- // new vscode.Diagnostic(
770- // new vscode.Range(
771- // new vscode.Position(line, 0),
772- // new vscode.Position(line, 100)
773- // ),
774- // `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
775- // vscode.DiagnosticSeverity.Warning
776- // )
777- // );
778- // }
779- // }
780849 if ( type === 'discussion' ) {
781850 // Only 'discussion' type is currently supported
782851 const name = fullRef . split ( '.' , 2 ) [ 1 ] ;
@@ -812,39 +881,6 @@ export async function parseYaml(document: vscode.TextDocument) {
812881 ) ;
813882 }
814883 }
815- // if (type === 'prompt') {
816- // // Only 'prompt' type is currently supported
817- // const name = fullRef.split('.', 2)[1];
818- // if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
819- // diagnostics.push(
820- // new vscode.Diagnostic(
821- // new vscode.Range(
822- // new vscode.Position(line, 0),
823- // new vscode.Position(line, 100)
824- // ),
825- // `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
826- // vscode.DiagnosticSeverity.Warning
827- // )
828- // );
829- // }
830- // }
831- // if (type === 'submitButton') {
832- // // Only 'submitButton' type is currently supported
833- // const name = fullRef.split('.', 2)[1];
834- // if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
835- // diagnostics.push(
836- // new vscode.Diagnostic(
837- // new vscode.Range(
838- // new vscode.Position(line, 0),
839- // new vscode
840- // .Position(line, 100)
841- // ),
842- // `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
843- // vscode.DiagnosticSeverity.Warning
844- // )
845- // );
846- // }
847- // }
848884 } ) ;
849885
850886
0 commit comments