Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"scripts": {
"build:css": "npx windicss deliberation-empirica/client/src/baseStyles.css -o src/views/styles.css --config deliberation-empirica/client/windi.config.cjs && npx tailwindcss -i src/views/index.css -o src/views/playerStyles.css --config src/views/tailwind.config.js && npx tailwindcss -i src/views/globals.css -o src/views/layout.css --config src/views/tailwind.config.js",
"prepare-submodule": "git submodule update --init --recursive",
"copy-validators": "cp ./deliberation-empirica/server/src/preFlight/validateTreatmentFile.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validatePromptFile.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validateDlConfig.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validateBatchConfig.ts ./src/zod-validators",
"copy-validators": "cp ./deliberation-empirica/server/src/preFlight/validateTreatmentFile.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validatePromptFile.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validateDlConfig.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/validateBatchConfig.ts ./src/zod-validators && cp ./deliberation-empirica/server/src/preFlight/fillTemplates.ts ./src && sed -i 's|from \"./validateTreatmentFile\"|from \"./zod-validators/validateTreatmentFile\"|' ./src/fillTemplates.ts",
"prepare-web": "npm run prepare-submodule && npm run copy-validators && npm run build:css",
"compile-web": "npm run prepare-web && tsc -p tsconfig.json && npm run lint && node esbuild.js",
"package-web": "npm run prepare-web && npm run check-types && npm run lint && node esbuild.js --production",
Expand Down
1 change: 1 addition & 0 deletions src/fillTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,4 @@ function applyTruncation(s: string, maxLines: number): { text: string; truncated
slice.push("# … truncated …");
return { text: slice.join("\n"), truncated: true };
}
//s
64 changes: 64 additions & 0 deletions src/parsers/parseMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ export function parseMarkdown(document: vscode.TextDocument) {
case "multipleChoice": {
let { text, index } = getIndex(document, 3);
const lineNum = (document.positionAt(index).line) + 1;
if (!response || /^\s*$/.test(response)) {
const diagnosticRange = new vscode.Range(
document.positionAt(index),
document.positionAt(text.length - 1)
);
const issue = "Response should contain at least one choice for type multiple choice";
diagnostics.push(
new vscode.Diagnostic(
diagnosticRange,
issue,
vscode.DiagnosticSeverity.Warning
)
);
}
for (let i = lineNum; i < document.lineCount; i++) {
const str = document.lineAt(i).text;
// console.log(str);
Expand All @@ -181,6 +195,20 @@ export function parseMarkdown(document: vscode.TextDocument) {
case "openResponse": {
let { text, index } = getIndex(document, 3);
const lineNum = (document.positionAt(index).line) + 1;
if (!response || /^\s*$/.test(response)) {
const diagnosticRange = new vscode.Range(
document.positionAt(index),
document.positionAt(text.length - 1)
);
const issue = "Response should contain text for type open response";
diagnostics.push(
new vscode.Diagnostic(
diagnosticRange,
issue,
vscode.DiagnosticSeverity.Warning
)
);
}
for (let i = lineNum; i < document.lineCount; i++) {
const str = document.lineAt(i).text;
if (!(/^\s*$/.test(str)) && str.substring(0, 2) !== "> ") {
Expand All @@ -200,6 +228,42 @@ export function parseMarkdown(document: vscode.TextDocument) {
}
break;
}
case "listSorter": {
let { text, index } = getIndex(document, 3);
const lineNum = (document.positionAt(index).line) + 1;
if (!response || /^\s*$/.test(response)) {
const diagnosticRange = new vscode.Range(
document.positionAt(index),
document.positionAt(text.length - 1)
);
const issue = "Response should contain sortable choices for type list sorter";
diagnostics.push(
new vscode.Diagnostic(
diagnosticRange,
issue,
vscode.DiagnosticSeverity.Warning
)
);
}
for (let i = lineNum; i < document.lineCount; i++) {
const str = document.lineAt(i).text;
if (!(/^\s*$/.test(str)) && str.substring(0, 2) !== "> ") {
const diagnosticRange = new vscode.Range(
new vscode.Position(i, 0),
new vscode.Position(i, str.length)
);
const issue = `Response at line ${i + 1} should start with "> " (for list sorter)`;
diagnostics.push(
new vscode.Diagnostic(
diagnosticRange,
issue,
vscode.DiagnosticSeverity.Warning
)
);
}
}
break;
}
default: {
// console.log("Type: " + type);
break;
Expand Down
204 changes: 120 additions & 84 deletions src/parsers/parseYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
treatmentFileSchema,
TreatmentFileType,
} from "../zod-validators/validateTreatmentFile";
import { detectPromptMarkdown } from '../detectFile';
import { handleError, offsetToPosition, findPositionFromPath } from "../errorPosition";
import { parse } from 'path';
import { off } from 'process';
Expand Down Expand Up @@ -35,8 +36,8 @@
node: any,
templateMap: Map<string, { idx: number; content: any }>
): any {
if (!node || typeof node !== 'object') return node;

Check warning on line 39 in src/parsers/parseYaml.ts

View workflow job for this annotation

GitHub Actions / test

Expected { after 'if' condition
if (!('template' in node)) return node;

Check warning on line 40 in src/parsers/parseYaml.ts

View workflow job for this annotation

GitHub Actions / test

Expected { after 'if' condition

const tplName = (node as any).template;
if (!tplName || !templateMap.has(tplName)) return node;
Expand Down Expand Up @@ -668,6 +669,124 @@

runReferenceStageOrderChecks(document, parsedData, parsedData.toJS(), diagnostics);

async function validateReferencedPromptFiles(
document: vscode.TextDocument,
parsedDoc: YAML.Document.Parsed,
root: any,
diagnostics: vscode.Diagnostic[],
fileExistsInWorkspace: (relativePath: string) => Promise<{ uri: vscode.Uri; exists: boolean }>
) {
try {
// Collect all prompt file references from the YAML
const promptFileRefs: Array<{
path: (string | number)[];
file: string;
}> = [];

function walkYaml(node: any, path: (string | number)[] = []) {
if (!node) return;
if (Array.isArray(node)) {
node.forEach((item, idx) => walkYaml(item, [...path, idx]));
} else if (typeof node === 'object') {
// Check if this is a prompt element with a file reference
if (node.type === 'prompt' && typeof node.file === 'string') {
promptFileRefs.push({
path: [...path, 'file'],
file: node.file
});
}
Object.entries(node)?.forEach(([key, value]) => {
walkYaml(value, [...path, key]);
});
}
}

walkYaml(root);

// Validate each referenced prompt file
for (const ref of promptFileRefs) {
try {
const fileData = await fileExistsInWorkspace(ref.file);

if (!fileData.exists) {
// File doesn't exist - already handled by existing validation
continue;
}

// Open the document to get its URI
let promptDoc: vscode.TextDocument;
try {
promptDoc = await vscode.workspace.openTextDocument(fileData.uri);
} catch (err) {
console.error(`Failed to open prompt file: ${ref.file}`, err);
continue;
}

const isPromptMarkdown = detectPromptMarkdown(promptDoc);
const pos = findPositionFromPath(ref.path, parsedDoc, document);

if (!isPromptMarkdown) {
if (pos) {
diagnostics.push(new vscode.Diagnostic(
new vscode.Range(pos.start, pos.end ?? pos.start),
`File "${ref.file}" is not a valid prompt markdown file. Prompt markdown files must have YAML metadata with 'type' and 'name' fields between '---' separators.`,
vscode.DiagnosticSeverity.Warning
));
}
continue;
}

// Check if the prompt file has any diagnostics
const promptDiagnostics = vscode.languages.getDiagnostics(promptDoc.uri);

if (promptDiagnostics && promptDiagnostics.length > 0) {
// Find the position of the 'file:' line in the treatments YAML
const pos = findPositionFromPath(ref.path, parsedDoc, document);

if (pos) {
// Count errors vs warnings
const errorCount = promptDiagnostics.filter(
d => d.severity === vscode.DiagnosticSeverity.Error
).length;
const warningCount = promptDiagnostics.filter(
d => d.severity === vscode.DiagnosticSeverity.Warning
).length;

let message = `Referenced prompt file "${ref.file}" has validation issues: `;
const issues: string[] = [];
if (errorCount > 0) issues.push(`${errorCount} error(s)`);
if (warningCount > 0) issues.push(`${warningCount} warning(s)`);
message += issues.join(', ');

// Use Error severity if the prompt has errors, Warning otherwise
const severity = errorCount > 0
? vscode.DiagnosticSeverity.Error
: vscode.DiagnosticSeverity.Warning;

diagnostics.push(new vscode.Diagnostic(
new vscode.Range(pos.start, pos.end ?? pos.start),
message,
severity
));
}
}
} catch (err) {
console.error(`Error validating prompt file ${ref.file}:`, err);
}
}
} catch (err) {
console.error('Error in validateReferencedPromptFiles:', err);
}
}

await validateReferencedPromptFiles(
document,
parsedData,
parsedData.toJS(),
diagnostics,
fileExistsInWorkspace
);

const missingFiles = asyncValidateFilesToIssues(
parsedData.toJS() as TreatmentFileType
).then((issues: ZodIssue[]) => {
Expand Down Expand Up @@ -708,47 +827,13 @@
if (Array.isArray(node)) {
node.forEach((item, idx) => walkYaml(item, [...path, idx]));
} else if (typeof node === 'object') {
// Only track elements of type "survey" for now
// if (node.type === 'survey' && typeof node.name === 'string') {
// // Find the path to the 'name' property
// const namePath = [...path, 'name'];
// const range = findPositionFromPath(namePath, parsedData, document);
// // Default to line 1 if range is not found
// const line = range ? range.start.line + 1 : 1;
// if (!referenceTypeMap['survey']) {
// referenceTypeMap['survey'] = [];
// }
// referenceTypeMap['survey'].push({ name: node.name, line });
// }
// if (node.type === 'prompt' && typeof node.name === 'string') {
// // Find the path to the 'name' property
// const namePath = [...path, 'name'];
// const range = findPositionFromPath(namePath, parsedData, document);
// // Default to line 1 if range is not found
// const line = range ? range.start.line + 1 : 1;
// if (!referenceTypeMap['prompt']) {
// referenceTypeMap['prompt'] = [];
// }
// referenceTypeMap['prompt'].push({ name: node.name, line });
// }
// if (node.type === 'submitButton' && typeof node.name === 'string') {
// // Find the path to the 'name' property
// const namePath = [...path, 'name'];
// const range = findPositionFromPath(namePath, parsedData, document);
// // Default to line 1 if range is not found
// const line = range ? range.start.line + 1 : 1;
// if (!referenceTypeMap['submitButton']) {
// referenceTypeMap['submitButton'] = [];
// }
// referenceTypeMap['submitButton'].push({ name: node.name, line });
// }
// Track references for any type (future extensibility)
if (typeof node.reference === 'string' && node.reference.includes('.')) {
const refPath = [...path, 'reference'];
const range = findPositionFromPath(refPath, parsedData, document);
const line = range ? range.start.line + 1 : 1;
const [type] = node.reference.split('.', 1);
referenceChecks.push({ type, line, fullRef: node.reference });
referenceChecks.push({ type, line, fullRef: node.reference});
}
Object.entries(node)?.forEach(([key, value]) => {
walkYaml(value, [...path, key]);
Expand All @@ -761,22 +846,6 @@

// Now check references for each type
referenceChecks.forEach(({ type, line, fullRef }) => {
// if (type === 'survey') {
// // Only 'survey' type is currently supported
// const name = fullRef.split('.', 2)[1];
// if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
// diagnostics.push(
// new vscode.Diagnostic(
// new vscode.Range(
// new vscode.Position(line, 0),
// new vscode.Position(line, 100)
// ),
// `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
// vscode.DiagnosticSeverity.Warning
// )
// );
// }
// }
if (type === 'discussion') {
// Only 'discussion' type is currently supported
const name = fullRef.split('.', 2)[1];
Expand Down Expand Up @@ -812,39 +881,6 @@
);
}
}
// if (type === 'prompt') {
// // Only 'prompt' type is currently supported
// const name = fullRef.split('.', 2)[1];
// if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
// diagnostics.push(
// new vscode.Diagnostic(
// new vscode.Range(
// new vscode.Position(line, 0),
// new vscode.Position(line, 100)
// ),
// `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
// vscode.DiagnosticSeverity.Warning
// )
// );
// }
// }
// if (type === 'submitButton') {
// // Only 'submitButton' type is currently supported
// const name = fullRef.split('.', 2)[1];
// if (!(referenceTypeMap[type]?.some(entry => entry.name === name && entry.line < line))) {
// diagnostics.push(
// new vscode.Diagnostic(
// new vscode.Range(
// new vscode.Position(line, 0),
// new vscode
// .Position(line, 100)
// ),
// `Reference "${fullRef}" does not match any previously defined ${type} element name.`,
// vscode.DiagnosticSeverity.Warning
// )
// );
// }
// }
});


Expand Down
Loading
Loading