@codefast/cli 0.5.0 → 0.6.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.
Files changed (52) hide show
  1. package/README.md +56 -13
  2. package/dist/arrange/domain/ast/helpers.js +1 -1
  3. package/dist/arrange/domain/ast/simplify-targets.js +4 -8
  4. package/dist/arrange/domain/ast/simplify-targets.js.map +1 -1
  5. package/dist/arrange/domain/constants.js +1 -1
  6. package/dist/arrange/domain/grouping.js +2 -2
  7. package/dist/arrange/domain/imports.js +1 -1
  8. package/dist/arrange/domain/tailwind-token.js +6 -4
  9. package/dist/arrange/domain/tailwind-token.js.map +1 -1
  10. package/dist/arrange/domain/token-classifier.js +12 -12
  11. package/dist/arrange/domain/token-classifier.js.map +1 -1
  12. package/dist/arrange/domain/types.js +1 -1
  13. package/dist/audit/cli-schema.js +24 -1
  14. package/dist/audit/cli-schema.js.map +1 -1
  15. package/dist/audit/command.js +89 -5
  16. package/dist/audit/command.js.map +1 -1
  17. package/dist/audit/domain/audit-file.js +1 -1
  18. package/dist/audit/domain/comment-content.js +178 -0
  19. package/dist/audit/domain/comment-content.js.map +1 -0
  20. package/dist/audit/domain/comment-dividers.js +146 -0
  21. package/dist/audit/domain/comment-dividers.js.map +1 -0
  22. package/dist/audit/domain/link-references.js +64 -0
  23. package/dist/audit/domain/link-references.js.map +1 -0
  24. package/dist/audit/domain/markdown-links.js +76 -0
  25. package/dist/audit/domain/markdown-links.js.map +1 -0
  26. package/dist/audit/domain/tokenize.js +2 -2
  27. package/dist/audit/domain/tsdoc-syntax.js +33 -0
  28. package/dist/audit/domain/tsdoc-syntax.js.map +1 -0
  29. package/dist/audit/output.js +88 -0
  30. package/dist/audit/output.js.map +1 -1
  31. package/dist/audit/prepare.js +63 -1
  32. package/dist/audit/prepare.js.map +1 -1
  33. package/dist/audit/run-comments.js +165 -0
  34. package/dist/audit/run-comments.js.map +1 -0
  35. package/dist/audit/run-links.js +92 -0
  36. package/dist/audit/run-links.js.map +1 -0
  37. package/dist/audit/run.js +1 -1
  38. package/dist/core/config/schema.js +24 -0
  39. package/dist/core/config/schema.js.map +1 -1
  40. package/dist/core/workspace/markdown-walk.js +28 -0
  41. package/dist/core/workspace/markdown-walk.js.map +1 -0
  42. package/dist/core/workspace/resolver.js +1 -1
  43. package/dist/core/workspace/skip-directories.js +19 -0
  44. package/dist/core/workspace/skip-directories.js.map +1 -0
  45. package/dist/core/workspace/source-walk.js +45 -0
  46. package/dist/core/workspace/source-walk.js.map +1 -0
  47. package/dist/core/workspace/typescript-walk.js +1 -13
  48. package/dist/core/workspace/typescript-walk.js.map +1 -1
  49. package/dist/mirror/domain/exports.js +1 -1
  50. package/dist/mirror/supplement-exports.js +1 -1
  51. package/dist/mirror/write-exports.js +1 -1
  52. package/package.json +6 -5
@@ -20,7 +20,7 @@ export function hasRtlCompanion(fileTokens, expectedValue) {
20
20
  });
21
21
  }
