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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "ng test",
"test:unit": "vitest run",
"test:schema": "tsx scripts/cross-env/verify_r_schema_ci.ts",
"test:syntax": "tsx scripts/validate-syntax.mjs",
"test:e2e:a11y": "playwright test tests_e2e/a11y.spec.ts",
"lint:code": "ng lint",
"lint:md": "node scripts/check-markdown-links.js && node scripts/check-orphaned-docs.js",
Expand Down
23 changes: 16 additions & 7 deletions scripts/validate-sas-syntax.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@

import { readdir, readFile } from 'fs/promises';
import { join, resolve } from 'path';
import { ASTValidator } from '../src/app/domain/schema-management/services/generation/ast-validator';
import { fileURLToPath } from 'url';
import astValPkg from '../src/app/domain/schema-management/services/generation/ast-validator';
const ASTValidator = astValPkg.ASTValidator || astValPkg;

// ---------------------------------------------------------------------------
// Configuration
Expand Down Expand Up @@ -234,7 +236,7 @@ function checkNullSafety(tokens) {
// Per-file validator
// ---------------------------------------------------------------------------

async function validateFile(filePath) {
export async function validateFile(filePath) {
const src = await readFile(filePath, 'utf-8');
const errors = [];

Expand Down Expand Up @@ -330,7 +332,12 @@ async function main() {
console.log('SAS Static Syntax Validator');
console.log(` Scanning: ${FIXTURE_ROOT}`);

const sasFiles = await collectSasFiles(FIXTURE_ROOT);
let sasFiles;
if (process.argv[2]) {
sasFiles = [resolve(process.argv[2])];
} else {
sasFiles = await collectSasFiles(FIXTURE_ROOT);
}

if (sasFiles.length === 0) {
console.error(
Expand Down Expand Up @@ -377,7 +384,9 @@ async function main() {
}
}

main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
}
23 changes: 16 additions & 7 deletions scripts/validate-stata-syntax.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

import { readdir, readFile } from 'fs/promises';
import { join, resolve } from 'path';
import { ASTValidator } from '../src/app/domain/schema-management/services/generation/ast-validator';
import { fileURLToPath } from 'url';
import astValPkg from '../src/app/domain/schema-management/services/generation/ast-validator';
const ASTValidator = astValPkg.ASTValidator || astValPkg;

const FIXTURE_ROOT = resolve(process.cwd(), 'artifacts', 'code-generation-fixtures');

Expand All @@ -21,7 +23,7 @@ const REQUIRED_HEADER_PATTERNS = [

const ISO_TIMESTAMP_RE = /Generated At:\s*(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/i;

async function validateFile(filePath) {
export async function validateFile(filePath) {
const src = await readFile(filePath, 'utf-8');
const errors = [];
const lines = src.split('\n');
Expand Down Expand Up @@ -85,7 +87,12 @@ async function main() {
console.log('Stata Static Syntax Validator');
console.log(` Scanning: ${FIXTURE_ROOT}`);

const stataFiles = await collectStataFiles(FIXTURE_ROOT);
let stataFiles;
if (process.argv[2]) {
stataFiles = [resolve(process.argv[2])];
} else {
stataFiles = await collectStataFiles(FIXTURE_ROOT);
}

if (stataFiles.length === 0) {
console.error(
Expand Down Expand Up @@ -128,7 +135,9 @@ async function main() {
}
}

main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
}
107 changes: 107 additions & 0 deletions scripts/validate-syntax.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env node
/**
* scripts/validate-syntax.mjs
*
* Unified static syntax validator for Equipose-generated SAS and Stata scripts.
* Runs both SAS and Stata syntax checks from a single command.
*/

import { readdir } from 'fs/promises';
import { join, resolve } from 'path';
import { validateFile as validateSasFile } from './validate-sas-syntax.mjs';
import { validateFile as validateStataFile } from './validate-stata-syntax.mjs';

const FIXTURE_ROOT = resolve(process.cwd(), 'artifacts', 'code-generation-fixtures');

async function collectFiles(dir, ext) {
const files = [];
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return files; // directory does not exist yet (e.g. first run)
}
for (const entry of entries) {
const full = join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...await collectFiles(full, ext));
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(ext.toLowerCase())) {
files.push(full);
}
}
return files;
}

async function main() {
console.log('=== Unified SAS & Stata Static Syntax Validator ===');
console.log(`Scanning: ${FIXTURE_ROOT}\n`);

const sasFiles = await collectFiles(FIXTURE_ROOT, '.sas');
const stataFiles = await collectFiles(FIXTURE_ROOT, '.do');

if (sasFiles.length === 0 && stataFiles.length === 0) {
console.error(
'ERROR: No .sas or .do files found under artifacts/code-generation-fixtures/.\n' +
'Please run the code generator or export step to generate fixtures first.'
);
process.exit(1);
}

console.log(`Found ${sasFiles.length} SAS (.sas) file(s)`);
console.log(`Found ${stataFiles.length} Stata (.do) file(s)\n`);

let totalErrors = 0;
const results = [];

// Validate SAS files
for (const file of sasFiles) {
const relPath = file.replace(process.cwd() + '/', '');
try {
const errors = await validateSasFile(file);
results.push({ type: 'SAS', file: relPath, errors });
totalErrors += errors.length;
} catch (err) {
results.push({ type: 'SAS', file: relPath, errors: [`Execution error during SAS validation: ${err.message}`] });
totalErrors += 1;
}
}

// Validate Stata files
for (const file of stataFiles) {
const relPath = file.replace(process.cwd() + '/', '');
try {
const errors = await validateStataFile(file);
results.push({ type: 'Stata', file: relPath, errors });
totalErrors += errors.length;
} catch (err) {
results.push({ type: 'Stata', file: relPath, errors: [`Execution error during Stata validation: ${err.message}`] });
totalErrors += 1;
}
}

// Report results
for (const { type, file, errors } of results) {
if (errors.length === 0) {
console.log(` ✓ [${type}] ${file}`);
} else {
console.log(` ✗ [${type}] ${file} — ${errors.length} error(s):`);
for (const err of errors) {
console.log(` - ${err}`);
}
}
}

console.log('');
if (totalErrors > 0) {
console.log(`UNIFIED_SYNTAX_CHECK: FAIL — ${totalErrors} error(s) found.`);
process.exit(1);
} else {
console.log('UNIFIED_SYNTAX_CHECK: PASS — All SAS and Stata scripts validated successfully!');
process.exit(0);
}
}

main().catch(err => {
console.error('Unexpected error:', err);
process.exit(1);
});
Loading
Loading