@descent-vtt/spec-guard 0.1.0

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/dist/glob.js ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Dependency-free glob matching and directory walking.
3
+ *
4
+ * Windows shells do not expand globs, so `spec-guard "docs/**\/*.md"` must
5
+ * behave identically on every platform. spec-guard therefore always expands
6
+ * patterns itself instead of trusting the shell.
7
+ */
8
+ import { promises as fs } from 'node:fs';
9
+ import path from 'node:path';
10
+ /** Directories never worth searching. Mirrors ripgrep's practical defaults. */
11
+ export const DEFAULT_IGNORED_DIRECTORIES = new Set([
12
+ '.git',
13
+ '.hg',
14
+ '.svn',
15
+ '.cache',
16
+ '.next',
17
+ '.nuxt',
18
+ '.svelte-kit',
19
+ '.turbo',
20
+ '.venv',
21
+ '__pycache__',
22
+ 'bower_components',
23
+ 'build',
24
+ 'coverage',
25
+ 'dist',
26
+ 'node_modules',
27
+ 'out',
28
+ 'venv',
29
+ ]);
30
+ const MAGIC_RE = /[*?[\]{}]/;
31
+ /** True when the pattern contains glob metacharacters. */
32
+ export function isGlob(pattern) {
33
+ return MAGIC_RE.test(pattern);
34
+ }
35
+ /** Normalises Windows separators so every internal path uses `/`. */
36
+ export function toPosix(value) {
37
+ return value.replace(/\\/g, '/');
38
+ }
39
+ const REGEXP_SPECIALS = new Set(['.', '+', '^', '$', '(', ')', '|', '\\']);
40
+ /**
41
+ * Converts a glob to an anchored RegExp.
42
+ * Supports `*`, `**`, `?`, `[...]` and `{a,b}` - the subset every developer
43
+ * already knows from .gitignore and ripgrep.
44
+ */
45
+ export function globToRegExp(pattern, options = {}) {
46
+ let source = '';
47
+ let index = 0;
48
+ const input = toPosix(pattern);
49
+ while (index < input.length) {
50
+ const char = input[index];
51
+ if (char === '*') {
52
+ if (input[index + 1] === '*') {
53
+ index += 2;
54
+ if (input[index] === '/') {
55
+ index += 1;
56
+ source += '(?:[^/]*\\/)*';
57
+ }
58
+ else {
59
+ source += '.*';
60
+ }
61
+ continue;
62
+ }
63
+ source += '[^/]*';
64
+ index += 1;
65
+ continue;
66
+ }
67
+ if (char === '?') {
68
+ source += '[^/]';
69
+ index += 1;
70
+ continue;
71
+ }
72
+ if (char === '[') {
73
+ const close = input.indexOf(']', index + 1);
74
+ if (close === -1) {
75
+ source += '\\[';
76
+ index += 1;
77
+ continue;
78
+ }
79
+ let body = input.slice(index + 1, close);
80
+ if (body.startsWith('!'))
81
+ body = `^${body.slice(1)}`;
82
+ source += `[${body}]`;
83
+ index = close + 1;
84
+ continue;
85
+ }
86
+ if (char === '{') {
87
+ const close = input.indexOf('}', index + 1);
88
+ if (close === -1) {
89
+ source += '\\{';
90
+ index += 1;
91
+ continue;
92
+ }
93
+ const alternatives = input
94
+ .slice(index + 1, close)
95
+ .split(',')
96
+ .map((alternative) => globToRegExp(alternative, options).source.slice(1, -1));
97
+ source += `(?:${alternatives.join('|')})`;
98
+ index = close + 1;
99
+ continue;
100
+ }
101
+ source += REGEXP_SPECIALS.has(char) ? `\\${char}` : char;
102
+ index += 1;
103
+ }
104
+ return new RegExp(`^${source}$`, options.ignoreCase ? 'i' : '');
105
+ }
106
+ /**
107
+ * Builds a predicate over root-relative POSIX paths.
108
+ *
109
+ * Following ripgrep's `-g` semantics, a pattern without a `/` is matched
110
+ * against the file's basename (`*.ts` matches `src/deep/a.ts`), while a pattern
111
+ * containing `/` is matched against the whole relative path.
112
+ */
113
+ export function createGlobMatcher(patterns) {
114
+ if (patterns.length === 0)
115
+ return () => true;
116
+ const matchers = patterns.map((pattern) => {
117
+ let normalized = toPosix(pattern).replace(/^\.\//, '');
118
+ if (normalized.endsWith('/'))
119
+ normalized += '**';
120
+ const basenameOnly = !normalized.includes('/');
121
+ return { regexp: globToRegExp(normalized), basenameOnly };
122
+ });
123
+ return (relativePath) => matchers.some(({ regexp, basenameOnly }) => regexp.test(basenameOnly ? path.posix.basename(relativePath) : relativePath));
124
+ }
125
+ /**
126
+ * Depth-first directory walk yielding regular files.
127
+ * Emits nothing when `root` is missing or is not a directory.
128
+ */
129
+ export async function* walkFiles(root, options = {}) {
130
+ const ignored = options.ignoredDirectories ?? DEFAULT_IGNORED_DIRECTORIES;
131
+ const includeHidden = options.includeHidden ?? false;
132
+ const followSymlinks = options.followSymlinks ?? false;
133
+ const seen = new Set();
134
+ async function* visit(directory, prefix) {
135
+ let entries;
136
+ try {
137
+ entries = await fs.readdir(directory, { withFileTypes: true });
138
+ }
139
+ catch {
140
+ return;
141
+ }
142
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
143
+ for (const entry of entries) {
144
+ const name = entry.name;
145
+ if (!includeHidden && name.startsWith('.'))
146
+ continue;
147
+ const absolutePath = path.join(directory, name);
148
+ const relativePath = prefix ? `${prefix}/${name}` : name;
149
+ let isDirectory = entry.isDirectory();
150
+ let isFile = entry.isFile();
151
+ if (entry.isSymbolicLink()) {
152
+ if (!followSymlinks)
153
+ continue;
154
+ const stats = await fs.stat(absolutePath).catch(() => null);
155
+ if (!stats)
156
+ continue;
157
+ isDirectory = stats.isDirectory();
158
+ isFile = stats.isFile();
159
+ }
160
+ if (isDirectory) {
161
+ if (ignored.has(name))
162
+ continue;
163
+ // Each physical directory is visited at most once. That stops symlink
164
+ // cycles, and - more importantly for a search tool - stops a linked
165
+ // tree from counting the same match twice.
166
+ const real = followSymlinks ? await fs.realpath(absolutePath).catch(() => absolutePath) : absolutePath;
167
+ if (seen.has(real))
168
+ continue;
169
+ seen.add(real);
170
+ yield* visit(absolutePath, relativePath);
171
+ continue;
172
+ }
173
+ if (!isFile)
174
+ continue;
175
+ const stats = await fs.stat(absolutePath).catch(() => null);
176
+ if (!stats)
177
+ continue;
178
+ yield { absolutePath, relativePath, size: stats.size };
179
+ }
180
+ }
181
+ const rootStats = await fs.stat(root).catch(() => null);
182
+ if (!rootStats?.isDirectory())
183
+ return;
184
+ yield* visit(root, '');
185
+ }
186
+ /** Longest leading directory of a glob that contains no metacharacters. */
187
+ export function globBase(pattern) {
188
+ const segments = toPosix(pattern).split('/');
189
+ const base = [];
190
+ let index = 0;
191
+ for (; index < segments.length; index++) {
192
+ const segment = segments[index];
193
+ if (isGlob(segment) || index === segments.length - 1)
194
+ break;
195
+ base.push(segment);
196
+ }
197
+ return { base: base.join('/'), rest: segments.slice(index).join('/') };
198
+ }
199
+ /**
200
+ * Expands CLI spec patterns into a sorted, de-duplicated list of absolute file
201
+ * paths. Plain paths are taken literally; a directory expands to the Markdown
202
+ * files it contains.
203
+ */
204
+ export async function expandSpecPatterns(patterns, root, defaultExtensions = ['.md', '.markdown', '.mdx']) {
205
+ const found = new Set();
206
+ for (const rawPattern of patterns) {
207
+ const pattern = toPosix(rawPattern);
208
+ if (!isGlob(pattern)) {
209
+ const absolute = path.resolve(root, pattern);
210
+ const stats = await fs.stat(absolute).catch(() => null);
211
+ if (stats?.isFile()) {
212
+ found.add(absolute);
213
+ }
214
+ else if (stats?.isDirectory()) {
215
+ for await (const file of walkFiles(absolute)) {
216
+ if (defaultExtensions.some((extension) => file.relativePath.toLowerCase().endsWith(extension))) {
217
+ found.add(file.absolutePath);
218
+ }
219
+ }
220
+ }
221
+ continue;
222
+ }
223
+ const isAbsolutePattern = path.isAbsolute(pattern);
224
+ const { base } = globBase(pattern);
225
+ const walkRoot = isAbsolutePattern ? base || path.parse(pattern).root : path.resolve(root, base);
226
+ const matcher = createGlobMatcher([pattern]);
227
+ const includeHidden = pattern.split('/').some((segment) => segment.startsWith('.'));
228
+ for await (const file of walkFiles(walkRoot, { includeHidden })) {
229
+ const candidate = isAbsolutePattern
230
+ ? toPosix(file.absolutePath)
231
+ : toPosix(path.relative(root, file.absolutePath));
232
+ if (matcher(candidate))
233
+ found.add(file.absolutePath);
234
+ }
235
+ }
236
+ return [...found].sort();
237
+ }
238
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Programmatic API.
3
+ *
4
+ * The CLI is a thin shell over `runSpecGuard`; anything the CLI can do, a build
5
+ * script or a custom reporter can do too.
6
+ *
7
+ * ```ts
8
+ * import { runSpecGuard, formatReport } from '@descent-vtt/spec-guard';
9
+ *
10
+ * const report = await runSpecGuard({ patterns: ['docs/**\/*.md'], root: process.cwd() });
11
+ * if (!report.ok) console.error(formatReport(report, { color: true, verbose: false }));
12
+ * ```
13
+ */
14
+ export { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';
15
+ export type { CliIO, CliOptions } from './cli.js';
16
+ export { buildJsRegExp, buildRipgrepArgs, createCachedEngine, escapeRegExp, findRipgrep, javascriptEngine, resetRipgrepProbe, resolveEngine, scanContent, MAX_FILE_SIZE, } from './engine.js';
17
+ export type { Engine, EnginePreference, SearchRequest } from './engine.js';
18
+ export { createGlobMatcher, expandSpecPatterns, globBase, globToRegExp, isGlob, toPosix, walkFiles, DEFAULT_IGNORED_DIRECTORIES, } from './glob.js';
19
+ export type { WalkedFile, WalkOptions } from './glob.js';
20
+ export { maskCode, parseAttributes, parseDirectives } from './parser.js';
21
+ export type { ParseContext } from './parser.js';
22
+ export { executeAssertion, resolveDirective, runSpecGuard, DEFAULT_CONCURRENCY, DEFAULT_MAX_SNIPPETS, } from './runner.js';
23
+ export type { ResolveContext, RunOptions, RunResult } from './runner.js';
24
+ export { createPainter, formatJson, formatReport, shouldUseAscii, shouldUseColor } from './reporter.js';
25
+ export type { ReporterOptions } from './reporter.js';
26
+ export type { Assertion, AssertionResult, Bounds, Directive, DirectiveError, DirectiveKind, EngineName, MatchLocation, ParseResult, RunReport, RunSummary, SearchOptions, SearchResult, SourceLocation, } from './types.js';
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC/F,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAElD,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,aAAa,GACd,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,OAAO,EACP,SAAS,EACT,2BAA2B,GAC5B,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACxG,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,YAAY,EACV,SAAS,EACT,eAAe,EACf,MAAM,EACN,SAAS,EACT,cAAc,EACd,aAAa,EACb,UAAU,EACV,aAAa,EACb,WAAW,EACX,SAAS,EACT,UAAU,EACV,aAAa,EACb,YAAY,EACZ,cAAc,GACf,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Programmatic API.
3
+ *
4
+ * The CLI is a thin shell over `runSpecGuard`; anything the CLI can do, a build
5
+ * script or a custom reporter can do too.
6
+ *
7
+ * ```ts
8
+ * import { runSpecGuard, formatReport } from '@descent-vtt/spec-guard';
9
+ *
10
+ * const report = await runSpecGuard({ patterns: ['docs/**\/*.md'], root: process.cwd() });
11
+ * if (!report.ok) console.error(formatReport(report, { color: true, verbose: false }));
12
+ * ```
13
+ */
14
+ export { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';
15
+ export { buildJsRegExp, buildRipgrepArgs, createCachedEngine, escapeRegExp, findRipgrep, javascriptEngine, resetRipgrepProbe, resolveEngine, scanContent, MAX_FILE_SIZE, } from './engine.js';
16
+ export { createGlobMatcher, expandSpecPatterns, globBase, globToRegExp, isGlob, toPosix, walkFiles, DEFAULT_IGNORED_DIRECTORIES, } from './glob.js';
17
+ export { maskCode, parseAttributes, parseDirectives } from './parser.js';
18
+ export { executeAssertion, resolveDirective, runSpecGuard, DEFAULT_CONCURRENCY, DEFAULT_MAX_SNIPPETS, } from './runner.js';
19
+ export { createPainter, formatJson, formatReport, shouldUseAscii, shouldUseColor } from './reporter.js';
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAG/F,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,aAAa,GACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,OAAO,EACP,SAAS,EACT,2BAA2B,GAC5B,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGzE,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Markdown directive parser.
3
+ *
4
+ * spec-guard directives are plain HTML comments, which means they are invisible
5
+ * in every Markdown renderer on earth and require no front-matter, no custom
6
+ * fences and no preprocessor:
7
+ *
8
+ * <!-- @assert-absence target="src/" symbol="LegacyPaymentGateway" -->
9
+ *
10
+ * Two properties matter more than raw speed here:
11
+ * 1. Directives inside fenced code blocks or inline code spans are IGNORED, so
12
+ * a README can document the syntax without executing it.
13
+ * 2. Anything that looks like a directive but is malformed is reported as an
14
+ * error rather than silently skipped - a typo must never turn into a
15
+ * silently-passing invariant.
16
+ */
17
+ import type { ParseResult } from './types.js';
18
+ export interface ParseContext {
19
+ /** Absolute path of the spec file. */
20
+ file: string;
21
+ /** Display path, relative to root, forward slashes. */
22
+ relativeFile: string;
23
+ }
24
+ /**
25
+ * Blanks out fenced code blocks and inline code spans, preserving every byte
26
+ * offset and newline so that reported line/column numbers stay exact.
27
+ */
28
+ export declare function maskCode(source: string): string;
29
+ /** Parses `name="value"` pairs; bare `name` is shorthand for `name="true"`. */
30
+ export declare function parseAttributes(input: string): Record<string, string>;
31
+ /** Extracts every spec-guard directive from a Markdown source string. */
32
+ export declare function parseDirectives(source: string, context: ParseContext): ParseResult;
33
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAA4C,WAAW,EAAkB,MAAM,YAAY,CAAC;AA0BxG,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAwD/C;AA0BD,+EAA+E;AAC/E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUrE;AAED,yEAAyE;AACzE,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,WAAW,CAkDlF"}
package/dist/parser.js ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Markdown directive parser.
3
+ *
4
+ * spec-guard directives are plain HTML comments, which means they are invisible
5
+ * in every Markdown renderer on earth and require no front-matter, no custom
6
+ * fences and no preprocessor:
7
+ *
8
+ * <!-- @assert-absence target="src/" symbol="LegacyPaymentGateway" -->
9
+ *
10
+ * Two properties matter more than raw speed here:
11
+ * 1. Directives inside fenced code blocks or inline code spans are IGNORED, so
12
+ * a README can document the syntax without executing it.
13
+ * 2. Anything that looks like a directive but is malformed is reported as an
14
+ * error rather than silently skipped - a typo must never turn into a
15
+ * silently-passing invariant.
16
+ */
17
+ const KINDS = new Set(['assert-absence', 'assert-count', 'assert-present']);
18
+ /** Attributes each directive kind accepts. */
19
+ const ALLOWED_ATTRIBUTES = {
20
+ 'assert-absence': new Set(['target', 'symbol', 'expected', 'max', 'glob', 'regex', 'word', 'ignore-case', 'reason']),
21
+ 'assert-count': new Set([
22
+ 'target',
23
+ 'symbol',
24
+ 'expected',
25
+ 'min',
26
+ 'max',
27
+ 'glob',
28
+ 'regex',
29
+ 'word',
30
+ 'ignore-case',
31
+ 'reason',
32
+ ]),
33
+ 'assert-present': new Set(['file', 'reason']),
34
+ };
35
+ const DIRECTIVE_RE = /<!--\s*@([a-zA-Z][\w-]*)([\s\S]*?)-->/g;
36
+ const ATTRIBUTE_RE = /([a-zA-Z][\w-]*)(?:\s*=\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^\s"'=<>`]+)))?/g;
37
+ /**
38
+ * Blanks out fenced code blocks and inline code spans, preserving every byte
39
+ * offset and newline so that reported line/column numbers stay exact.
40
+ */
41
+ export function maskCode(source) {
42
+ // split('') keeps UTF-16 index parity with the original string, which
43
+ // [...source] would break on astral characters (and break line numbers).
44
+ const chars = source.split('');
45
+ const blank = (start, end) => {
46
+ for (let i = start; i < end && i < chars.length; i++) {
47
+ if (chars[i] !== '\n')
48
+ chars[i] = ' ';
49
+ }
50
+ };
51
+ // Fenced code blocks: ``` or ~~~ (3+ markers), optionally indented up to 3 spaces.
52
+ const fenceRe = /^[ \t]{0,3}(`{3,}|~{3,})[^\n]*$/gm;
53
+ let match;
54
+ const fences = [];
55
+ while ((match = fenceRe.exec(source)) !== null) {
56
+ fences.push({ index: match.index, end: match.index + match[0].length, marker: match[1] });
57
+ }
58
+ const consumed = [];
59
+ for (let i = 0; i < fences.length; i++) {
60
+ const open = fences[i];
61
+ if (consumed.some(([s, e]) => open.index >= s && open.index < e))
62
+ continue;
63
+ const char = open.marker[0];
64
+ let closeEnd = source.length;
65
+ for (let j = i + 1; j < fences.length; j++) {
66
+ const candidate = fences[j];
67
+ if (candidate.marker[0] === char && candidate.marker.length >= open.marker.length) {
68
+ closeEnd = candidate.end;
69
+ break;
70
+ }
71
+ }
72
+ consumed.push([open.index, closeEnd]);
73
+ blank(open.index, closeEnd);
74
+ }
75
+ // Inline code spans, using CommonMark's rule: a run of N backticks is closed
76
+ // by the next run of EXACTLY N backticks. Runs of a different length are
77
+ // skipped rather than treated as a closer - otherwise a stray ``` inside a
78
+ // sentence shifts every later pairing by one and un-masks real prose.
79
+ const masked = chars.join('');
80
+ const runs = [];
81
+ const runRe = /`+/g;
82
+ let run;
83
+ while ((run = runRe.exec(masked)) !== null) {
84
+ runs.push({ index: run.index, length: run[0].length });
85
+ }
86
+ for (let index = 0; index < runs.length; index++) {
87
+ const open = runs[index];
88
+ const closeIndex = runs.findIndex((candidate, position) => position > index && candidate.length === open.length);
89
+ if (closeIndex === -1)
90
+ continue;
91
+ const close = runs[closeIndex];
92
+ blank(open.index, close.index + close.length);
93
+ index = closeIndex;
94
+ }
95
+ return chars.join('');
96
+ }
97
+ /** Pre-computed line starts, for O(log n) offset -> line/column resolution. */
98
+ function lineStarts(source) {
99
+ const starts = [0];
100
+ for (let i = 0; i < source.length; i++) {
101
+ if (source[i] === '\n')
102
+ starts.push(i + 1);
103
+ }
104
+ return starts;
105
+ }
106
+ function locate(starts, index) {
107
+ let low = 0;
108
+ let high = starts.length - 1;
109
+ while (low < high) {
110
+ const mid = (low + high + 1) >> 1;
111
+ if (starts[mid] <= index)
112
+ low = mid;
113
+ else
114
+ high = mid - 1;
115
+ }
116
+ return { line: low + 1, column: index - starts[low] + 1 };
117
+ }
118
+ function unescape(value) {
119
+ return value.replace(/\\(.)/g, '$1');
120
+ }
121
+ /** Parses `name="value"` pairs; bare `name` is shorthand for `name="true"`. */
122
+ export function parseAttributes(input) {
123
+ const attributes = {};
124
+ ATTRIBUTE_RE.lastIndex = 0;
125
+ let match;
126
+ while ((match = ATTRIBUTE_RE.exec(input)) !== null) {
127
+ const name = match[1].toLowerCase();
128
+ const raw = match[2] ?? match[3] ?? match[4];
129
+ attributes[name] = raw === undefined ? 'true' : unescape(raw);
130
+ }
131
+ return attributes;
132
+ }
133
+ /** Extracts every spec-guard directive from a Markdown source string. */
134
+ export function parseDirectives(source, context) {
135
+ const directives = [];
136
+ const errors = [];
137
+ const masked = maskCode(source);
138
+ const starts = lineStarts(source);
139
+ DIRECTIVE_RE.lastIndex = 0;
140
+ let match;
141
+ while ((match = DIRECTIVE_RE.exec(masked)) !== null) {
142
+ const kind = match[1].toLowerCase();
143
+ const body = match[2];
144
+ const raw = source.slice(match.index, match.index + match[0].length);
145
+ const position = locate(starts, match.index);
146
+ const location = {
147
+ file: context.file,
148
+ relativeFile: context.relativeFile,
149
+ line: position.line,
150
+ column: position.column,
151
+ };
152
+ if (!KINDS.has(kind)) {
153
+ // Only complain about things that clearly meant to be a directive.
154
+ if (kind.startsWith('assert')) {
155
+ errors.push({
156
+ location,
157
+ raw,
158
+ message: `Unknown directive "@${kind}". Expected one of: ${[...KINDS].map((k) => `@${k}`).join(', ')}.`,
159
+ });
160
+ }
161
+ continue;
162
+ }
163
+ const attributes = parseAttributes(body);
164
+ const allowed = ALLOWED_ATTRIBUTES[kind];
165
+ const unknown = Object.keys(attributes).filter((name) => !allowed.has(name));
166
+ if (unknown.length > 0) {
167
+ errors.push({
168
+ location,
169
+ raw,
170
+ message: `Unknown attribute${unknown.length > 1 ? 's' : ''} ${unknown
171
+ .map((name) => `"${name}"`)
172
+ .join(', ')} on @${kind}. Allowed: ${[...allowed].join(', ')}.`,
173
+ });
174
+ continue;
175
+ }
176
+ directives.push({ kind: kind, attributes, location, raw });
177
+ }
178
+ return { directives, errors };
179
+ }
180
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Terminal and JSON reporting.
3
+ *
4
+ * The failure block is the whole product: a spec author who broke an invariant
5
+ * should be able to fix it without opening a single file, so every failure
6
+ * carries the spec location, the expectation, the observed count and real
7
+ * snippets from the offending code.
8
+ */
9
+ import type { RunResult } from './runner.js';
10
+ export interface ReporterOptions {
11
+ color: boolean;
12
+ verbose: boolean;
13
+ /** Symbols safe for legacy Windows consoles. */
14
+ ascii?: boolean;
15
+ }
16
+ declare const ANSI: {
17
+ readonly reset: `${string}[0m`;
18
+ readonly bold: `${string}[1m`;
19
+ readonly dim: `${string}[2m`;
20
+ readonly red: `${string}[31m`;
21
+ readonly green: `${string}[32m`;
22
+ readonly yellow: `${string}[33m`;
23
+ readonly blue: `${string}[34m`;
24
+ readonly magenta: `${string}[35m`;
25
+ readonly cyan: `${string}[36m`;
26
+ readonly gray: `${string}[90m`;
27
+ };
28
+ type Style = keyof typeof ANSI;
29
+ /** Creates a `paint(text, style)` helper honouring the color setting. */
30
+ export declare function createPainter(color: boolean): (text: string, ...styles: Style[]) => string;
31
+ /**
32
+ * Colour is on only when the stream is a TTY and nobody asked otherwise.
33
+ * Honours the NO_COLOR and FORCE_COLOR conventions.
34
+ */
35
+ export declare function shouldUseColor(stream: {
36
+ isTTY?: boolean;
37
+ }, flag: boolean | undefined, env?: NodeJS.ProcessEnv): boolean;
38
+ /** Legacy Windows consoles render box-drawing glyphs poorly; degrade to ASCII. */
39
+ export declare function shouldUseAscii(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
40
+ /** Renders the full human-readable report. */
41
+ export declare function formatReport(report: RunResult, options: ReporterOptions, maxSnippets?: number): string;
42
+ /** Machine-readable output for CI consumers. */
43
+ export declare function formatJson(report: RunResult): string;
44
+ export {};
45
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAKD,QAAA,MAAM,IAAI;aACR,KAAK;aACL,IAAI;aACJ,GAAG;aACH,GAAG;aACH,KAAK;aACL,MAAM;aACN,IAAI;aACJ,OAAO;aACP,IAAI;aACJ,IAAI;CACI,CAAC;AAEX,KAAK,KAAK,GAAG,MAAM,OAAO,IAAI,CAAC;AAE/B,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,KAAK,MAAM,CAI1F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,EAC3B,IAAI,EAAE,OAAO,GAAG,SAAS,EACzB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAKT;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,EAAE,QAAQ,kBAAmB,GAAG,OAAO,CAGzG;AA+ED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,SAAI,GAAG,MAAM,CA6DjG;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CA4CpD"}