22
22
  /**
23
- * Detect physical-direction Tailwind classes that should be logical or rtl:-paired.
23
+ * Detects physical-direction Tailwind classes that should be logical or rtl:-paired.
24
24
  *
25
25
  * @since 1.0.0-canary.7
26
26
  */
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Recognises comment content the repo bans outright: repo-document pointers and JSDoc type syntax.
3
+ */
4
+ // "see <anything>.md" — a pointer at a repo document. A bare .md mention is allowed: code that
5
+ // generates a markdown file legitimately names its own output.
6
+ const docPointerPattern = /\bsee\b[^\n]*?[\w./-]+\.md\b/i;
7
+ // The classic JSDoc `{type}` payload TSDoc drops — TS already declares the type.
8
+ const jsdocTypePattern = /@(?:param|returns?|type|prop(?:erty)?)\s*\{/;
9
+ // TSDoc separates the name from its description with a hyphen.
10
+ const paramNoHyphenPattern = /^\s*\*\s*@(?:param|typeParam)\s+[\w.$[\]]+\s+(?!-\s)\S/;
11
+ const blockTagPattern = /^\s*\*\s*@[a-z]/i;
12
+ const sinceTagPattern = /^\s*\*\s*@since\b/;
13
+ const lineCommentPattern = /^[ \t]*\/\//;
14
+ const blockLinePattern = /^[ \t]*(?:\/\*|\*)/;
15
+ const docBlockClosePattern = /^[ \t]*(?:\*.*)?\*\/[ \t]*$/;
16
+ const paramTagPattern = /^\s*\*\s*@param\s+([\w.$]+)/;
17
+ const declarationPattern = /^[ \t]*(?:export|const|let|var|function|class|interface|type|enum|async|declare)\b/;
18
+ // A divider or a tooling directive above a doc block is not a stacked note.
19
+ const dividerLinePattern = /^[ \t]*\/\/[ \t]*[-=─_*~#]{2,}/;
20
+ const directiveLinePattern = /^[ \t]*\/\/[ \t]*(?:oxlint-|eslint-|@ts-|prettier-)/;
21
+ /**
22
+ * Scans a source file's comments for banned content, in source order.
23
+ *
24
+ * @since 0.6.0
25
+ */
26
+ export function scanCommentContent(content, language) {
27
+ const findings = [];
28
+ const lines = content.split(/\r?\n/);
29
+ let insideBlock = false;
30
+ let pendingSinceLine = 0;
31
+ let documentedParams = [];
32
+ for (let index = 0; index < lines.length; index++) {
33
+ const line = lines[index];
34
+ const trimmed = line.trim();
35
+ // Only a line-leading `/*` opens a block — a marker inside code or a string literal does not.
36
+ const opensBlock = trimmed.startsWith("/*") && !trimmed.includes("*/");
37
+ // A `//` run stacked directly above a doc block reads as a second doc — it belongs inside.
38
+ if (language === "js" && !insideBlock && trimmed.startsWith("/**")) {
39
+ let runStart = index;
40
+ while (runStart > 0 &&
41
+ lineCommentPattern.test(lines[runStart - 1]) &&
42
+ !dividerLinePattern.test(lines[runStart - 1]) &&
43
+ !directiveLinePattern.test(lines[runStart - 1])) {
44
+ runStart--;
45
+ }
46
+ if (runStart < index) {
47
+ findings.push({ line: runStart + 1, raw: lines[runStart].trim().slice(0, 80), defect: "stacked-doc" });
48
+ }
49
+ }
50
+ const isComment = insideBlock ||
51
+ opensBlock ||
52
+ (language === "js" ? lineCommentPattern.test(line) || blockLinePattern.test(line) : trimmed.startsWith("/*"));
53
+ if (opensBlock) {
54
+ insideBlock = true;
55
+ }
56
+ if (insideBlock && trimmed.includes("*/")) {
57
+ insideBlock = false;
58
+ pendingSinceLine = 0;
59
+ }
60
+ if (!isComment) {
61
+ continue;
62
+ }
63
+ const pointer = docPointerPattern.exec(line);
64
+ if (pointer !== null) {
65
+ findings.push({ line: index + 1, raw: pointer[0].trim(), defect: "doc-pointer" });
66
+ }
67
+ if (jsdocTypePattern.test(line)) {
68
+ findings.push({ line: index + 1, raw: trimmed.slice(0, 80), defect: "jsdoc-type" });
69
+ }
70
+ if (paramNoHyphenPattern.test(line)) {
71
+ findings.push({ line: index + 1, raw: trimmed.slice(0, 80), defect: "param-hyphen" });
72
+ }
73
+ // `@since` is stamped at release and stays the block's last tag; any tag after it is misplaced.
74
+ if (insideBlock && sinceTagPattern.test(line)) {
75
+ pendingSinceLine = index + 1;
76
+ }
77
+ else if (pendingSinceLine > 0 && blockTagPattern.test(line)) {
78
+ findings.push({ line: pendingSinceLine, raw: trimmed.slice(0, 80), defect: "since-order" });
79
+ pendingSinceLine = 0;
80
+ }
81
+ const paramTag = paramTagPattern.exec(line);
82
+ if (insideBlock && paramTag !== null) {
83
+ documentedParams.push(paramTag[1].split(".", 1)[0]);
84
+ }
85
+ // A `//` run between a doc block and its declaration detaches the block from the symbol.
86
+ if (language === "js" && docBlockClosePattern.test(line) && index + 1 < lines.length) {
87
+ let cursor = index + 1;
88
+ while (cursor < lines.length && lineCommentPattern.test(lines[cursor])) {
89
+ cursor++;
90
+ }
91
+ if (cursor > index + 1 && cursor < lines.length && declarationPattern.test(lines[cursor])) {
92
+ findings.push({ line: index + 2, raw: lines[index + 1].trim().slice(0, 80), defect: "detached-doc" });
93
+ }
94
+ if (documentedParams.length > 0) {
95
+ const missing = missingSignatureParams(lines, index + 1, documentedParams);
96
+ if (missing.length > 0) {
97
+ findings.push({ line: index + 1, raw: `@param missing: ${missing.join(", ")}`, defect: "param-coverage" });
98
+ }
99
+ }
100
+ documentedParams = [];
101
+ }
102
+ }
103
+ return findings;
104
+ }
105
+ // A partial list reads as a complete one, so a block naming any parameter must name them all.
106
+ function missingSignatureParams(lines, startIndex, documented) {
107
+ // gather the declaration text up to a balanced top-level `(...)` group
108
+ let text = "";
109
+ let sawOpen = false;
110
+ let depth = 0;
111
+ for (let index = startIndex; index < Math.min(lines.length, startIndex + 40); index++) {
112
+ for (const ch of lines[index]) {
113
+ if (ch === "(") {
114
+ depth++;
115
+ sawOpen = true;
116
+ if (depth === 1) {
117
+ continue;
118
+ }
119
+ }
120
+ else if (ch === ")") {
121
+ depth--;
122
+ }
123
+ if (sawOpen && depth > 0) {
124
+ text += ch;
125
+ }
126
+ if (sawOpen && depth === 0) {
127
+ return compareParams(text, documented);
128
+ }
129
+ }
130
+ if (sawOpen) {
131
+ text += "\n";
132
+ }
133
+ }
134
+ return [];
135
+ }
136
+ function compareParams(parameterListText, documented) {
137
+ const entries = [];
138
+ let depth = 0;
139
+ let current = "";
140
+ for (const ch of parameterListText) {
141
+ if ("([{<".includes(ch)) {
142
+ depth++;
143
+ }
144
+ else if (")]}>".includes(ch)) {
145
+ depth--;
146
+ }
147
+ if (ch === "," && depth === 0) {
148
+ entries.push(current);
149
+ current = "";
150
+ }
151
+ else {
152
+ current += ch;
153
+ }
154
+ }
155
+ if (current.trim().length > 0) {
156
+ entries.push(current);
157
+ }
158
+ const missing = [];
159
+ for (const entry of entries) {
160
+ const cleaned = entry.trim().replace(/^\.{3}/, "");
161
+ // A wrapper call or a destructured parameter cannot be matched by name — skip, never guess.
162
+ if (cleaned.startsWith("(")) {
163
+ return [];
164
+ }
165
+ if (cleaned.startsWith("{") || cleaned.startsWith("[")) {
166
+ continue;
167
+ }
168
+ const name = /^(?:readonly\s+)?([\w$]+)/.exec(cleaned)?.[1];
169
+ if (name === undefined || name === "this") {
170
+ continue;
171
+ }
172
+ if (!documented.includes(name)) {
173
+ missing.push(name);
174
+ }
175
+ }
176
+ return missing;
177
+ }
178
+ //# sourceMappingURL=comment-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-content.js","sourceRoot":"","sources":["../../../src/audit/domain/comment-content.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Recognises section dividers in source text and renders them in the one form the repo allows.
3
+ */
4
+ /**
5
+ * The column every divider's rule ends at — `oxfmt`'s `printWidth`, indentation included.
6
+ *
7
+ * @since 0.6.0
8
+ */
9
+ export const DIVIDER_COLUMN = 120;
10
+ const RULE_GLYPH = "─";
11
+ const LEAD_GLYPHS = "──";
12
+ /** Every glyph a divider has historically been drawn with, so legacy forms are recognised too. */
13
+ const RULE_CHARACTER_CLASS = String.raw `[-=─_*~#]`;
14
+ const ruleOnlyLinePattern = new RegExp(String.raw `^(?<indent>[ \t]*)(?:\/\/|\/\*|\*)[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*(?:\*\/)?[ \t]*$`);
15
+ const titledLinePattern = new RegExp(String.raw `^(?<indent>[ \t]*)(?:\/\/|\/\*)[ \t]*${RULE_CHARACTER_CLASS}{2,}[ \t]+(?<title>.*?)[ \t]+${RULE_CHARACTER_CLASS}{2,}[ \t]*(?:\*\/)?[ \t]*$`);
16
+ const commentLinePattern = /^[ \t]*(?:\/\/|\/\*|\*)/;
17
+ const bareRuleClosePattern = new RegExp(String.raw `^[ \t]*${RULE_CHARACTER_CLASS}{4,}[ \t]*\*\/[ \t]*$`);
18
+ const rulesOnlyPattern = new RegExp(String.raw `^(?:${RULE_CHARACTER_CLASS}|[ \t])*$`);
19
+ const commentPrefixPattern = /^[ \t]*(?:\/\/|\/\*|\*)[ \t]?/;
20
+ const commentSuffixPattern = /[ \t]*\*\/[ \t]*$/;
21
+ /** A banner spanning more lines than this is prose that happens to start with a rule, not a divider. */
22
+ const MAX_BANNER_SPAN = 16;
23
+ /** Past this a banner's lone line is a sentence the writer wanted kept, not a section name. */
24
+ const MAX_TITLE_LENGTH = 60;
25
+ /**
26
+ * Renders a divider in the canonical form.
27
+ *
28
+ * @param indent - leading whitespace copied from the site, counted toward the column
29
+ * @param title - the section name, already trimmed
30
+ * @param language - which comment syntax to draw the divider in
31
+ *
32
+ * @since 0.6.0
33
+ */
34
+ export function renderDivider(indent, title, language) {
35
+ if (language === "css") {
36
+ const head = `${indent}/* ${LEAD_GLYPHS} ${title} `;
37
+ return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length - 3))} */`;
38
+ }
39
+ const head = `${indent}// ${LEAD_GLYPHS} ${title} `;
40
+ return `${head}${RULE_GLYPH.repeat(Math.max(2, DIVIDER_COLUMN - head.length))}`;
41
+ }
42
+ /**
43
+ * Every section divider in a file, canonical ones included, in source order.
44
+ *
45
+ * @remarks A rule-framed comment that carries prose is a doc block, not a divider, and never
46
+ * appears here — the two are different things and only one of them has a fixed form.
47
+ *
48
+ * @since 0.6.0
49
+ */
50
+ export function scanCommentDividers(content, language) {
51
+ const lines = content.split(/\r?\n/);
52
+ const regions = [];
53
+ for (let index = 0; index < lines.length; index++) {
54
+ const region = readRegionAt(lines, index, language);
55
+ if (region === null) {
56
+ continue;
57
+ }
58
+ regions.push(region);
59
+ index = region.endLine - 1;
60
+ }
61
+ return regions;
62
+ }
63
+ /**
64
+ * Rewrites every off-convention divider into the canonical form.
65
+ *
66
+ * @since 0.6.0
67
+ */
68
+ export function applyCommentDividerFixes(content, language) {
69
+ const regions = scanCommentDividers(content, language).filter((region) => region.defect !== null);
70
+ if (regions.length === 0) {
71
+ return { content, fixedCount: 0 };
72
+ }
73
+ const newline = content.includes("\r\n") ? "\r\n" : "\n";
74
+ const lines = content.split(/\r?\n/);
75
+ // Descending, so an earlier region's replacement cannot shift a later region's line numbers.
76
+ for (const region of [...regions].toReversed()) {
77
+ lines.splice(region.startLine - 1, region.endLine - region.startLine + 1, renderDivider(region.indent, region.title, language));
78
+ }
79
+ return { content: lines.join(newline), fixedCount: regions.length };
80
+ }
81
+ function readRegionAt(lines, index, language) {
82
+ const line = lines[index];
83
+ const titled = titledLinePattern.exec(line);
84
+ const title = titled?.groups?.title?.trim();
85
+ if (titled !== null && title !== undefined && title.length > 0 && !rulesOnlyPattern.test(title)) {
86
+ const indent = titled.groups?.indent ?? "";
87
+ const canonical = renderDivider(indent, title, language);
88
+ return {
89
+ startLine: index + 1,
90
+ endLine: index + 1,
91
+ indent,
92
+ title,
93
+ raw: line.trim(),
94
+ defect: line === canonical ? null : usesCanonicalGlyphs(line) ? "bad-width" : "legacy-form",
95
+ };
96
+ }
97
+ const ruleOnly = ruleOnlyLinePattern.exec(line);
98
+ if (ruleOnly === null) {
99
+ return null;
100
+ }
101
+ return readBannerAt(lines, index, ruleOnly.groups?.indent ?? "");
102
+ }
103
+ function readBannerAt(lines, index, indent) {
104
+ const line = lines[index];
105
+ // An unterminated `/*` opens a block whose body needs no per-line marker, so the run ends at `*/`.
106
+ const insideBlock = line.trimStart().startsWith("/*") && !line.includes("*/");
107
+ const body = [];
108
+ let cursor = index + 1;
109
+ while (cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && !isBannerClose(lines[cursor], insideBlock)) {
110
+ if (!insideBlock && !commentLinePattern.test(lines[cursor])) {
111
+ break;
112
+ }
113
+ body.push(stripCommentPrefix(lines[cursor]));
114
+ cursor++;
115
+ }
116
+ const closed = cursor < lines.length && cursor - index <= MAX_BANNER_SPAN && isBannerClose(lines[cursor], insideBlock);
117
+ const meaningful = body.filter((entry) => entry.length > 0);
118
+ // A frame around prose is a doc block, and an unclosed rule is prose formatting inside one.
119
+ if (!closed || meaningful.length !== 1 || !looksLikeTitle(meaningful[0])) {
120
+ return null;
121
+ }
122
+ return {
123
+ startLine: index + 1,
124
+ endLine: cursor + 1,
125
+ indent,
126
+ title: meaningful[0],
127
+ raw: line.trim(),
128
+ defect: "legacy-form",
129
+ };
130
+ }
131
+ function looksLikeTitle(text) {
132
+ return text.length <= MAX_TITLE_LENGTH && !text.endsWith(".");
133
+ }
134
+ function isBannerClose(line, insideBlock) {
135
+ if (ruleOnlyLinePattern.test(line)) {
136
+ return true;
137
+ }
138
+ return insideBlock && bareRuleClosePattern.test(line);
139
+ }
140
+ function usesCanonicalGlyphs(line) {
141
+ return line.trimStart().startsWith(`// ${LEAD_GLYPHS} `) || line.trimStart().startsWith(`/* ${LEAD_GLYPHS} `);
142
+ }
143
+ function stripCommentPrefix(line) {
144
+ return line.replace(commentPrefixPattern, "").replace(commentSuffixPattern, "").trim();
145
+ }
146
+ //# sourceMappingURL=comment-dividers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-dividers.js","sourceRoot":"","sources":["../../../src/audit/domain/comment-dividers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Resolves `{@link}` references against the scanned tree, so a rename that orphans one is caught.
3
+ */
4
+ const linkPattern = /\{@link\s+([^}|\s]+)/g;
5
+ const commentLinePattern = /^[ \t]*(?:\/\/|\/\*|\*)/;
6
+ /**
7
+ * Every `{@link}` target in a file's comments, in source order.
8
+ *
9
+ * @since 0.6.0
10
+ */
11
+ export function scanLinkReferences(content) {
12
+ const references = [];
13
+ const lines = content.split(/\r?\n/);
14
+ for (let index = 0; index < lines.length; index++) {
15
+ if (!commentLinePattern.test(lines[index])) {
16
+ continue;
17
+ }
18
+ for (const match of lines[index].matchAll(linkPattern)) {
19
+ references.push({ line: index + 1, target: match[1] });
20
+ }
21
+ }
22
+ return references;
23
+ }
24
+ /**
25
+ * The identifier a declaration-style target must resolve through — `Foo.bar` resolves via `Foo`.
26
+ *
27
+ * @since 0.6.0
28
+ */
29
+ export function linkTargetHead(target) {
30
+ return target.split(/[.#]/, 1)[0];
31
+ }
32
+ /**
33
+ * Whether a target names a file or URL rather than a declaration.
34
+ *
35
+ * @since 0.6.0
36
+ */
37
+ export function isPathLinkTarget(target) {
38
+ return (target.startsWith("http://") || target.startsWith("https://") || target.startsWith("./") || target.startsWith("../"));
39
+ }
40
+ /**
41
+ * Counts word-boundary occurrences of each head across a body of source text.
42
+ *
43
+ * @remarks A `{@link X}` occurrence itself mentions `X` once, so a target is orphaned when its
44
+ * total mentions do not exceed its link occurrences — a rename removes every real mention.
45
+ *
46
+ * @since 0.6.0
47
+ */
48
+ export function countHeadMentions(contents, heads) {
49
+ const counts = new Map();
50
+ for (const head of heads) {
51
+ counts.set(head, 0);
52
+ }
53
+ const wordPattern = /[A-Za-z_$][\w$]*/g;
54
+ for (const content of contents) {
55
+ for (const match of content.matchAll(wordPattern)) {
56
+ const found = counts.get(match[0]);
57
+ if (found !== undefined) {
58
+ counts.set(match[0], found + 1);
59
+ }
60
+ }
61
+ }
62
+ return counts;
63
+ }
64
+ //# sourceMappingURL=link-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-references.js","sourceRoot":"","sources":["../../../src/audit/domain/link-references.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH,MAAM,WAAW,GAAG,uBAAuB,CAAC;AAC5C,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,MAAM,UAAU,GAAyB,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAE,CAAC,EAAE,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,KAAK,CAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACxD,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,CACL,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CACrH,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAA0B,EAAE,KAA0B;IACtF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtB,CAAC;IACD,MAAM,WAAW,GAAG,mBAAmB,CAAC;IACxC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Extracts the two things a markdown cross-reference can get wrong: where it points and what it lands on.
3
+ */
4
+ // Anything with a scheme, a protocol-relative host, or a bare mail address is somebody else's to check.
5
+ const EXTERNAL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
6
+ /**
7
+ * The anchor ids a rendered document exposes: explicit `<a id>` targets plus every heading's slug.
8
+ *
9
+ * @remarks Slugging matches GitHub's — lowercase, drop everything that is not a letter, number, space
10
+ * or hyphen, then hyphenate spaces. Duplicate headings get a `-1` suffix there; this returns the base
11
+ * only, so a link to the second copy reads as dangling rather than being silently accepted.
12
+ *
13
+ * @since 0.5.0
14
+ */
15
+ export function collectMarkdownAnchors(content) {
16
+ const anchors = new Set();
17
+ for (const match of content.matchAll(/<a\s+id="([^"]+)"\s*><\/a>/g)) {
18
+ anchors.add(match[1]);
19
+ }
20
+ for (const match of content.matchAll(/^#{1,6}\s+(.+?)\s*$/gm)) {
21
+ anchors.add(match[1]
22
+ .toLowerCase()
23
+ .replaceAll(/[^\p{L}\p{N} -]/gu, "")
24
+ .trim()
25
+ .replaceAll(/\s+/g, "-"));
26
+ }
27
+ return anchors;
28
+ }
29
+ /**
30
+ * Every repo-local link a document makes, with the anchors it offers.
31
+ *
32
+ * @remarks Fenced code is stripped first: a fence showing a link is an example, not a reference, and
33
+ * checking it would make the audit fail on documentation that is doing its job.
34
+ *
35
+ * @since 0.5.0
36
+ */
37
+ export function scanMarkdownLinks(content) {
38
+ const withoutFences = content.replaceAll(/^```[\s\S]*?^```/gm, (block) => block.replaceAll(/[^\n]/g, " "));
39
+ const references = [];
40
+ for (const match of withoutFences.matchAll(/\[[^\]]*]\(\s*([^)\s]+?)\s*\)/g)) {
41
+ const raw = match[1];
42
+ if (EXTERNAL.test(raw)) {
43
+ continue;
44
+ }
45
+ const hashAt = raw.indexOf("#");
46
+ const targetPath = hashAt === -1 ? raw : raw.slice(0, hashAt);
47
+ const anchor = hashAt === -1 ? null : raw.slice(hashAt + 1);
48
+ if (targetPath === "" && anchor === null) {
49
+ continue;
50
+ }
51
+ references.push({
52
+ line: lineNumberAt(withoutFences, match.index),
53
+ targetPath: decodeTarget(targetPath),
54
+ anchor: anchor === null || anchor === "" ? null : decodeTarget(anchor),
55
+ });
56
+ }
57
+ return { references, anchors: collectMarkdownAnchors(content) };
58
+ }
59
+ function decodeTarget(value) {
60
+ try {
61
+ return decodeURIComponent(value);
62
+ }
63
+ catch {
64
+ return value;
65
+ }
66
+ }
67
+ function lineNumberAt(content, index) {
68
+ let line = 1;
69
+ for (let position = 0; position < index; position++) {
70
+ if (content.charCodeAt(position) === 10) {
71
+ line++;
72
+ }
73
+ }
74
+ return line;
75
+ }
76
+ //# sourceMappingURL=markdown-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-links.js","sourceRoot":"","sources":["../../../src/audit/domain/markdown-links.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Split a class token into [variant, value, modifier], colon/slash-aware of
2
+ * Splits a class token into [variant, value, modifier], colon/slash-aware of
3
3
  * brackets and parens (arbitrary values like data-[side=left] or calc(...)).
