@descent-vtt/spec-graph 0.5.0 → 0.7.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 CHANGED
@@ -8,7 +8,8 @@
8
8
  * POSIX paths so results do not depend on the host platform.
9
9
  */
10
10
  import { readdir, stat } from 'node:fs/promises';
11
- import { joinPosix, normalisePosix, toPosix } from './paths.js';
11
+ import { isAbsolutePath, joinPosix, normalisePosix, toPosix } from './paths.js';
12
+ import { compilePattern } from './regex.js';
12
13
  /** Directories skipped unless a pattern explicitly names them. */
13
14
  export const DEFAULT_IGNORED_DIRECTORIES = Object.freeze([
14
15
  '.git',
@@ -41,8 +42,46 @@ export function isGlob(pattern) {
41
42
  * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a
42
43
  * bare directory name matches everything beneath it, which is what people mean
43
44
  * when they write `--ignore drafts`.
45
+ *
46
+ * Nothing in the pipeline matches with this any more - see {@link compileGlob},
47
+ * which gives the same answers without the backtracking. It stays for callers
48
+ * who want a `RegExp`, and as the oracle the tests hold the automaton to.
44
49
  */
45
50
  export function globToRegExp(pattern) {
51
+ return new RegExp(`^${globSource(pattern)}$`, process.platform === 'win32' ? 'i' : '');
52
+ }
53
+ /**
54
+ * Compiles a glob to the automaton every matcher here uses.
55
+ *
56
+ * A glob has no nested quantifiers, and ADR-0017 took that to mean there was no
57
+ * backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each
58
+ * `*` is a `[^/]*`, and against a subject that fails to match, a backtracking
59
+ * engine tries every way of dividing it between them - the subject's length to
60
+ * the power of the stars:
61
+ *
62
+ * ```text
63
+ * RegExp here
64
+ * **\/*-*-*-*.md a 643-character hyphenated file name 2.3s 0.2ms
65
+ * *-*-*-x a 10,000-character reference target 120s 0.9ms
66
+ * ```
67
+ *
68
+ * The first is bounded by the filesystem, which caps a name at 255 bytes. The
69
+ * second is not: `--ignore-ref` patterns are matched against targets read out
70
+ * of documents, and a document is whatever somebody wrote.
71
+ */
72
+ export function compileGlob(pattern, options = {}) {
73
+ const source = globSource(pattern);
74
+ try {
75
+ return compilePattern(`^${source}$`, { ignoreCase: options.ignoreCase ?? process.platform === 'win32' });
76
+ }
77
+ catch (error) {
78
+ // The message describes an expression the user never wrote, so it is told
79
+ // against the glob they did.
80
+ throw new Error(`invalid glob "${pattern}": ${error.message}`);
81
+ }
82
+ }
83
+ /** The expression a glob stands for, before anchors and flags. */
84
+ function globSource(pattern) {
46
85
  let source = '';
47
86
  let i = 0;
48
87
  const braces = [];
@@ -99,7 +138,10 @@ export function globToRegExp(pattern) {
99
138
  source += ch.replace(/[.+^${}()|[\]\\]/g, '\\$&');
100
139
  i += 1;
101
140
  }
102
- return new RegExp(`^${source}$`, process.platform === 'win32' ? 'i' : '');
141
+ // Otherwise reported as an unmatched parenthesis, in a glob that has none.
142
+ if (braces.length > 0)
143
+ throw new Error(`invalid glob "${pattern}": unclosed "{"`);
144
+ return source;
103
145
  }
104
146
  function escapeClass(body) {
105
147
  return body.replace(/[\\^\]]/g, '\\$&');
@@ -120,8 +162,8 @@ export function createGlobMatcher(patterns) {
120
162
  return {
121
163
  negated,
122
164
  exact: isGlob(normalised) ? null : normalised.toLowerCase(),
123
- expression: globToRegExp(expanded),
124
- direct: globToRegExp(normalised),
165
+ expression: compileGlob(expanded),
166
+ direct: compileGlob(normalised),
125
167
  };
126
168
  });
127
169
  return (path) => {
@@ -148,13 +190,18 @@ export function createGlobMatcher(patterns) {
148
190
  * Matching is case-insensitive on every platform. Path matching inherits the
149
191
  * host filesystem's case rules, which is right for paths and wrong here: a
150
192
  * repository's findings must not depend on which machine ran the check.
193
+ *
194
+ * Both sides are lower-cased, and that is the whole of the case rule. Folding
195
+ * as well used to happen on Windows only, where it made U+00B5 (micro sign)
196
+ * and U+03BC (mu) the same target - one machine's answer, which is what this
197
+ * function exists not to give.
151
198
  */
152
199
  export function createReferenceFilter(patterns) {
153
200
  if (patterns.length === 0)
154
201
  return () => false;
155
202
  const compiled = patterns
156
203
  .filter((pattern) => pattern.trim().length > 0)
157
- .map((pattern) => globToRegExp(pattern.trim().toLowerCase()));
204
+ .map((pattern) => compileGlob(pattern.trim().toLowerCase(), { ignoreCase: false }));
158
205
  if (compiled.length === 0)
159
206
  return () => false;
160
207
  return (target) => {
@@ -277,8 +324,16 @@ export async function walkFiles(options) {
277
324
  await walk(base);
278
325
  return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
279
326
  }
280
- /** Resolves a repository-relative path against the root, in POSIX form. */
327
+ /**
328
+ * Resolves a repository-relative path against the root, in POSIX form.
329
+ *
330
+ * An absolute path already names one place, and joining it under the root names
331
+ * another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing
332
+ * there, and accepted nothing without saying so.
333
+ */
281
334
  export function underRoot(root, relative) {
335
+ if (isAbsolutePath(relative))
336
+ return toPosix(relative);
282
337
  return joinPosix(toPosix(root), relative);
283
338
  }
284
339
  //# sourceMappingURL=glob.js.map
package/dist/glob.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * A dependency-free glob, because pulling in a matcher for `docs/**\\/*.md` would\n * be the single largest thing in the package. The supported syntax is the\n * portion people actually type - `**`, `*`, `?`, `{a,b}`, `[abc]`, and a leading\n * `!` for negation - and everything is matched against repository-relative\n * POSIX paths so results do not depend on the host platform.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { joinPosix, normalisePosix, toPosix } from './paths.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return /[*?[\\]{}]/.test(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression.\n *\n * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a\n * bare directory name matches everything beneath it, which is what people mean\n * when they write `--ignore drafts`.\n */\nexport function globToRegExp(pattern: string): RegExp {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n return new RegExp(`^${source}$`, process.platform === 'win32' ? 'i' : '');\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled = patterns.map((pattern) => {\n const negated = pattern.startsWith('!');\n const body = negated ? pattern.slice(1) : pattern;\n const normalised = normalisePosix(toPosix(body));\n // A bare directory means everything under it.\n const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;\n return {\n negated,\n exact: isGlob(normalised) ? null : normalised.toLowerCase(),\n expression: globToRegExp(expanded),\n direct: globToRegExp(normalised),\n };\n });\n\n return (path: string): boolean => {\n let included = false;\n for (const entry of compiled) {\n const hit =\n entry.direct.test(path) ||\n entry.expression.test(path) ||\n (entry.exact !== null && entry.exact === path.toLowerCase());\n if (hit) included = !entry.negated;\n }\n return included;\n };\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: it expands a bare name to `name/**` because a directory means\n * everything under it. A reference target has no such structure - `trap 55` is\n * a name, not a location - so a bare pattern must match it literally and\n * nothing else.\n *\n * Matching is case-insensitive on every platform. Path matching inherits the\n * host filesystem's case rules, which is right for paths and wrong here: a\n * repository's findings must not depend on which machine ran the check.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n if (patterns.length === 0) return () => false;\n const compiled = patterns\n .filter((pattern) => pattern.trim().length > 0)\n .map((pattern) => globToRegExp(pattern.trim().toLowerCase()));\n if (compiled.length === 0) return () => false;\n return (target: string): boolean => {\n const value = target.trim().toLowerCase();\n return compiled.some((expression) => expression.test(value));\n };\n}\n\n/** The literal directory prefix of a pattern, used to avoid walking the world. */\nexport function globBase(pattern: string): string {\n const normalised = normalisePosix(toPosix(pattern.startsWith('!') ? pattern.slice(1) : pattern));\n const segments = normalised.split('/');\n const literal: string[] = [];\n for (const segment of segments) {\n if (isGlob(segment)) break;\n literal.push(segment);\n }\n // The last literal segment may be the file itself rather than a directory.\n if (literal.length === segments.length && literal.length > 0) literal.pop();\n return literal.join('/');\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const matcher = createGlobMatcher(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n const ignoredNames = new Set([\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...ignores.filter((pattern) => !isGlob(pattern) && !pattern.includes('/')),\n ]);\n const pathIgnores = ignores.filter((pattern) => isGlob(pattern) || pattern.includes('/'));\n const ignoreMatcher = pathIgnores.length > 0 ? createGlobMatcher(pathIgnores) : null;\n const excluded = (path: string): boolean => ignoreMatcher !== null && ignoreMatcher(path);\n\n // Only walk the directories the patterns can possibly reach.\n const bases = new Set<string>();\n for (const pattern of options.patterns) {\n if (pattern.startsWith('!')) continue;\n bases.add(globBase(pattern));\n }\n if (bases.size === 0) bases.add('');\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!matcher(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!matcher(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/** Resolves a repository-relative path against the root, in POSIX form. */\nexport function underRoot(root: string, relative: string): string {\n return joinPosix(toPosix(root), relative);\n}\n"]}
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../src/glob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Pattern matching and directory walking.\n *\n * A dependency-free glob, because pulling in a matcher for `docs/**\\/*.md` would\n * be the single largest thing in the package. The supported syntax is the\n * portion people actually type - `**`, `*`, `?`, `{a,b}`, `[abc]`, and a leading\n * `!` for negation - and everything is matched against repository-relative\n * POSIX paths so results do not depend on the host platform.\n */\n\nimport { readdir, stat } from 'node:fs/promises';\n\nimport { isAbsolutePath, joinPosix, normalisePosix, toPosix } from './paths.js';\nimport { compilePattern, type Matcher } from './regex.js';\n\n/** Directories skipped unless a pattern explicitly names them. */\nexport const DEFAULT_IGNORED_DIRECTORIES: readonly string[] = Object.freeze([\n '.git',\n '.hg',\n '.svn',\n '.cache',\n '.next',\n '.nuxt',\n '.turbo',\n '.venv',\n 'node_modules',\n 'bower_components',\n 'vendor',\n 'dist',\n 'build',\n 'out',\n 'target',\n 'coverage',\n '__pycache__',\n]);\n\n/** Files larger than this are skipped: a specification is not a binary. */\nexport const MAX_FILE_SIZE = 4 * 1024 * 1024;\n\nexport interface WalkedFile {\n /** Repository-relative POSIX path. */\n readonly path: string;\n /** Absolute host path, for reading. */\n readonly absolute: string;\n readonly size: number;\n}\n\nexport interface WalkOptions {\n readonly root: string;\n readonly patterns: readonly string[];\n readonly ignore?: readonly string[] | undefined;\n readonly maxFileSize?: number | undefined;\n /** Follow directory symlinks. Off by default: cycles are real. */\n readonly followSymlinks?: boolean | undefined;\n}\n\n/** True when the string contains glob syntax rather than being a literal path. */\nexport function isGlob(pattern: string): boolean {\n return /[*?[\\]{}]/.test(pattern);\n}\n\n/**\n * Compiles a glob to an anchored regular expression.\n *\n * `**` crosses directory separators; `*` and `?` do not. A trailing `/` or a\n * bare directory name matches everything beneath it, which is what people mean\n * when they write `--ignore drafts`.\n *\n * Nothing in the pipeline matches with this any more - see {@link compileGlob},\n * which gives the same answers without the backtracking. It stays for callers\n * who want a `RegExp`, and as the oracle the tests hold the automaton to.\n */\nexport function globToRegExp(pattern: string): RegExp {\n return new RegExp(`^${globSource(pattern)}$`, process.platform === 'win32' ? 'i' : '');\n}\n\nexport interface GlobOptions {\n /** Fold case. Defaults to the host's convention: on for Windows, off elsewhere. */\n readonly ignoreCase?: boolean | undefined;\n}\n\n/**\n * Compiles a glob to the automaton every matcher here uses.\n *\n * A glob has no nested quantifiers, and ADR-0017 took that to mean there was no\n * backtracking hazard in handing one to `RegExp`. Nobody had timed it. Each\n * `*` is a `[^/]*`, and against a subject that fails to match, a backtracking\n * engine tries every way of dividing it between them - the subject's length to\n * the power of the stars:\n *\n * ```text\n * RegExp here\n * **\\/*-*-*-*.md a 643-character hyphenated file name 2.3s 0.2ms\n * *-*-*-x a 10,000-character reference target 120s 0.9ms\n * ```\n *\n * The first is bounded by the filesystem, which caps a name at 255 bytes. The\n * second is not: `--ignore-ref` patterns are matched against targets read out\n * of documents, and a document is whatever somebody wrote.\n */\nexport function compileGlob(pattern: string, options: GlobOptions = {}): Matcher {\n const source = globSource(pattern);\n try {\n return compilePattern(`^${source}$`, { ignoreCase: options.ignoreCase ?? process.platform === 'win32' });\n } catch (error) {\n // The message describes an expression the user never wrote, so it is told\n // against the glob they did.\n throw new Error(`invalid glob \"${pattern}\": ${(error as Error).message}`);\n }\n}\n\n/** The expression a glob stands for, before anchors and flags. */\nfunction globSource(pattern: string): string {\n let source = '';\n let i = 0;\n const braces: number[] = [];\n\n while (i < pattern.length) {\n const ch = pattern[i] as string;\n\n if (ch === '*') {\n const doubled = pattern[i + 1] === '*';\n if (doubled) {\n const slashAfter = pattern[i + 2] === '/';\n // `a/**/b` must also match `a/b`, so the separator is folded in.\n source += slashAfter ? '(?:.*/)?' : '.*';\n i += slashAfter ? 3 : 2;\n } else {\n source += '[^/]*';\n i += 1;\n }\n continue;\n }\n\n if (ch === '?') {\n source += '[^/]';\n i += 1;\n continue;\n }\n\n if (ch === '[') {\n const close = pattern.indexOf(']', i + 1);\n if (close === -1) {\n source += '\\\\[';\n i += 1;\n continue;\n }\n const body = pattern.slice(i + 1, close);\n source += `[${body.startsWith('!') ? `^${escapeClass(body.slice(1))}` : escapeClass(body)}]`;\n i = close + 1;\n continue;\n }\n\n if (ch === '{') {\n braces.push(source.length);\n source += '(?:';\n i += 1;\n continue;\n }\n if (ch === '}' && braces.length > 0) {\n braces.pop();\n source += ')';\n i += 1;\n continue;\n }\n if (ch === ',' && braces.length > 0) {\n source += '|';\n i += 1;\n continue;\n }\n\n source += ch.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n i += 1;\n }\n\n // Otherwise reported as an unmatched parenthesis, in a glob that has none.\n if (braces.length > 0) throw new Error(`invalid glob \"${pattern}\": unclosed \"{\"`);\n return source;\n}\n\nfunction escapeClass(body: string): string {\n return body.replace(/[\\\\^\\]]/g, '\\\\$&');\n}\n\nexport interface GlobMatcher {\n (path: string): boolean;\n}\n\n/**\n * Builds a matcher from include patterns, honouring `!` negations.\n *\n * Later patterns win, so `docs/**\\/*.md` followed by `!docs/drafts/**` reads the\n * way a `.gitignore` does.\n */\nexport function createGlobMatcher(patterns: readonly string[]): GlobMatcher {\n const compiled = patterns.map((pattern) => {\n const negated = pattern.startsWith('!');\n const body = negated ? pattern.slice(1) : pattern;\n const normalised = normalisePosix(toPosix(body));\n // A bare directory means everything under it.\n const expanded = isGlob(normalised) ? normalised : `${normalised}/**`;\n return {\n negated,\n exact: isGlob(normalised) ? null : normalised.toLowerCase(),\n expression: compileGlob(expanded),\n direct: compileGlob(normalised),\n };\n });\n\n return (path: string): boolean => {\n let included = false;\n for (const entry of compiled) {\n const hit =\n entry.direct.test(path) ||\n entry.expression.test(path) ||\n (entry.exact !== null && entry.exact === path.toLowerCase());\n if (hit) included = !entry.negated;\n }\n return included;\n };\n}\n\n/**\n * Builds a predicate over reference *targets*, not paths.\n *\n * Separate from {@link createGlobMatcher} on purpose. That one is\n * path-oriented: it expands a bare name to `name/**` because a directory means\n * everything under it. A reference target has no such structure - `trap 55` is\n * a name, not a location - so a bare pattern must match it literally and\n * nothing else.\n *\n * Matching is case-insensitive on every platform. Path matching inherits the\n * host filesystem's case rules, which is right for paths and wrong here: a\n * repository's findings must not depend on which machine ran the check.\n *\n * Both sides are lower-cased, and that is the whole of the case rule. Folding\n * as well used to happen on Windows only, where it made U+00B5 (micro sign)\n * and U+03BC (mu) the same target - one machine's answer, which is what this\n * function exists not to give.\n */\nexport function createReferenceFilter(patterns: readonly string[]): (target: string) => boolean {\n if (patterns.length === 0) return () => false;\n const compiled = patterns\n .filter((pattern) => pattern.trim().length > 0)\n .map((pattern) => compileGlob(pattern.trim().toLowerCase(), { ignoreCase: false }));\n if (compiled.length === 0) return () => false;\n return (target: string): boolean => {\n const value = target.trim().toLowerCase();\n return compiled.some((expression) => expression.test(value));\n };\n}\n\n/** The literal directory prefix of a pattern, used to avoid walking the world. */\nexport function globBase(pattern: string): string {\n const normalised = normalisePosix(toPosix(pattern.startsWith('!') ? pattern.slice(1) : pattern));\n const segments = normalised.split('/');\n const literal: string[] = [];\n for (const segment of segments) {\n if (isGlob(segment)) break;\n literal.push(segment);\n }\n // The last literal segment may be the file itself rather than a directory.\n if (literal.length === segments.length && literal.length > 0) literal.pop();\n return literal.join('/');\n}\n\n/**\n * Walks the tree and returns every file matching the patterns.\n *\n * Directories are pruned as early as possible: a repository with a 2 GB\n * `node_modules` should cost nothing to scan.\n */\nexport async function walkFiles(options: WalkOptions): Promise<WalkedFile[]> {\n const root = toPosix(options.root).replace(/\\/+$/, '');\n const matcher = createGlobMatcher(options.patterns);\n const maxSize = options.maxFileSize ?? MAX_FILE_SIZE;\n\n // Ignores come in two shapes and both are documented. A bare name prunes any\n // directory called that, at any depth, the way a `.gitignore` line does.\n // Anything carrying a separator or glob syntax is matched against the\n // repository-relative path, which is what `--ignore \"docs/drafts/**\"` means -\n // and treating that as a directory name silently excluded nothing at all.\n const ignores = options.ignore ?? [];\n const ignoredNames = new Set([\n ...DEFAULT_IGNORED_DIRECTORIES,\n ...ignores.filter((pattern) => !isGlob(pattern) && !pattern.includes('/')),\n ]);\n const pathIgnores = ignores.filter((pattern) => isGlob(pattern) || pattern.includes('/'));\n const ignoreMatcher = pathIgnores.length > 0 ? createGlobMatcher(pathIgnores) : null;\n const excluded = (path: string): boolean => ignoreMatcher !== null && ignoreMatcher(path);\n\n // Only walk the directories the patterns can possibly reach.\n const bases = new Set<string>();\n for (const pattern of options.patterns) {\n if (pattern.startsWith('!')) continue;\n bases.add(globBase(pattern));\n }\n if (bases.size === 0) bases.add('');\n // Drop any base already contained in another: walking it again would only\n // repeat work. The empty base is the repository root, which contains\n // everything, so when it is present it is the only root worth walking.\n const roots = bases.has('')\n ? ['']\n : [...bases].filter((base) => ![...bases].some((other) => other !== base && base.startsWith(`${other}/`)));\n\n const out = new Map<string, WalkedFile>();\n const visited = new Set<string>();\n\n const walk = async (relative: string): Promise<void> => {\n const absolute = relative.length === 0 ? root : `${root}/${relative}`;\n if (visited.has(absolute)) return;\n visited.add(absolute);\n\n let entries;\n try {\n entries = await readdir(absolute, { withFileTypes: true });\n } catch {\n // An unreadable directory is not a reason to fail the whole run.\n return;\n }\n\n for (const entry of entries) {\n const child = relative.length === 0 ? entry.name : `${relative}/${entry.name}`;\n\n if (entry.isDirectory()) {\n // Pruning a whole subtree is an optimisation; a pattern that only\n // matches the files inside it is still honoured when they are filtered.\n if (ignoredNames.has(entry.name) || excluded(child)) continue;\n await walk(child);\n continue;\n }\n\n if (entry.isSymbolicLink()) {\n if (!options.followSymlinks) continue;\n try {\n const info = await stat(`${root}/${child}`);\n if (info.isDirectory()) {\n await walk(child);\n continue;\n }\n if (!matcher(child) || excluded(child) || info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n continue;\n }\n\n if (!entry.isFile()) continue;\n if (!matcher(child) || excluded(child)) continue;\n\n try {\n const info = await stat(`${root}/${child}`);\n if (info.size > maxSize) continue;\n out.set(child, { path: child, absolute: `${root}/${child}`, size: info.size });\n } catch {\n continue;\n }\n }\n };\n\n for (const base of roots) await walk(base);\n\n return [...out.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Resolves a repository-relative path against the root, in POSIX form.\n *\n * An absolute path already names one place, and joining it under the root names\n * another: `--baseline /tmp/b.json` read `<root>/tmp/b.json`, found nothing\n * there, and accepted nothing without saying so.\n */\nexport function underRoot(root: string, relative: string): string {\n if (isAbsolutePath(relative)) return toPosix(relative);\n return joinPosix(toPosix(root), relative);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -33,7 +33,7 @@ export { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';
33
33
  export type { SpecGraph } from './graph.js';
34
34
  export { attributesOf, execute, matches, parseQuery, pattern, query, renderMatch, QueryError, DEFAULT_MATCH_LIMIT, SELECTOR_KEYS, } from './select.js';
35
35
  export { compilePattern, PatternError } from './regex.js';
36
- export type { Matcher } from './regex.js';
36
+ export type { Matcher, PatternOptions } from './regex.js';
37
37
  export type { ExecuteOptions, Match, NodeMatcher, Operator, Predicate, QuerySpec, StepSpec, } from './select.js';
38
38
  export { resolveStrict, runRules, sortDiagnostics, DEFAULT_SEVERITIES, RULE_DECISIONS, RULE_DESCRIPTIONS, RULE_IDS, RULE_QUERIES, } from './rules.js';
39
39
  export type { RuleOptions } from './rules.js';
@@ -49,7 +49,7 @@ export { resolveCorpus, documentOf } from './resolve.js';
49
49
  export type { ResolvedCorpus, ResolveOptions } from './resolve.js';
50
50
  export { scanMarkdown, slugify } from './markdown.js';
51
51
  export type { FrontMatter, Heading, HtmlComment, Link, LinkForm, ListItem, Range, ScannedDocument, ScannedLine, } from './markdown.js';
52
- export { parseDirectives, attr, attrList, directiveFor } from './directives.js';
52
+ export { parseDirectives, attr, attrList, bindItemDirectives, directiveFor } from './directives.js';
53
53
  export type { Attribute, Directive, DirectiveName } from './directives.js';
54
54
  export { parseFrontMatter, toRecord, valuesOf } from './yaml.js';
55
55
  export type { YamlEntry } from './yaml.js';
@@ -58,10 +58,10 @@ export { resolveItemState, KNOWN_STATE_WORDS } from './state.js';
58
58
  export type { ResolvedState, StateInput } from './state.js';
59
59
  export { familyFromPath, identify, isDocumentTarget, isExternal, looksLikePath, normaliseRef, parseBareRef, parsePrefixedRef, splitAnchor, ID_KEYS, } from './identity.js';
60
60
  export type { DocumentIdentity, IdentityInput } from './identity.js';
61
- export { createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
62
- export type { GlobMatcher, WalkedFile, WalkOptions } from './glob.js';
61
+ export { compileGlob, createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
62
+ export type { GlobMatcher, GlobOptions, WalkedFile, WalkOptions } from './glob.js';
63
63
  export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
64
64
  export type { LineIndex } from './source.js';
65
- export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
65
+ export { basenamePosix, dirnamePosix, extnamePosix, isAbsolutePath, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
66
66
  export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
67
67
  export type { AnalysisSummary, AnyRuleId, DanglingReason, DanglingRef, Diagnostic, Disposition, DocumentNode, Edge, EdgeKind, EdgeOrigin, EdgeTraits, ItemNode, MisreadKey, NodeKind, Openness, ParseProblem, Phase, Position, Receptivity, ProjectRuleId, RelatedLocation, RuleId, Severity, SourceRef, Span, SpecNode, StateSignal, SuppressedRef, } from './types.js';
package/dist/index.js CHANGED
@@ -35,13 +35,13 @@ export { extractDocument, extractSpecifications, classifyReference, inheritableF
35
35
  export { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';
36
36
  export { resolveCorpus, documentOf } from './resolve.js';
37
37
  export { scanMarkdown, slugify } from './markdown.js';
38
- export { parseDirectives, attr, attrList, directiveFor } from './directives.js';
38
+ export { parseDirectives, attr, attrList, bindItemDirectives, directiveFor } from './directives.js';
39
39
  export { parseFrontMatter, toRecord, valuesOf } from './yaml.js';
40
40
  export { isRetired, isStatusHeading, normaliseStatus, phaseFromPath, phaseOf, receptivityOf, supersessionTargetsIn, STATUS_KEYS, } from './lifecycle.js';
41
41
  export { resolveItemState, KNOWN_STATE_WORDS } from './state.js';
42
42
  export { familyFromPath, identify, isDocumentTarget, isExternal, looksLikePath, normaliseRef, parseBareRef, parsePrefixedRef, splitAnchor, ID_KEYS, } from './identity.js';
43
- export { createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
43
+ export { compileGlob, createGlobMatcher, createReferenceFilter, globBase, globToRegExp, isGlob, walkFiles, DEFAULT_IGNORED_DIRECTORIES, MAX_FILE_SIZE, } from './glob.js';
44
44
  export { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';
45
- export { basenamePosix, dirnamePosix, extnamePosix, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
45
+ export { basenamePosix, dirnamePosix, extnamePosix, isAbsolutePath, joinPosix, normalisePosix, resolveFrom, toPosix, } from './paths.js';
46
46
  export { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';
47
47
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';\n\nexport { discoverConfig, loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { DiscoveredConfig, DiscoveryIO, LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n pattern,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n SELECTOR_KEYS,\n} from './select.js';\n\nexport { compilePattern, PatternError } from './regex.js';\nexport type { Matcher } from './regex.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport { compileProjectRules, renderTemplate } from './project-rules.js';\nexport type { CompiledProjectRules, ProjectRule } from './project-rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport {\n extractDocument,\n extractSpecifications,\n classifyReference,\n inheritableFrontMatter,\n sectionPathAt,\n} from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';\nexport type {\n AnalysisSummary,\n AnyRuleId,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n MisreadKey,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n ProjectRuleId,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n SuppressedRef,\n} from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Programmatic API.\n *\n * The CLI is a shell over `analyse`; anything the CLI can do, a build script, a\n * custom reporter or an editor extension can do too.\n *\n * ```ts\n * import { analyse, formatReport } from '@descent-vtt/spec-graph';\n *\n * const result = await analyse({ root: process.cwd(), patterns: ['docs/**\\/*.md'] });\n * if (!result.ok) console.error(formatReport(result, { color: true }));\n * ```\n *\n * For a corpus that is already in memory - a monorepo tool, a language server,\n * a test - skip the filesystem entirely:\n *\n * ```ts\n * import { analyseSources, query } from '@descent-vtt/spec-graph';\n *\n * const { graph } = analyseSources([{ path: 'adr/0001.md', text: '...' }]);\n * const ghosts = query(graph, 'item[openness=open] -delegates-to-> document[phase=retired]');\n * ```\n */\n\nexport {\n applyBaseline,\n formatBaseline,\n fingerprintOf,\n parseBaseline,\n BASELINE_VERSION,\n EMPTY_BASELINE,\n} from './baseline.js';\nexport type { Baseline, BaselineEntry, BaselineOutcome, ParsedBaseline, StaleEntry } from './baseline.js';\n\nexport { discoverConfig, loadConfig, parseConfig, CONFIG_FILES, CONFIG_PACKAGE_KEY } from './config.js';\nexport type { DiscoveredConfig, DiscoveryIO, LoadedConfig, SpecGraphConfig } from './config.js';\n\nexport { main, parseArgs, HELP, UsageError, EXIT_OK, EXIT_FAILED, EXIT_ERROR } from './cli.js';\nexport type { CliIO, CliOptions, Command } from './cli.js';\n\nexport {\n analyse,\n analyseSources,\n createFamilyFilter,\n createHistoryMatcher,\n withDiagnostics,\n DEFAULT_CONCURRENCY,\n DEFAULT_PATTERNS,\n} from './runner.js';\nexport type { AnalyseOptions, AnalyseSourcesOptions, AnalysisResult, Source } from './runner.js';\n\nexport { buildGraph, LOAD_BEARING_EDGES, OBLIGATION_EDGES } from './graph.js';\nexport type { SpecGraph } from './graph.js';\n\nexport {\n attributesOf,\n execute,\n matches,\n parseQuery,\n pattern,\n query,\n renderMatch,\n QueryError,\n DEFAULT_MATCH_LIMIT,\n SELECTOR_KEYS,\n} from './select.js';\n\nexport { compilePattern, PatternError } from './regex.js';\nexport type { Matcher, PatternOptions } from './regex.js';\nexport type {\n ExecuteOptions,\n Match,\n NodeMatcher,\n Operator,\n Predicate,\n QuerySpec,\n StepSpec,\n} from './select.js';\n\nexport {\n resolveStrict,\n runRules,\n sortDiagnostics,\n DEFAULT_SEVERITIES,\n RULE_DECISIONS,\n RULE_DESCRIPTIONS,\n RULE_IDS,\n RULE_QUERIES,\n} from './rules.js';\nexport type { RuleOptions } from './rules.js';\n\nexport { compileProjectRules, renderTemplate } from './project-rules.js';\nexport type { CompiledProjectRules, ProjectRule } from './project-rules.js';\n\nexport {\n createPainter,\n formatGraph,\n formatJson,\n formatMarkdown,\n formatReport,\n formatSarif,\n shouldUseAscii,\n shouldUseColor,\n} from './report.js';\nexport type { ColorEnvironment, GraphExportOptions, GraphFormat, Painter, ReporterOptions } from './report.js';\n\nexport {\n extractDocument,\n extractSpecifications,\n classifyReference,\n inheritableFrontMatter,\n sectionPathAt,\n} from './extract.js';\nexport { findSpecificationRegions, readRegionStatus, regionAt } from './sections.js';\nexport type { RegionStatus, SpecificationRegion } from './sections.js';\nexport type { Classification, ExtractedDocument, ExtractInput, ReferenceCandidate } from './extract.js';\n\nexport { resolveCorpus, documentOf } from './resolve.js';\nexport type { ResolvedCorpus, ResolveOptions } from './resolve.js';\n\nexport { scanMarkdown, slugify } from './markdown.js';\nexport type {\n FrontMatter,\n Heading,\n HtmlComment,\n Link,\n LinkForm,\n ListItem,\n Range,\n ScannedDocument,\n ScannedLine,\n} from './markdown.js';\n\nexport { parseDirectives, attr, attrList, bindItemDirectives, directiveFor } from './directives.js';\nexport type { Attribute, Directive, DirectiveName } from './directives.js';\n\nexport { parseFrontMatter, toRecord, valuesOf } from './yaml.js';\nexport type { YamlEntry } from './yaml.js';\n\nexport {\n isRetired,\n isStatusHeading,\n normaliseStatus,\n phaseFromPath,\n phaseOf,\n receptivityOf,\n supersessionTargetsIn,\n STATUS_KEYS,\n} from './lifecycle.js';\n\nexport { resolveItemState, KNOWN_STATE_WORDS } from './state.js';\nexport type { ResolvedState, StateInput } from './state.js';\n\nexport {\n familyFromPath,\n identify,\n isDocumentTarget,\n isExternal,\n looksLikePath,\n normaliseRef,\n parseBareRef,\n parsePrefixedRef,\n splitAnchor,\n ID_KEYS,\n} from './identity.js';\nexport type { DocumentIdentity, IdentityInput } from './identity.js';\n\nexport {\n compileGlob,\n createGlobMatcher,\n createReferenceFilter,\n globBase,\n globToRegExp,\n isGlob,\n walkFiles,\n DEFAULT_IGNORED_DIRECTORIES,\n MAX_FILE_SIZE,\n} from './glob.js';\nexport type { GlobMatcher, GlobOptions, WalkedFile, WalkOptions } from './glob.js';\n\nexport { compareRefs, createLineIndex, formatRef, refOf, spanOf } from './source.js';\nexport type { LineIndex } from './source.js';\n\nexport {\n basenamePosix,\n dirnamePosix,\n extnamePosix,\n isAbsolutePath,\n joinPosix,\n normalisePosix,\n resolveFrom,\n toPosix,\n} from './paths.js';\n\nexport { isProjectRule, DISPOSITIONS, EDGE_KINDS, EDGE_TRAITS, OPENNESS_OF, PROJECT_RULE_PREFIX } from './types.js';\nexport type {\n AnalysisSummary,\n AnyRuleId,\n DanglingReason,\n DanglingRef,\n Diagnostic,\n Disposition,\n DocumentNode,\n Edge,\n EdgeKind,\n EdgeOrigin,\n EdgeTraits,\n ItemNode,\n MisreadKey,\n NodeKind,\n Openness,\n ParseProblem,\n Phase,\n Position,\n Receptivity,\n ProjectRuleId,\n RelatedLocation,\n RuleId,\n Severity,\n SourceRef,\n Span,\n SpecNode,\n StateSignal,\n SuppressedRef,\n} from './types.js';\n"]}
package/dist/paths.d.ts CHANGED
@@ -8,6 +8,16 @@
8
8
  */
9
9
  /** Converts a host path to POSIX form. */
10
10
  export declare function toPosix(value: string): string;
11
+ /**
12
+ * Whether a path names a place outright rather than a place relative to one.
13
+ *
14
+ * Both platforms' spellings, whichever this is running on: one repository is
15
+ * read on a Windows checkout and in Linux CI, so a path is absolute where it
16
+ * was written rather than where it is being read. `C:docs` counts, because it
17
+ * is relative to a drive rather than to a root, and prefixing it with a root
18
+ * produces a third place that is neither.
19
+ */
20
+ export declare function isAbsolutePath(value: string): boolean;
11
21
  /** Removes `.` and `..` segments. Leading `..` are preserved. */
12
22
  export declare function normalisePosix(value: string): string;
13
23
  /** The directory containing `value`, or `''` at the root. */
package/dist/paths.js CHANGED
@@ -10,6 +10,19 @@
10
10
  export function toPosix(value) {
11
11
  return value.replace(/\\/g, '/');
12
12
  }
13
+ const ABSOLUTE = /^(?:[/\\]|[A-Za-z]:)/;
14
+ /**
15
+ * Whether a path names a place outright rather than a place relative to one.
16
+ *
17
+ * Both platforms' spellings, whichever this is running on: one repository is
18
+ * read on a Windows checkout and in Linux CI, so a path is absolute where it
19
+ * was written rather than where it is being read. `C:docs` counts, because it
20
+ * is relative to a drive rather than to a root, and prefixing it with a root
21
+ * produces a third place that is neither.
22
+ */
23
+ export function isAbsolutePath(value) {
24
+ return ABSOLUTE.test(value);
25
+ }
13
26
  /** Removes `.` and `..` segments. Leading `..` are preserved. */
14
27
  export function normalisePosix(value) {
15
28
  const absolute = value.startsWith('/');
package/dist/paths.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;iBAC1C,IAAI,CAAC,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,GAAG,QAA2B;IACtD,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC","sourcesContent":["/**\n * POSIX path arithmetic, independent of the host platform.\n *\n * Every path inside spec-graph is a repository-relative POSIX path, on Windows\n * as much as on Linux. Node's `path` module would happily produce\n * `docs\\adr\\0007.md` on one machine and `docs/adr/0007.md` on another, and a\n * graph whose node identities depend on which laptop built it is not a graph.\n */\n\n/** Converts a host path to POSIX form. */\nexport function toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\n/** Removes `.` and `..` segments. Leading `..` are preserved. */\nexport function normalisePosix(value: string): string {\n const absolute = value.startsWith('/');\n const out: string[] = [];\n for (const segment of value.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n const last = out[out.length - 1];\n if (out.length > 0 && last !== '..') out.pop();\n else if (!absolute) out.push('..');\n continue;\n }\n out.push(segment);\n }\n const joined = out.join('/');\n return absolute ? `/${joined}` : joined;\n}\n\n/** The directory containing `value`, or `''` at the root. */\nexport function dirnamePosix(value: string): string {\n const slash = value.lastIndexOf('/');\n return slash === -1 ? '' : value.slice(0, slash);\n}\n\n/** The final segment of `value`. */\nexport function basenamePosix(value: string): string {\n return value.slice(value.lastIndexOf('/') + 1);\n}\n\n/** Joins segments and normalises the result. */\nexport function joinPosix(...segments: readonly string[]): string {\n return normalisePosix(segments.filter((segment) => segment.length > 0).join('/'));\n}\n\n/** Resolves `target` against the directory holding `from`. */\nexport function resolveFrom(from: string, target: string): string {\n if (target.startsWith('/')) return normalisePosix(target);\n return joinPosix(dirnamePosix(from), target);\n}\n\n/** The extension including the dot, lower-cased, or `''`. */\nexport function extnamePosix(value: string): string {\n const base = basenamePosix(value);\n const dot = base.lastIndexOf('.');\n return dot <= 0 ? '' : base.slice(dot).toLowerCase();\n}\n"]}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACnC,CAAC;AAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI;gBAAE,GAAG,CAAC,GAAG,EAAE,CAAC;iBAC1C,IAAI,CAAC,QAAQ;gBAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,QAAQ,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,SAAS,CAAC,GAAG,QAA2B;IACtD,OAAO,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACpF,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAc;IACtD,IAAI,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;IAC1D,OAAO,SAAS,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;AAC/C,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACvD,CAAC","sourcesContent":["/**\n * POSIX path arithmetic, independent of the host platform.\n *\n * Every path inside spec-graph is a repository-relative POSIX path, on Windows\n * as much as on Linux. Node's `path` module would happily produce\n * `docs\\adr\\0007.md` on one machine and `docs/adr/0007.md` on another, and a\n * graph whose node identities depend on which laptop built it is not a graph.\n */\n\n/** Converts a host path to POSIX form. */\nexport function toPosix(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\nconst ABSOLUTE = /^(?:[/\\\\]|[A-Za-z]:)/;\n\n/**\n * Whether a path names a place outright rather than a place relative to one.\n *\n * Both platforms' spellings, whichever this is running on: one repository is\n * read on a Windows checkout and in Linux CI, so a path is absolute where it\n * was written rather than where it is being read. `C:docs` counts, because it\n * is relative to a drive rather than to a root, and prefixing it with a root\n * produces a third place that is neither.\n */\nexport function isAbsolutePath(value: string): boolean {\n return ABSOLUTE.test(value);\n}\n\n/** Removes `.` and `..` segments. Leading `..` are preserved. */\nexport function normalisePosix(value: string): string {\n const absolute = value.startsWith('/');\n const out: string[] = [];\n for (const segment of value.split('/')) {\n if (segment === '' || segment === '.') continue;\n if (segment === '..') {\n const last = out[out.length - 1];\n if (out.length > 0 && last !== '..') out.pop();\n else if (!absolute) out.push('..');\n continue;\n }\n out.push(segment);\n }\n const joined = out.join('/');\n return absolute ? `/${joined}` : joined;\n}\n\n/** The directory containing `value`, or `''` at the root. */\nexport function dirnamePosix(value: string): string {\n const slash = value.lastIndexOf('/');\n return slash === -1 ? '' : value.slice(0, slash);\n}\n\n/** The final segment of `value`. */\nexport function basenamePosix(value: string): string {\n return value.slice(value.lastIndexOf('/') + 1);\n}\n\n/** Joins segments and normalises the result. */\nexport function joinPosix(...segments: readonly string[]): string {\n return normalisePosix(segments.filter((segment) => segment.length > 0).join('/'));\n}\n\n/** Resolves `target` against the directory holding `from`. */\nexport function resolveFrom(from: string, target: string): string {\n if (target.startsWith('/')) return normalisePosix(target);\n return joinPosix(dirnamePosix(from), target);\n}\n\n/** The extension including the dot, lower-cased, or `''`. */\nexport function extnamePosix(value: string): string {\n const base = basenamePosix(value);\n const dot = base.lastIndexOf('.');\n return dot <= 0 ? '' : base.slice(dot).toLowerCase();\n}\n"]}
package/dist/regex.d.ts CHANGED
@@ -35,10 +35,12 @@
35
35
  * whole trade: a predicate that finishes, against a short list of constructs
36
36
  * that a documentation selector never needed.
37
37
  *
38
- * Matching is case-insensitive and unanchored because that is what `~=` has
39
- * always been - `new RegExp(source, 'i')` and `.test()`. There are no capture
40
- * groups at all: nothing asks where the match was, only whether there was one,
41
- * which is why this is a plain NFA simulation rather than a Pike VM.
38
+ * Matching is unanchored and, by default, case-insensitive, because that is
39
+ * what `~=` has always been - `new RegExp(source, 'i')` and `.test()`. Globs
40
+ * compile to this as well, and a path on a case-sensitive filesystem needs the
41
+ * case respected, which is the one option. There are no capture groups at all:
42
+ * nothing asks where the match was, only whether there was one, which is why
43
+ * this is a plain NFA simulation rather than a Pike VM.
42
44
  *
43
45
  * See ADR-0017.
44
46
  */
@@ -48,17 +50,27 @@ export declare class PatternError extends Error {
48
50
  readonly offset: number;
49
51
  constructor(message: string, offset: number);
50
52
  }
51
- /** A compiled pattern. Immutable, reusable, and safe to cache. */
53
+ /**
54
+ * A compiled pattern. Reusable and safe to cache.
55
+ *
56
+ * Not reentrant, which nothing can ask of it: `test` is synchronous and calls
57
+ * out to nothing, and the buffers it keeps between subjects are what make it
58
+ * cheap enough to run against every path a walk visits.
59
+ */
52
60
  export interface Matcher {
53
61
  /** True when the pattern matches anywhere in the subject. */
54
62
  test(subject: string): boolean;
55
63
  /** State count. Published for the size test, and for nothing else. */
56
64
  readonly size: number;
57
65
  }
66
+ export interface PatternOptions {
67
+ /** Fold case the way the `i` flag does. On unless set to `false`. */
68
+ readonly ignoreCase?: boolean | undefined;
69
+ }
58
70
  /**
59
71
  * Compiles a pattern into a matcher.
60
72
  *
61
73
  * Throws {@link PatternError} on anything it cannot run in linear time, which
62
74
  * is the point: the alternative to a message here is a build that stops.
63
75
  */
64
- export declare function compilePattern(source: string): Matcher;
76
+ export declare function compilePattern(source: string, options?: PatternOptions): Matcher;