File size: 2,634 Bytes
0ade7b1
 
 
 
 
47b8a5f
0ade7b1
 
 
47b8a5f
 
0ade7b1
47b8a5f
 
0ade7b1
 
 
 
 
47b8a5f
 
 
0ade7b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47b8a5f
 
0ade7b1
 
 
 
 
 
 
47b8a5f
0ade7b1
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/env node
// Compiles every ```dssl fenced block in GUIDE.md (strict: 0 errors allowed)
// and reports README.md blocks (the README may carry teaching examples that
// intentionally error; those are tagged with <!-- dssl:has-errors -->).
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { parse } from '../public/dssl/parser.js';
import { resolve, DEFAULT_TOKENS } from '../public/dssl/resolver.js';

// paths are relative to the repo, so this works in any checkout
const repo = (f) => fileURLToPath(new URL('../' + f, import.meta.url));
const files = [
  { path: repo('GUIDE.md'), strict: true },
  { path: repo('README.md'), strict: false },
];

let pass = 0, fail = 0;
for (const { path, strict } of files) {
  let text;
  // a missing file used to be skipped quietly, which let the whole check
  // report "0 passed, 0 failed" and look green. A missing file is a failure.
  try { text = readFileSync(path, 'utf8'); } catch { console.error('FAIL cannot read ' + path); fail++; continue; }
  const blocks = [...text.matchAll(/```dssl\n([\s\S]*?)```/g)].map(m => m[1]);
  // whole-file workspace fold: seed with the engine's default tokens, collect
  // every token defined anywhere in the document, then compile each block
  // against the union. Guides are read non-linearly; examples may reference
  // tokens from any example on the page.
  let registry = { ...DEFAULT_TOKENS };
  for (const src of blocks) {
    const { ast, errors: pe } = parse(src, 'tokens-pass');
    const doc = resolve(ast, 'tokens-pass', registry);
    registry = { ...registry, ...doc.tokens };
  }
  blocks.forEach((src, i) => {
    const name = `${path.split('/').pop()} block ${i + 1}`;
    const { ast, errors: parseErrors } = parse(src, name);
    const doc = resolve(ast, name, registry);
    const errors = [...parseErrors, ...doc.errors];
    // raw { } needs real CSS, so a missing semicolon in a documented example is a docs bug
    if (strict) for (const w of doc.warnings || []) if (/needs a ";"/.test(w.message)) errors.push({ line: w.line, message: w.message });
    if (errors.length === 0) { pass++; return; }
    if (!strict && /dssl:has-errors/.test(src)) { pass++; return; }
    fail++;
    console.error(`FAIL ${name}:`);
    for (const e of errors.slice(0, 4)) console.error('   line ' + (e.line ?? '?') + ': ' + e.message);
  });
  console.log(`${path.split('/').pop()}: ${blocks.length} blocks checked`);
  if (blocks.length === 0) { console.error('FAIL no dssl blocks found in ' + path); fail++; }
}
console.log(`${pass} passed, ${fail} failed`);
process.exitCode = fail ? 1 : 0;