4
4
  *
5
5
  * @since 1.0.0-canary.7
@@ -48,7 +48,7 @@ export function splitClassName(token) {
48
48
  return [variant, value, modifier];
49
49
  }
50
50
  /**
51
- * Extract string/template literal contents with their line numbers.
51
+ * Extracts string/template literal contents with their line numbers.
52
52
  *
53
53
  * @since 1.0.0-canary.7
54
54
  */
@@ -0,0 +1,33 @@
1
+ import { TSDocConfiguration, TSDocParser, TSDocTagDefinition, TSDocTagSyntaxKind } from "@microsoft/tsdoc";
2
+ const configuration = new TSDocConfiguration();
3
+ // The repo's one custom tag, stamped at release by `codefast tag`.
4
+ configuration.addTagDefinition(new TSDocTagDefinition({ tagName: "@since", syntaxKind: TSDocTagSyntaxKind.BlockTag }));
5
+ configuration.setSupportForTags(configuration.tagDefinitions, true);
6
+ const parser = new TSDocParser(configuration);
7
+ // Only a line-leading `/**` opens a doc block — one inside code or a string literal does not.
8
+ const docBlockPattern = /^[ \t]*\/\*\*[\s\S]*?\*\//gm;
9
+ /**
10
+ * Every TSDoc grammar diagnostic in a file's doc blocks, in source order.
11
+ *
12
+ * @since 0.6.0
13
+ */
14
+ export function scanTsdocSyntax(content) {
15
+ const findings = [];
16
+ for (const match of content.matchAll(docBlockPattern)) {
17
+ const context = parser.parseString(match[0]);
18
+ if (context.log.messages.length === 0) {
19
+ continue;
20
+ }
21
+ const blockLine = content.slice(0, match.index).split("\n").length;
22
+ for (const message of context.log.messages) {
23
+ const offsetInBlock = match[0].slice(0, message.textRange.pos).split("\n").length - 1;
24
+ findings.push({
25
+ line: blockLine + offsetInBlock,
26
+ raw: message.messageId,
27
+ reason: message.unformattedText,
28
+ });
29
+ }
30
+ }
31
+ return findings;
32
+ }
33
+ //# sourceMappingURL=tsdoc-syntax.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsdoc-syntax.js","sourceRoot":"","sources":["../../../src/audit/domain/tsdoc-syntax.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAkB3G,MAAM,aAAa,GAAG,IAAI,kBAAkB,EAAE,CAAC;AAC/C,mEAAmE;AACnE,aAAa,CAAC,gBAAgB,CAAC,IAAI,kBAAkB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,kBAAkB,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;AACvH,aAAa,CAAC,iBAAiB,CAAC,aAAa,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;AACpE,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,aAAa,CAAC,CAAC;AAE9C,8FAA8F;AAC9F,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,QAAQ,GAA8B,EAAE,CAAC;IAC/C,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;QACtD,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtC,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QACnE,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC3C,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;YACtF,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,SAAS,GAAG,aAAa;gBAC/B,GAAG,EAAE,OAAO,CAAC,SAAS;gBACtB,MAAM,EAAE,OAAO,CAAC,eAAe;aAChC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -41,4 +41,92 @@ export function formatRtlAuditJsonOutput(result, rootDir) {
41
41
  result,
