@syncerauk/test-rules 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/package.json +1 -1
- package/test-rules.mjs +23 -5
package/README.md
CHANGED
|
@@ -49,8 +49,10 @@ Both in the repo's `scripts/test-rules/`:
|
|
|
49
49
|
|
|
50
50
|
- `config.json`: what differs between repos. `fail` (false is report only), `tests` and
|
|
51
51
|
`ignore` (which files are test files), `fixtures` (folders a test may read), `wording` (the
|
|
52
|
-
one list of what a person reads), `reads` (the reads register: `"comment"
|
|
53
|
-
`{ "json": "<path>" }`
|
|
52
|
+
one list of what a person reads), `reads` (the reads register: `"comment"`, or
|
|
53
|
+
`{ "json": "<path>" }` for the map under `"tests"` in that file, `"key": "<name>"` for another
|
|
54
|
+
key, `"key": null` when the file itself is the map). The defaults are `CONFIG_DEFAULTS` in the
|
|
55
|
+
script.
|
|
54
56
|
- `listed.json`: the breaches the repo had on the day the check arrived. Written once by
|
|
55
57
|
`test-rules --init`.
|
|
56
58
|
|
|
@@ -63,6 +65,15 @@ Both in the repo's `scripts/test-rules/`:
|
|
|
63
65
|
|
|
64
66
|
A repo starts with `"fail": false`: breaches are printed and the exit is 0. One line turns it on.
|
|
65
67
|
|
|
68
|
+
## Versions
|
|
69
|
+
|
|
70
|
+
- **1.0.2** — A path that starts with a dot-folder (`.github/workflows/gate.yml`) is a repo
|
|
71
|
+
path; only `./` and `../` are relative to the test. A JSON reads register may be the whole
|
|
72
|
+
file (`"key": null`); a register with no map at its key cannot run (exit 3) instead of reading
|
|
73
|
+
as empty. Both make the check see reads it missed, so a repo's first run on 1.0.2 can show
|
|
74
|
+
breaches that are not on its list.
|
|
75
|
+
- **1.0.1** — The first release through the publish workflow. Same file as 1.0.0.
|
|
76
|
+
|
|
66
77
|
## Releases
|
|
67
78
|
|
|
68
79
|
The source is `scripts/test-rules.mjs` in Syncera-UK/syncera-orchestrator, where its tests run
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncerauk/test-rules",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "The static check of Syncera's test-writing rules (T1, T4, T6 and the line limit of R7). One file, no dependency. Published to npm by the orchestrator's publish-test-rules workflow; each repo pins an exact version.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
package/test-rules.mjs
CHANGED
|
@@ -69,7 +69,7 @@ export const CONFIG_DEFAULTS = Object.freeze({
|
|
|
69
69
|
ignore: [], // test files the rules do not judge (a real-time tier 3 suite)
|
|
70
70
|
fixtures: [], // folders a test may read: `a/fixtures/`
|
|
71
71
|
wording: [], // the one list of what a person reads (a built page, a legal text): files, globs, folders
|
|
72
|
-
reads: null, // the reads register: null, "comment" (`// reads:` lines) or { json: "<path>", key: "tests" }
|
|
72
|
+
reads: null, // the reads register: null, "comment" (`// reads:` lines) or { json: "<path>", key: "tests" } (key null: the file itself is the map)
|
|
73
73
|
source_ext: ['mjs', 'cjs', 'js', 'jsx', 'ts', 'tsx', 'mts', 'cts', 'sql', 'css', 'scss', 'html', 'sh', 'yml', 'yaml', 'md', 'mdx'],
|
|
74
74
|
max_lines: 1500,
|
|
75
75
|
task_names: ['(?:^|[^a-z0-9])w-?\\d+', '(?:^|[^a-z0-9])(?:fs|ops|trk|ci|app|leg|mig|bank|push|fun)-\\d+', '(?:^|[^a-z0-9])s\\d+(?:[^a-z0-9]|$)'],
|
|
@@ -204,7 +204,8 @@ const firstIndex = (text, re) => { const m = re.exec(text); return m ? m.index :
|
|
|
204
204
|
|
|
205
205
|
// ---------------------------------------------------------------------------------------------
|
|
206
206
|
// The reads register. Comment form: `// reads: a b` and `// runs: a b` on comment lines of their
|
|
207
|
-
// own (the orchestrator's declared reads). JSON form: { <key>: { "<test>": ["<pattern>", …] } }
|
|
207
|
+
// own (the orchestrator's declared reads). JSON form: { <key>: { "<test>": ["<pattern>", …] } },
|
|
208
|
+
// or the file itself is that map (`"key": null`): registerOf, below the config.
|
|
208
209
|
// ---------------------------------------------------------------------------------------------
|
|
209
210
|
const DECLARED = /^[ \t]*\/\/[ \t]*(reads|runs):[ \t]*(\S.*)$/gm;
|
|
210
211
|
export function declaredIn(text) {
|
|
@@ -299,8 +300,9 @@ function sourceTextLine({ path, text, code, bare, strings }, { config, register,
|
|
|
299
300
|
if (/[\s*?$]/.test(v) || !isSource(v)) continue;
|
|
300
301
|
const before = code.slice(code.lastIndexOf('\n', s.start) + 1, s.start - 1);
|
|
301
302
|
if (IMPORT_BEFORE.test(before) || !BUILT_HERE.test(before)) continue;
|
|
303
|
+
// `./x` and `../x` are relative to the test; `.github/x` is a repo path like any other.
|
|
302
304
|
const here = posix.normalize(posix.join(posix.dirname(path), v));
|
|
303
|
-
const target = v.startsWith('.') || !tracked.has(v) ? here : v;
|
|
305
|
+
const target = v.startsWith('./') || v.startsWith('../') || !tracked.has(v) ? here : v;
|
|
304
306
|
if (target === path || !tracked.has(target) || allowed(target)) continue;
|
|
305
307
|
if (said.some((p) => globRe(p).test(target))) continue;
|
|
306
308
|
return lineOf(code, s.start);
|
|
@@ -394,10 +396,26 @@ export function withDefaults(raw) {
|
|
|
394
396
|
if (!Array.isArray(config[k]) || config[k].some((x) => typeof x !== 'string')) throw fail(EXIT.FAILED, `${CONFIG_FILE}: ${k} must be a list of strings`);
|
|
395
397
|
}
|
|
396
398
|
const r = config.reads;
|
|
397
|
-
|
|
399
|
+
const json = r && typeof r === 'object' && !Array.isArray(r) && typeof r.json === 'string'
|
|
400
|
+
&& Object.keys(r).every((k) => k === 'json' || k === 'key') && (r.key === undefined || r.key === null || typeof r.key === 'string');
|
|
401
|
+
if (!(r === null || r === 'comment' || json)) throw fail(EXIT.FAILED, `${CONFIG_FILE}: reads must be null, "comment" or { "json": "<path>", "key": "<name>" | null }`);
|
|
398
402
|
return config;
|
|
399
403
|
}
|
|
400
404
|
|
|
405
|
+
/**
|
|
406
|
+
* The reads register inside a parsed JSON file: the map at `reads.key` ("tests" when no key is
|
|
407
|
+
* named), or the whole file when the key is null or empty. No map there cannot run (exit 3): an
|
|
408
|
+
* empty register would read as "no test reads anything".
|
|
409
|
+
*/
|
|
410
|
+
export function registerOf(file, reads) {
|
|
411
|
+
const key = reads.key === undefined ? 'tests' : reads.key;
|
|
412
|
+
const whole = key === null || key === '';
|
|
413
|
+
const map = whole ? file : file?.[key];
|
|
414
|
+
const isMap = map && typeof map === 'object' && !Array.isArray(map) && Object.values(map).every((v) => Array.isArray(v));
|
|
415
|
+
if (!isMap) throw fail(EXIT.FAILED, `${reads.json}: no ${whole ? 'top-level' : `"${key}"`} map of { "<test file>": ["<pattern>", …] }${whole ? '' : ' (name it with "key", or "key": null when the file itself is the map)'}`);
|
|
416
|
+
return map;
|
|
417
|
+
}
|
|
418
|
+
|
|
401
419
|
// ---------------------------------------------------------------------------------------------
|
|
402
420
|
// The command line: the only part that touches disk and git.
|
|
403
421
|
// ---------------------------------------------------------------------------------------------
|
|
@@ -446,7 +464,7 @@ export function main(argv = [], { log = console.log, error = console.error } = {
|
|
|
446
464
|
const tracked = new Set(present);
|
|
447
465
|
const testPaths = present.filter((p) => !p.split('/').includes('node_modules') && matches(config.tests, p) && !matches(config.ignore, p)).sort();
|
|
448
466
|
const files = testPaths.map((path) => ({ path, text: readFileSync(join(dir, path), 'utf8') }));
|
|
449
|
-
const register = config.reads && typeof config.reads === 'object' ? (readJson(dir, config.reads.json)
|
|
467
|
+
const register = config.reads && typeof config.reads === 'object' ? registerOf(readJson(dir, config.reads.json), config.reads) : null;
|
|
450
468
|
const all = breachesOf({ files, config, register, tracked });
|
|
451
469
|
|
|
452
470
|
if (opts.init) {
|