okf-kit 0.8.0 → 0.10.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/CHANGELOG.md +291 -0
- package/README.md +85 -11
- package/dist/bundle.d.ts +18 -1
- package/dist/bundle.js +8 -1
- package/dist/bundle.js.map +1 -1
- package/dist/cli.d.ts +21 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/git.d.ts +2 -2
- package/dist/git.js +16 -2
- package/dist/git.js.map +1 -1
- package/dist/rules/citations-resolve.d.ts +105 -0
- package/dist/rules/citations-resolve.js +154 -17
- package/dist/rules/citations-resolve.js.map +1 -1
- package/dist/rules/index.d.ts +3 -2
- package/dist/rules/index.js +15 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/prose-line-references.d.ts +2 -0
- package/dist/rules/prose-line-references.js +512 -0
- package/dist/rules/prose-line-references.js.map +1 -0
- package/dist/rules/sources-fresh.d.ts +35 -0
- package/dist/rules/sources-fresh.js +398 -18
- package/dist/rules/sources-fresh.js.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/util.d.ts +52 -0
- package/dist/util.js +72 -0
- package/dist/util.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,14 @@ export function runCheck(bundleDir, options = {}) {
|
|
|
28
28
|
if (options.requireAnchors) {
|
|
29
29
|
ctx.requireAnchors = { allow: options.requireAnchorsAllow ?? [] };
|
|
30
30
|
}
|
|
31
|
+
if (options.proseLineReferences) {
|
|
32
|
+
ctx.proseLineReferences = {
|
|
33
|
+
strict: Boolean(options.proseLineReferencesStrict),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (options.futureSkewMinutes !== undefined) {
|
|
37
|
+
ctx.freshnessFutureSkewSeconds = Math.round(options.futureSkewMinutes * 60);
|
|
38
|
+
}
|
|
31
39
|
const findings = allRules.flatMap((rule) => rule.run(ctx));
|
|
32
40
|
const summary = summarize(findings);
|
|
33
41
|
const exitCode = summary.errors > 0 || (Boolean(options.strict) && summary.warnings > 0)
|
|
@@ -58,14 +66,35 @@ program
|
|
|
58
66
|
"a string anchor lands uniquely on the last line of its range (opt-in, see README)")
|
|
59
67
|
.option("--require-anchors-allow <patterns...>", "citations-resolve: citedPath glob/exact patterns (e.g. README.md) exempt from " +
|
|
60
68
|
"--require-anchors' anchor-required check")
|
|
69
|
+
.option("--prose-line-references", "prose-line-references: flag a drifted, unresolvable, or ambiguous prose-embedded line " +
|
|
70
|
+
'reference outside citations-resolve\'s own backtick grammar, e.g. "lines 129-132" (opt-in, see README)')
|
|
71
|
+
.option("--prose-line-references-strict", "prose-line-references: also flag every prose line reference, not only a drifted one, with " +
|
|
72
|
+
"the remedy to lift it into a backtick citation or a symbol name (ignored unless --prose-line-references is also passed)")
|
|
73
|
+
.option("--future-skew-minutes <n>", "sources-fresh-future: clock-skew allowance in minutes before a doc `timestamp` later than " +
|
|
74
|
+
"the doc's own last commit is flagged as future-dated (default 10)")
|
|
61
75
|
.exitOverride()
|
|
62
76
|
.action((bundleDir, opts) => {
|
|
63
77
|
try {
|
|
78
|
+
let futureSkewMinutes;
|
|
79
|
+
if (opts.futureSkewMinutes !== undefined) {
|
|
80
|
+
const trimmed = opts.futureSkewMinutes.trim();
|
|
81
|
+
// `Number("")` and `Number(" ")` both resolve to 0, which would
|
|
82
|
+
// otherwise silently accept an empty/whitespace-only value as
|
|
83
|
+
// "0 minutes" instead of rejecting it as the usage error it is.
|
|
84
|
+
const n = trimmed === "" ? Number.NaN : Number(trimmed);
|
|
85
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
86
|
+
throw new UsageError(`--future-skew-minutes must be a non-negative number, got \`${opts.futureSkewMinutes}\``);
|
|
87
|
+
}
|
|
88
|
+
futureSkewMinutes = n;
|
|
89
|
+
}
|
|
64
90
|
const result = runCheck(bundleDir, {
|
|
65
91
|
repoRoot: opts.repoRoot,
|
|
66
92
|
strict: opts.strict,
|
|
67
93
|
requireAnchors: opts.requireAnchors,
|
|
68
94
|
requireAnchorsAllow: opts.requireAnchorsAllow,
|
|
95
|
+
proseLineReferences: opts.proseLineReferences,
|
|
96
|
+
proseLineReferencesStrict: opts.proseLineReferencesStrict,
|
|
97
|
+
futureSkewMinutes,
|
|
69
98
|
});
|
|
70
99
|
const output = opts.json
|
|
71
100
|
? renderJson(result.bundleDir, result.findings)
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGhE,OAAO,EAAE,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/git.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import type { RunGit } from "./types.js";
|
|
|
3
3
|
* Default RunGit implementation: shells out to the real `git` binary.
|
|
4
4
|
* stderr is discarded (git's own "fatal: not a git repository" etc. text is
|
|
5
5
|
* an expected, silent signal here, not something to surface), and any
|
|
6
|
-
* failure (non-zero exit, git missing
|
|
7
|
-
* throwing.
|
|
6
|
+
* failure (non-zero exit, git missing, output past MAX_GIT_OUTPUT_BYTES)
|
|
7
|
+
* resolves to null instead of throwing.
|
|
8
8
|
*/
|
|
9
9
|
export declare const runGit: RunGit;
|
|
10
10
|
/**
|
package/dist/git.js
CHANGED
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Output cap (bytes) for a single git invocation. Node's own default for
|
|
4
|
+
* `execFileSync` is 1 MiB, and exceeding it does not throw a distinguishable
|
|
5
|
+
* error here -- it lands in the catch below and resolves to null, i.e. the
|
|
6
|
+
* caller sees "git failed" for a perfectly healthy repository. `sources-fresh`
|
|
7
|
+
* reads whole doc blobs (`git show <sha>:<path>`) to compare frontmatter
|
|
8
|
+
* timestamps, and an OKF doc larger than 1 MiB is unusual but entirely legal,
|
|
9
|
+
* so the cap is raised well past any plausible doc size. It is deliberately
|
|
10
|
+
* still a cap and not `Infinity`: a runaway git invocation should fail loudly
|
|
11
|
+
* (as null, which every caller treats as "not assessable") rather than grow
|
|
12
|
+
* the process heap without bound.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_GIT_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
2
15
|
/**
|
|
3
16
|
* Default RunGit implementation: shells out to the real `git` binary.
|
|
4
17
|
* stderr is discarded (git's own "fatal: not a git repository" etc. text is
|
|
5
18
|
* an expected, silent signal here, not something to surface), and any
|
|
6
|
-
* failure (non-zero exit, git missing
|
|
7
|
-
* throwing.
|
|
19
|
+
* failure (non-zero exit, git missing, output past MAX_GIT_OUTPUT_BYTES)
|
|
20
|
+
* resolves to null instead of throwing.
|
|
8
21
|
*/
|
|
9
22
|
export const runGit = (args, cwd) => {
|
|
10
23
|
try {
|
|
@@ -12,6 +25,7 @@ export const runGit = (args, cwd) => {
|
|
|
12
25
|
cwd,
|
|
13
26
|
encoding: "utf8",
|
|
14
27
|
stdio: ["ignore", "pipe", "ignore"],
|
|
28
|
+
maxBuffer: MAX_GIT_OUTPUT_BYTES,
|
|
15
29
|
}).trim();
|
|
16
30
|
}
|
|
17
31
|
catch {
|
package/dist/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;YAC/B,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD;;;;;;;;;;;GAWG;AACH,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAW,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC1C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;YAC/B,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;YACnC,SAAS,EAAE,oBAAoB;SAChC,CAAC,CAAC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAgB,EAChB,MAAc,MAAM;IAEpB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACrC,CAAC"}
|
|
@@ -1,2 +1,107 @@
|
|
|
1
1
|
import type { Rule } from "../types.js";
|
|
2
|
+
export declare const CITATION_RE: RegExp;
|
|
3
|
+
/**
|
|
4
|
+
* Per-root basename index, see findByBasename. Exported so
|
|
5
|
+
* `prose-line-references` can build its own cache instance for
|
|
6
|
+
* `resolveCitation` calls, matching this rule's own scoping (fresh per
|
|
7
|
+
* `run(ctx)` invocation, never held at module scope).
|
|
8
|
+
*/
|
|
9
|
+
export type BasenameCache = Map<string, Map<string, string[]>>;
|
|
10
|
+
export type Resolution = {
|
|
11
|
+
skip: true;
|
|
12
|
+
} | {
|
|
13
|
+
path: string;
|
|
14
|
+
} | {
|
|
15
|
+
ambiguous: true;
|
|
16
|
+
candidates: string[];
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* True when citedPath has a literal `..` path segment. Exported so
|
|
20
|
+
* `prose-line-references` rejects the same shape before ever calling
|
|
21
|
+
* `resolveCitation` on a file-mention token, matching this rule's own
|
|
22
|
+
* `path-traversal-rejected` posture.
|
|
23
|
+
*/
|
|
24
|
+
export declare function hasParentSegment(citedPath: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Resolves a citation's path to a single real file. Returns `{ skip: true }`
|
|
27
|
+
* for a citedPath out of scope (leading `/`), `{ path }` on a definitive
|
|
28
|
+
* single resolution, `{ ambiguous: true, candidates }` when more than one
|
|
29
|
+
* plausible target exists, or `null` when nothing matches. Callers must
|
|
30
|
+
* reject a citedPath with a `..` segment (see hasParentSegment) before
|
|
31
|
+
* calling this; it is not re-checked here.
|
|
32
|
+
*
|
|
33
|
+
* Exported so `prose-line-references` (see
|
|
34
|
+
* src/rules/prose-line-references.ts) reuses this exact resolution order
|
|
35
|
+
* for binding a bare prose line reference to the file mention nearest it,
|
|
36
|
+
* rather than re-implementing (and risking drifting from) this rule's own
|
|
37
|
+
* path-resolution rules.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveCitation(cache: BasenameCache, root: string, docAbsPath: string, docContent: string, docSources: string[], citedPath: string, matchIndex: number): Resolution | null;
|
|
40
|
+
/**
|
|
41
|
+
* Char spans of every fenced code block in `content` (```` ``` ```` or
|
|
42
|
+
* `~~~`, optionally with a trailing language tag), each span running from
|
|
43
|
+
* the start of the opening fence line to the end of the closing fence line
|
|
44
|
+
* inclusive. An unterminated fence (no matching close before end of doc) is
|
|
45
|
+
* treated as running to the end of the content -- conservative, since an
|
|
46
|
+
* unterminated fence is itself a doc problem outside this rule's scope, not
|
|
47
|
+
* a reason to scan its contents for short-form citations. Derived from
|
|
48
|
+
* `scanFenceLines` (see there): per-line fenced/opens/closes state is
|
|
49
|
+
* converted to char-offset spans by tracking each line's `[start, end)`
|
|
50
|
+
* offset in `content` alongside it.
|
|
51
|
+
*
|
|
52
|
+
* Exported (with computeIndentedCodeSpans and computeTableRowSpans below)
|
|
53
|
+
* so `prose-line-references` can build its OWN excluded-span set for file
|
|
54
|
+
* mentions that leaves out computeInlineCodeSpans -- a bare backtick-
|
|
55
|
+
* wrapped filename (`` `src/cli.ts` ``) is the normal, encouraged way to
|
|
56
|
+
* write a file mention in prose, unlike a short-form bare number, so it
|
|
57
|
+
* must NOT be excluded from mention detection the way it is excluded from
|
|
58
|
+
* short-form citation matching here.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeFencedSpans(content: string): Array<[number, number]>;
|
|
61
|
+
/**
|
|
62
|
+
* Char spans of every CommonMark-style indented code block in `content`: a
|
|
63
|
+
* maximal run of consecutive non-blank lines, each indented by at least
|
|
64
|
+
* four spaces or a leading tab, whose first line is preceded by a blank
|
|
65
|
+
* line or the start of the document (an indented code block cannot
|
|
66
|
+
* interrupt a paragraph). A blank line inside the run does not itself end
|
|
67
|
+
* it, matching CommonMark. Simplified relative to the full CommonMark
|
|
68
|
+
* spec (no list-item-context awareness); adequate for this mechanical,
|
|
69
|
+
* warn-only rule.
|
|
70
|
+
*/
|
|
71
|
+
export declare function computeIndentedCodeSpans(content: string): Array<[number, number]>;
|
|
72
|
+
/**
|
|
73
|
+
* Char spans of every Markdown table row in `content`: a line whose
|
|
74
|
+
* trimmed form starts and ends with `|`. Decision (documented in the
|
|
75
|
+
* README): a short-form citation inside a table cell is never recognised,
|
|
76
|
+
* the same way one inside a code span is not -- excluded here rather than
|
|
77
|
+
* left to the plausibility gate, since a table cell's content is prose-like
|
|
78
|
+
* and can otherwise carry a range shape the gate would not reject (e.g.
|
|
79
|
+
* `| col (5-9) |`).
|
|
80
|
+
*/
|
|
81
|
+
export declare function computeTableRowSpans(content: string): Array<[number, number]>;
|
|
82
|
+
/**
|
|
83
|
+
* All char spans short-form matching must never fire inside: fenced code,
|
|
84
|
+
* indented code, inline code spans, and Markdown table rows. Computed once
|
|
85
|
+
* per doc and combined with fullSpans (see scanDoc) via the existing
|
|
86
|
+
* isWithinAnySpan helper -- the same mechanism a full citation's own span
|
|
87
|
+
* already uses, not a second one.
|
|
88
|
+
*
|
|
89
|
+
* Exported so `prose-line-references` excludes the identical set of spans
|
|
90
|
+
* from its own extraction (a code-fenced or inline-code "line N" is a code
|
|
91
|
+
* example, not a live prose reference), rather than maintaining a second,
|
|
92
|
+
* possibly-drifting copy of "what counts as excluded prose".
|
|
93
|
+
*/
|
|
94
|
+
export declare function computeExcludedSpans(content: string): Array<[number, number]>;
|
|
95
|
+
/**
|
|
96
|
+
* Paragraph-start offsets in `content`, ascending, always including 0. A
|
|
97
|
+
* paragraph boundary is a blank (empty or whitespace-only) line.
|
|
98
|
+
*
|
|
99
|
+
* Exported (with paragraphStartFor below) so `prose-line-references` binds
|
|
100
|
+
* against the same notion of "paragraph" this rule already uses for
|
|
101
|
+
* short-form citation binding, instead of a second, possibly-inconsistent
|
|
102
|
+
* definition.
|
|
103
|
+
*/
|
|
104
|
+
export declare function computeParagraphStarts(content: string): number[];
|
|
105
|
+
/** The start offset of the paragraph containing `index` (see computeParagraphStarts). */
|
|
106
|
+
export declare function paragraphStartFor(starts: number[], index: number): number;
|
|
2
107
|
export declare const citationsResolveRule: Rule;
|
|
@@ -101,8 +101,8 @@ const RULE_ID = "citations-resolve";
|
|
|
101
101
|
* citation, prose habitually repeats just the line (or range) for a later
|
|
102
102
|
* reference in the same sentence rather than retyping the path, in three
|
|
103
103
|
* forms:
|
|
104
|
-
* - `` `:N`
|
|
105
|
-
* - `` -`M`
|
|
104
|
+
* - `` `:N` ``/`` `:N-M` `` -- a bare colon-prefixed line/range
|
|
105
|
+
* - `` -`M` ``/`` –`M` `` -- a hyphen- or en-dash-led bare line, the
|
|
106
106
|
* tail half of a `` `path:N`-`M` `` split range
|
|
107
107
|
* - `` (`N`) `` -- a parenthesized bare line
|
|
108
108
|
* Each of these resolves against `governing`: the nearest *preceding*
|
|
@@ -198,6 +198,11 @@ const RULE_ID = "citations-resolve";
|
|
|
198
198
|
* nowhere natural to hang an anchor on one without inventing a second,
|
|
199
199
|
* detached syntax; the migration this rule was built for (CHANGELOG.md
|
|
200
200
|
* citations) is written as full citations throughout the corpus it targets.
|
|
201
|
+
* Under `--require-anchors`, this remains true (no second anchor syntax is
|
|
202
|
+
* added), but a continuation or short-form that chains off an in-repo full
|
|
203
|
+
* citation is instead flagged, `anchor-required-continuation`, so it does
|
|
204
|
+
* not drift silently -- see the "Anchor strictness (opt-in)" doc block
|
|
205
|
+
* below for that check.
|
|
201
206
|
*
|
|
202
207
|
* Rejected alternatives (see the PR/CHANGELOG for the fuller writeup):
|
|
203
208
|
* - Embedding the literal heading markup itself, e.g.
|
|
@@ -291,6 +296,30 @@ const RULE_ID = "citations-resolve";
|
|
|
291
296
|
* `checkShortFormTarget`'s own doc comment already gives for the Markdown
|
|
292
297
|
* half of the block-boundary check: a full citation into a Markdown target
|
|
293
298
|
* legitimately cites a couple of arbitrary lines all the time.
|
|
299
|
+
*
|
|
300
|
+
* A fifth check, `anchor-required-continuation` (warning), closes a gap the
|
|
301
|
+
* four checks above leave open: every one of them is gated on reaching a
|
|
302
|
+
* "full" atom, so a continuation (` :N`/`:N-M` `, `` -`M` ``/`` –`M` ``,
|
|
303
|
+
* `` (`N`) ``) or a paragraph-bound short-form `:N-M` chained off an
|
|
304
|
+
* ALREADY-ANCHORED full citation was previously invisible to
|
|
305
|
+
* `--require-anchors` entirely -- it carries no path of its own
|
|
306
|
+
* (see the "Continuation citations" / "Short-form citations" doc blocks),
|
|
307
|
+
* so it was never itself an in-repo full citation `anchor-required` could
|
|
308
|
+
* reach, and the anchor on the governing citation it chains off does not
|
|
309
|
+
* (and structurally cannot) cover it: a line-shift that only affects the
|
|
310
|
+
* continuation's own start/end line is exactly the drift this rule's base
|
|
311
|
+
* checks (blank-start-line and friends) already catch when it lands on
|
|
312
|
+
* unusable content, but a shift that lands the continuation on still
|
|
313
|
+
* non-blank, in-bounds, unrelated content is invisible to any of them --
|
|
314
|
+
* see `pushAnchorRequiredContinuation` for where this is pushed, from all
|
|
315
|
+
* three places a continuation or short-form atom is processed in `scanDoc`.
|
|
316
|
+
* Unlike `anchor-required`, there is no "already carries one" escape here:
|
|
317
|
+
* a continuation is structurally anchor-less regardless of whether its
|
|
318
|
+
* governing full citation has an anchor, so this fires unconditionally
|
|
319
|
+
* once the same exemptions (reserved citing doc, `requireAnchors.allow`
|
|
320
|
+
* matched against the GOVERNING citation's citedPath) clear. The remedy is
|
|
321
|
+
* always the same: lift the continuation into its own full,
|
|
322
|
+
* `path:N-M#anchor` citation.
|
|
294
323
|
*/
|
|
295
324
|
/**
|
|
296
325
|
* True when `citedPath` (the citation's raw, as-written path text) matches
|
|
@@ -523,7 +552,10 @@ function checkAnchor(anchor, startLine, endLine, lines, requireAnchors) {
|
|
|
523
552
|
}
|
|
524
553
|
return null;
|
|
525
554
|
}
|
|
526
|
-
|
|
555
|
+
// Exported so `prose-line-references` (see src/rules/prose-line-references.ts)
|
|
556
|
+
// can exclude a real citations-resolve citation span from its own
|
|
557
|
+
// extraction, rather than re-deriving this grammar itself.
|
|
558
|
+
export const CITATION_RE = /([\w./-]+\.(?:ts|js|mjs|md|yml|yaml|json)):(\d+)(?:-(\d+))?(?:#(\[?\w(?:[\w.-]*\w)?\]?|"[^"\n`]*"))?/g;
|
|
527
559
|
/**
|
|
528
560
|
* Heading-section citations. A CHANGELOG.md that grows by insertion at the
|
|
529
561
|
* top forces every later `path:N-M#anchor` citation to be re-pointed on
|
|
@@ -666,8 +698,13 @@ function isFile(p) {
|
|
|
666
698
|
return false;
|
|
667
699
|
}
|
|
668
700
|
}
|
|
669
|
-
/**
|
|
670
|
-
|
|
701
|
+
/**
|
|
702
|
+
* True when citedPath has a literal `..` path segment. Exported so
|
|
703
|
+
* `prose-line-references` rejects the same shape before ever calling
|
|
704
|
+
* `resolveCitation` on a file-mention token, matching this rule's own
|
|
705
|
+
* `path-traversal-rejected` posture.
|
|
706
|
+
*/
|
|
707
|
+
export function hasParentSegment(citedPath) {
|
|
671
708
|
return citedPath.split("/").includes("..");
|
|
672
709
|
}
|
|
673
710
|
/**
|
|
@@ -770,8 +807,14 @@ function resolveViaAncestorClimb(root, docAbsPath, citedPath) {
|
|
|
770
807
|
* plausible target exists, or `null` when nothing matches. Callers must
|
|
771
808
|
* reject a citedPath with a `..` segment (see hasParentSegment) before
|
|
772
809
|
* calling this; it is not re-checked here.
|
|
810
|
+
*
|
|
811
|
+
* Exported so `prose-line-references` (see
|
|
812
|
+
* src/rules/prose-line-references.ts) reuses this exact resolution order
|
|
813
|
+
* for binding a bare prose line reference to the file mention nearest it,
|
|
814
|
+
* rather than re-implementing (and risking drifting from) this rule's own
|
|
815
|
+
* path-resolution rules.
|
|
773
816
|
*/
|
|
774
|
-
function resolveCitation(cache, root, docAbsPath, docContent, docSources, citedPath, matchIndex) {
|
|
817
|
+
export function resolveCitation(cache, root, docAbsPath, docContent, docSources, citedPath, matchIndex) {
|
|
775
818
|
if (citedPath.startsWith("/")) {
|
|
776
819
|
return { skip: true };
|
|
777
820
|
}
|
|
@@ -1368,6 +1411,7 @@ function collectContinuationAtoms(content) {
|
|
|
1368
1411
|
kind: "cont-ext",
|
|
1369
1412
|
index: m.index,
|
|
1370
1413
|
value: m[2] ? Number(m[2]) : Number(m[1]),
|
|
1414
|
+
raw: m[0],
|
|
1371
1415
|
});
|
|
1372
1416
|
}
|
|
1373
1417
|
else {
|
|
@@ -1376,12 +1420,18 @@ function collectContinuationAtoms(content) {
|
|
|
1376
1420
|
index: m.index,
|
|
1377
1421
|
startLine: Number(m[1]),
|
|
1378
1422
|
endLine: m[2] ? Number(m[2]) : null,
|
|
1423
|
+
raw: m[0],
|
|
1379
1424
|
});
|
|
1380
1425
|
}
|
|
1381
1426
|
}
|
|
1382
1427
|
const dashRe = new RegExp(CONT_DASH_RE.source, "g");
|
|
1383
1428
|
while ((m = dashRe.exec(content)) !== null) {
|
|
1384
|
-
atoms.push({
|
|
1429
|
+
atoms.push({
|
|
1430
|
+
kind: "cont-ext",
|
|
1431
|
+
index: m.index,
|
|
1432
|
+
value: Number(m[1]),
|
|
1433
|
+
raw: m[0],
|
|
1434
|
+
});
|
|
1385
1435
|
}
|
|
1386
1436
|
const parenRe = new RegExp(CONT_PAREN_RE.source, "g");
|
|
1387
1437
|
while ((m = parenRe.exec(content)) !== null) {
|
|
@@ -1390,6 +1440,7 @@ function collectContinuationAtoms(content) {
|
|
|
1390
1440
|
index: m.index,
|
|
1391
1441
|
startLine: Number(m[1]),
|
|
1392
1442
|
endLine: null,
|
|
1443
|
+
raw: m[0],
|
|
1393
1444
|
});
|
|
1394
1445
|
}
|
|
1395
1446
|
return atoms;
|
|
@@ -1443,8 +1494,16 @@ function collectShortFormMatches(content, excludedSpans) {
|
|
|
1443
1494
|
* `scanFenceLines` (see there): per-line fenced/opens/closes state is
|
|
1444
1495
|
* converted to char-offset spans by tracking each line's `[start, end)`
|
|
1445
1496
|
* offset in `content` alongside it.
|
|
1497
|
+
*
|
|
1498
|
+
* Exported (with computeIndentedCodeSpans and computeTableRowSpans below)
|
|
1499
|
+
* so `prose-line-references` can build its OWN excluded-span set for file
|
|
1500
|
+
* mentions that leaves out computeInlineCodeSpans -- a bare backtick-
|
|
1501
|
+
* wrapped filename (`` `src/cli.ts` ``) is the normal, encouraged way to
|
|
1502
|
+
* write a file mention in prose, unlike a short-form bare number, so it
|
|
1503
|
+
* must NOT be excluded from mention detection the way it is excluded from
|
|
1504
|
+
* short-form citation matching here.
|
|
1446
1505
|
*/
|
|
1447
|
-
function computeFencedSpans(content) {
|
|
1506
|
+
export function computeFencedSpans(content) {
|
|
1448
1507
|
const spans = [];
|
|
1449
1508
|
const lines = content.split("\n");
|
|
1450
1509
|
const states = scanFenceLines(lines);
|
|
@@ -1475,7 +1534,7 @@ function computeFencedSpans(content) {
|
|
|
1475
1534
|
* spec (no list-item-context awareness); adequate for this mechanical,
|
|
1476
1535
|
* warn-only rule.
|
|
1477
1536
|
*/
|
|
1478
|
-
function computeIndentedCodeSpans(content) {
|
|
1537
|
+
export function computeIndentedCodeSpans(content) {
|
|
1479
1538
|
const spans = [];
|
|
1480
1539
|
const lines = content.split("\n");
|
|
1481
1540
|
let offset = 0;
|
|
@@ -1546,7 +1605,7 @@ function computeInlineCodeSpans(content) {
|
|
|
1546
1605
|
* and can otherwise carry a range shape the gate would not reject (e.g.
|
|
1547
1606
|
* `| col (5-9) |`).
|
|
1548
1607
|
*/
|
|
1549
|
-
function computeTableRowSpans(content) {
|
|
1608
|
+
export function computeTableRowSpans(content) {
|
|
1550
1609
|
const spans = [];
|
|
1551
1610
|
const lines = content.split("\n");
|
|
1552
1611
|
let offset = 0;
|
|
@@ -1567,8 +1626,13 @@ function computeTableRowSpans(content) {
|
|
|
1567
1626
|
* per doc and combined with fullSpans (see scanDoc) via the existing
|
|
1568
1627
|
* isWithinAnySpan helper -- the same mechanism a full citation's own span
|
|
1569
1628
|
* already uses, not a second one.
|
|
1629
|
+
*
|
|
1630
|
+
* Exported so `prose-line-references` excludes the identical set of spans
|
|
1631
|
+
* from its own extraction (a code-fenced or inline-code "line N" is a code
|
|
1632
|
+
* example, not a live prose reference), rather than maintaining a second,
|
|
1633
|
+
* possibly-drifting copy of "what counts as excluded prose".
|
|
1570
1634
|
*/
|
|
1571
|
-
function computeExcludedSpans(content) {
|
|
1635
|
+
export function computeExcludedSpans(content) {
|
|
1572
1636
|
return [
|
|
1573
1637
|
...computeFencedSpans(content),
|
|
1574
1638
|
...computeIndentedCodeSpans(content),
|
|
@@ -1579,8 +1643,13 @@ function computeExcludedSpans(content) {
|
|
|
1579
1643
|
/**
|
|
1580
1644
|
* Paragraph-start offsets in `content`, ascending, always including 0. A
|
|
1581
1645
|
* paragraph boundary is a blank (empty or whitespace-only) line.
|
|
1646
|
+
*
|
|
1647
|
+
* Exported (with paragraphStartFor below) so `prose-line-references` binds
|
|
1648
|
+
* against the same notion of "paragraph" this rule already uses for
|
|
1649
|
+
* short-form citation binding, instead of a second, possibly-inconsistent
|
|
1650
|
+
* definition.
|
|
1582
1651
|
*/
|
|
1583
|
-
function computeParagraphStarts(content) {
|
|
1652
|
+
export function computeParagraphStarts(content) {
|
|
1584
1653
|
const starts = [0];
|
|
1585
1654
|
const re = /\n[ \t]*\n+/g;
|
|
1586
1655
|
let m;
|
|
@@ -1590,7 +1659,7 @@ function computeParagraphStarts(content) {
|
|
|
1590
1659
|
return starts;
|
|
1591
1660
|
}
|
|
1592
1661
|
/** The start offset of the paragraph containing `index` (see computeParagraphStarts). */
|
|
1593
|
-
function paragraphStartFor(starts, index) {
|
|
1662
|
+
export function paragraphStartFor(starts, index) {
|
|
1594
1663
|
let result = starts[0];
|
|
1595
1664
|
for (const s of starts) {
|
|
1596
1665
|
if (s > index)
|
|
@@ -1641,6 +1710,40 @@ function pushUnreadable(findings, file, citation, resolvedTo, code) {
|
|
|
1641
1710
|
detail: `resolvedTo: ${resolvedTo}, errorCode: ${code}`,
|
|
1642
1711
|
});
|
|
1643
1712
|
}
|
|
1713
|
+
/**
|
|
1714
|
+
* `anchor-required-continuation` (opt-in, `--require-anchors`, warning):
|
|
1715
|
+
* see the "Anchor strictness (opt-in)" doc block above. A continuation
|
|
1716
|
+
* atom (cont-fresh or cont-ext, any of the three backtick forms, or a
|
|
1717
|
+
* paragraph-bound short-form `:N-M`) is structurally anchor-less by
|
|
1718
|
+
* construction -- see the "Continuation citations" doc block -- so
|
|
1719
|
+
* unlike `anchor-required` there is no "already carries one" escape: this
|
|
1720
|
+
* fires once its governing citation resolved in-repo, REGARDLESS of
|
|
1721
|
+
* whether that governing full citation itself carries an anchor (an
|
|
1722
|
+
* anchor on the full citation does not extend to a later continuation of
|
|
1723
|
+
* it). Exemptions mirror `anchor-required`'s exactly: the caller already
|
|
1724
|
+
* folds the reserved-citing-doc carve-out into `requireAnchorsForDoc`
|
|
1725
|
+
* (undefined for a reserved doc), and `requireAnchors.allow` is matched
|
|
1726
|
+
* here against `governing.citedPath` -- the path the continuation is
|
|
1727
|
+
* chained to, since a continuation carries no path of its own to match
|
|
1728
|
+
* against.
|
|
1729
|
+
*/
|
|
1730
|
+
function pushAnchorRequiredContinuation(findings, file, citation, raw, governing, requireAnchorsForDoc, root) {
|
|
1731
|
+
if (!requireAnchorsForDoc ||
|
|
1732
|
+
matchesAllowPattern(governing.citedPath, requireAnchorsForDoc.allow)) {
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
const governingRange = `${governing.citedPath}:${governing.startLine}${governing.endLine ? "-" + governing.endLine : ""}`;
|
|
1736
|
+
// "chained to" rather than "extends": for a cont-ext that itself chains
|
|
1737
|
+
// off an earlier cont-fresh (not the original full citation), the range
|
|
1738
|
+
// it literally extends is the cont-fresh's own range, not `governing`'s
|
|
1739
|
+
// -- `governing` (see the loop above) always carries the ORIGINAL full
|
|
1740
|
+
// citation's own startLine/endLine, unchanged across intervening
|
|
1741
|
+
// cont-fresh atoms. Naming it "the governing citation" is honest for
|
|
1742
|
+
// every caller (cont-ext, cont-fresh, and the short-form call site
|
|
1743
|
+
// below) without having to thread the true immediate antecedent range
|
|
1744
|
+
// through here.
|
|
1745
|
+
pushDrift(findings, file, citation, "anchor-required-continuation", `continuation ${raw} is chained to the governing citation \`${governingRange}\`; a continuation cannot carry its own #anchor, lift it into a full \`path:N-M#anchor\` citation (--require-anchors is on)`, path.relative(root, governing.resolvedPath));
|
|
1746
|
+
}
|
|
1644
1747
|
/**
|
|
1645
1748
|
* True when a `CITATION_RE` match at `matchIndex` is the phantom tail of a
|
|
1646
1749
|
* filename hard-wrapped across a line break (see the "Hard-wrapped prose"
|
|
@@ -1771,9 +1874,13 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1771
1874
|
const atoms = [...fullAtoms, ...collectContinuationAtoms(content)].sort((a, b) => a.index - b.index);
|
|
1772
1875
|
// `governing`: nearest preceding citation (full or continuation) that
|
|
1773
1876
|
// resolved to a real file; see the "Continuation citations" doc block
|
|
1774
|
-
// above for the reset rules.
|
|
1775
|
-
//
|
|
1776
|
-
//
|
|
1877
|
+
// above for the reset rules. Carries the ORIGINAL full citation's own
|
|
1878
|
+
// startLine/endLine (not the extended range a later cont-ext might grow
|
|
1879
|
+
// into) so `anchor-required-continuation` (see `pushAnchorRequiredContinuation`
|
|
1880
|
+
// above) can name "the governing citation" the same way it was actually
|
|
1881
|
+
// written. `lastStartLine`: the start line a "cont-ext" atom extends into
|
|
1882
|
+
// a range; tracks the most recent full or cont-fresh atom's own
|
|
1883
|
+
// startLine, scoped together with `governing`.
|
|
1777
1884
|
let governing = null;
|
|
1778
1885
|
let lastStartLine = null;
|
|
1779
1886
|
for (const atom of atoms) {
|
|
@@ -1781,6 +1888,7 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1781
1888
|
if (!governing || lastStartLine === null)
|
|
1782
1889
|
continue; // nothing to extend
|
|
1783
1890
|
const citation = `${governing.citedPath}:${lastStartLine}-${atom.value} (continuation)`;
|
|
1891
|
+
pushAnchorRequiredContinuation(findings, doc.relPath, citation, atom.raw, governing, requireAnchorsForDoc, root);
|
|
1784
1892
|
const problem = checkRangeBoundOnly(lastStartLine, atom.value, governing.resolvedPath);
|
|
1785
1893
|
if (problem?.rule === "unreadable-target") {
|
|
1786
1894
|
pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
|
|
@@ -1795,6 +1903,7 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1795
1903
|
continue; // nothing to validate a bare continuation against
|
|
1796
1904
|
const { startLine, endLine } = atom;
|
|
1797
1905
|
const citation = `${governing.citedPath}:${startLine}${endLine ? "-" + endLine : ""} (continuation)`;
|
|
1906
|
+
pushAnchorRequiredContinuation(findings, doc.relPath, citation, atom.raw, governing, requireAnchorsForDoc, root);
|
|
1798
1907
|
const problem = checkTarget(governing.citedPath, startLine, endLine, governing.resolvedPath);
|
|
1799
1908
|
if (problem?.rule === "unreadable-target") {
|
|
1800
1909
|
pushUnreadable(findings, doc.relPath, citation, path.relative(root, governing.resolvedPath), problem.code ?? "UNKNOWN");
|
|
@@ -1869,7 +1978,12 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1869
1978
|
pushDrift(findings, doc.relPath, citation, problem.rule, problem.message, path.relative(root, resolution.path));
|
|
1870
1979
|
}
|
|
1871
1980
|
}
|
|
1872
|
-
governing = {
|
|
1981
|
+
governing = {
|
|
1982
|
+
citedPath,
|
|
1983
|
+
resolvedPath: resolution.path,
|
|
1984
|
+
startLine,
|
|
1985
|
+
endLine,
|
|
1986
|
+
};
|
|
1873
1987
|
lastStartLine = startLine;
|
|
1874
1988
|
}
|
|
1875
1989
|
// Heading-section citations -- see the "Heading-section citations" doc
|
|
@@ -1945,6 +2059,8 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1945
2059
|
namedFullAtoms.push({
|
|
1946
2060
|
index: a.index,
|
|
1947
2061
|
citedPath: a.citedPath,
|
|
2062
|
+
startLine: a.startLine,
|
|
2063
|
+
endLine: a.endLine,
|
|
1948
2064
|
});
|
|
1949
2065
|
}
|
|
1950
2066
|
}
|
|
@@ -1974,6 +2090,27 @@ function scanDoc(cache, root, bundleDir, doc, requireAnchors) {
|
|
|
1974
2090
|
pushAmbiguous(findings, doc.relPath, citation, resolution.candidates);
|
|
1975
2091
|
continue;
|
|
1976
2092
|
}
|
|
2093
|
+
// anchor-required-continuation (opt-in, warning) also applies to a
|
|
2094
|
+
// bound short-form citation: it is just as anchor-less as a
|
|
2095
|
+
// backtick continuation (see the "Short-form citations" doc block
|
|
2096
|
+
// above), for the identical reason -- a bound short form cannot
|
|
2097
|
+
// carry an anchor either, so it is flagged as well, rather than
|
|
2098
|
+
// left as a silent gap in `--require-anchors`.
|
|
2099
|
+
//
|
|
2100
|
+
// `raw` here is synthesized from the parsed startLine/endLine
|
|
2101
|
+
// (`:${rangeLabel}`) rather than carried from the match itself --
|
|
2102
|
+
// `ShortFormMatch` (see there) does not retain the matched text.
|
|
2103
|
+
// This is exact for `SHORT_FORM_COLON_RE` (`/:(\d+)-(\d+)/`) as
|
|
2104
|
+
// written today, since re-stringifying the two captured numbers
|
|
2105
|
+
// reproduces the source bytes; it would stop being exact if that
|
|
2106
|
+
// regex ever tolerated something re-stringifying can't round-trip
|
|
2107
|
+
// (e.g. a leading zero).
|
|
2108
|
+
pushAnchorRequiredContinuation(findings, doc.relPath, citation, `:${rangeLabel}`, {
|
|
2109
|
+
citedPath: targetPath,
|
|
2110
|
+
resolvedPath: resolution.path,
|
|
2111
|
+
startLine: target.startLine,
|
|
2112
|
+
endLine: target.endLine,
|
|
2113
|
+
}, requireAnchorsForDoc, root);
|
|
1977
2114
|
const problem = checkShortFormTarget(targetPath, sf.startLine, sf.endLine, resolution.path);
|
|
1978
2115
|
if (problem?.rule === "unreadable-target") {
|
|
1979
2116
|
pushUnreadable(findings, doc.relPath, citation, path.relative(root, resolution.path), problem.code ?? "UNKNOWN");
|