42
42
  });
43
43
  }
44
+ /**
45
+ * Exit `1` when any non-allowlisted broken link remains.
46
+ *
47
+ * @since 0.5.0
48
+ */
49
+ export function exitCodeForLinkAuditResult(result) {
50
+ return result.breakageCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
51
+ }
52
+ /**
53
+ * Human-readable link audit report.
54
+ *
55
+ * @since 0.5.0
56
+ */
57
+ export function presentLinkAuditResult(result) {
58
+ for (const file of result.files) {
59
+ logger.out(`\n${file.relativePath}`);
60
+ for (const { line, raw, reason } of file.breakages) {
61
+ logger.out(` ${line}: ${raw} → ${reason}`);
62
+ }
63
+ }
64
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
65
+ if (result.breakageCount > 0) {
66
+ logger.out(`\n✖ ${result.breakageCount} broken link(s)${allowlistSuffix}`);
67
+ }
68
+ else {
69
+ logger.out(`✓ ${result.linkCount} repo-local link(s) across ${result.scannedFileCount} document(s) all resolve${allowlistSuffix}`);
70
+ }
71
+ }
72
+ /**
73
+ * Machine-readable link audit summary for `--json`.
74
+ *
75
+ * @since 0.5.0
76
+ */
77
+ export function formatLinkAuditJsonOutput(result, rootDir) {
78
+ return JSON.stringify({
79
+ schemaVersion: 1,
80
+ ok: result.breakageCount === 0,
81
+ cwd: rootDir,
82
+ result,
83
+ });
84
+ }
85
+ /**
86
+ * Exit `1` when any non-allowlisted divider still breaks the convention.
87
+ *
88
+ * @since 0.6.0
89
+ */
90
+ export function exitCodeForCommentAuditResult(result) {
91
+ return result.breakageCount > 0 ? CLI_EXIT_GENERAL_ERROR : CLI_EXIT_SUCCESS;
92
+ }
93
+ /**
94
+ * Human-readable comment-divider report.
95
+ *
96
+ * @since 0.6.0
97
+ */
98
+ export function presentCommentAuditResult(result) {
99
+ for (const file of result.files) {
100
+ logger.out(`\n${file.relativePath}`);
101
+ for (const { line, raw, reason } of file.breakages) {
102
+ logger.out(` ${line}: ${truncate(raw)} → ${reason}`);
103
+ }
104
+ }
105
+ const allowlistSuffix = result.allowlistedCount > 0 ? ` (${result.allowlistedCount} allowlisted)` : "";
106
+ if (result.fixedCount > 0) {
107
+ logger.out(`\n✎ Rewrote ${result.fixedCount} divider(s)`);
108
+ }
109
+ if (result.breakageCount > 0) {
110
+ logger.out(`\n✖ ${result.breakageCount} comment issue(s)${allowlistSuffix} — --fix rewrites divider forms`);
111
+ }
112
+ else {
113
+ logger.out(`✓ ${result.dividerCount} divider(s) across ${result.scannedFileCount} file(s), no banned comment content${allowlistSuffix}`);
114
+ }
115
+ }
116
+ /**
117
+ * Machine-readable comment-divider summary for `--json`.
118
+ *
119
+ * @since 0.6.0
120
+ */
121
+ export function formatCommentAuditJsonOutput(result, rootDir) {
122
+ return JSON.stringify({
123
+ schemaVersion: 1,
124
+ ok: result.breakageCount === 0,
125
+ cwd: rootDir,
126
+ result,
127
+ });
128
+ }
129
+ function truncate(raw) {
130
+ return raw.length <= 60 ? raw : `${raw.slice(0, 57)}…`;
131
+ }
44
132
  //# sourceMappingURL=output.js.map