pasika 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -17
- package/dist/cli/index.d.ts +0 -1
- package/dist/cli/index.js +4999 -103
- package/dist/eslint/pasika/index.d.ts +165 -53
- package/dist/eslint/pasika/index.js +4424 -89
- package/package.json +3 -3
- package/dist/cli/output.d.ts +0 -3
- package/dist/cli/output.js +0 -11
- package/dist/enforcement/coverage.d.ts +0 -55
- package/dist/enforcement/coverage.js +0 -239
- package/dist/enforcement/doctor.d.ts +0 -16
- package/dist/enforcement/doctor.js +0 -249
- package/dist/enforcement/normalize.d.ts +0 -11
- package/dist/enforcement/normalize.js +0 -21
- package/dist/enforcement/parse-docs.d.ts +0 -58
- package/dist/enforcement/parse-docs.js +0 -98
- package/dist/enforcement/types.d.ts +0 -54
- package/dist/enforcement/types.js +0 -50
- package/dist/eslint/pasika/ast-types.d.ts +0 -122
- package/dist/eslint/pasika/ast-types.js +0 -1
- package/dist/eslint/pasika/project/ccf.d.ts +0 -48
- package/dist/eslint/pasika/project/ccf.js +0 -119
- package/dist/eslint/pasika/project/index.d.ts +0 -21
- package/dist/eslint/pasika/project/index.js +0 -139
- package/dist/eslint/pasika/project/parse-module.d.ts +0 -27
- package/dist/eslint/pasika/project/parse-module.js +0 -128
- package/dist/eslint/pasika/rules/application-structure.d.ts +0 -2
- package/dist/eslint/pasika/rules/application-structure.js +0 -152
- package/dist/eslint/pasika/rules/component-conventions.d.ts +0 -16
- package/dist/eslint/pasika/rules/component-conventions.js +0 -145
- package/dist/eslint/pasika/rules/component-nesting.d.ts +0 -12
- package/dist/eslint/pasika/rules/component-nesting.js +0 -69
- package/dist/eslint/pasika/rules/component-placement.d.ts +0 -11
- package/dist/eslint/pasika/rules/component-placement.js +0 -75
- package/dist/eslint/pasika/rules/config-extraction.d.ts +0 -14
- package/dist/eslint/pasika/rules/config-extraction.js +0 -74
- package/dist/eslint/pasika/rules/cross-feature-import.d.ts +0 -2
- package/dist/eslint/pasika/rules/cross-feature-import.js +0 -75
- package/dist/eslint/pasika/rules/css/apply-usage.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/apply-usage.js +0 -45
- package/dist/eslint/pasika/rules/css/base-layer-pair.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/base-layer-pair.js +0 -49
- package/dist/eslint/pasika/rules/css/css-variable-naming.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/css-variable-naming.js +0 -39
- package/dist/eslint/pasika/rules/css/custom-utility-apply.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/custom-utility-apply.js +0 -52
- package/dist/eslint/pasika/rules/css/global-css-location.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/global-css-location.js +0 -42
- package/dist/eslint/pasika/rules/css/helpers.d.ts +0 -29
- package/dist/eslint/pasika/rules/css/helpers.js +0 -93
- package/dist/eslint/pasika/rules/css/index.d.ts +0 -13
- package/dist/eslint/pasika/rules/css/index.js +0 -25
- package/dist/eslint/pasika/rules/css/root-variables.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/root-variables.js +0 -53
- package/dist/eslint/pasika/rules/css/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/rule-tester.js +0 -22
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/stylesheet-ordering.js +0 -76
- package/dist/eslint/pasika/rules/css/surface-utility.d.ts +0 -10
- package/dist/eslint/pasika/rules/css/surface-utility.js +0 -41
- package/dist/eslint/pasika/rules/css/theme-reset.d.ts +0 -9
- package/dist/eslint/pasika/rules/css/theme-reset.js +0 -36
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.d.ts +0 -11
- package/dist/eslint/pasika/rules/css/theme-variable-namespace.js +0 -53
- package/dist/eslint/pasika/rules/cva-appearance-props.d.ts +0 -10
- package/dist/eslint/pasika/rules/cva-appearance-props.js +0 -77
- package/dist/eslint/pasika/rules/cva-boolean-variants.d.ts +0 -11
- package/dist/eslint/pasika/rules/cva-boolean-variants.js +0 -97
- package/dist/eslint/pasika/rules/data-testid-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/data-testid-case.js +0 -76
- package/dist/eslint/pasika/rules/enforce-barrel-exports.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-barrel-exports.js +0 -78
- package/dist/eslint/pasika/rules/enforce-cn-merge.d.ts +0 -21
- package/dist/eslint/pasika/rules/enforce-cn-merge.js +0 -86
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.d.ts +0 -9
- package/dist/eslint/pasika/rules/enforce-cva-variant-props.js +0 -86
- package/dist/eslint/pasika/rules/filename-case.d.ts +0 -2
- package/dist/eslint/pasika/rules/filename-case.js +0 -123
- package/dist/eslint/pasika/rules/hook-complexity.d.ts +0 -12
- package/dist/eslint/pasika/rules/hook-complexity.js +0 -114
- package/dist/eslint/pasika/rules/hook-extraction.d.ts +0 -11
- package/dist/eslint/pasika/rules/hook-extraction.js +0 -55
- package/dist/eslint/pasika/rules/import-boundaries.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-boundaries.js +0 -167
- package/dist/eslint/pasika/rules/import-through-index.d.ts +0 -2
- package/dist/eslint/pasika/rules/import-through-index.js +0 -52
- package/dist/eslint/pasika/rules/interactive-component.d.ts +0 -10
- package/dist/eslint/pasika/rules/interactive-component.js +0 -76
- package/dist/eslint/pasika/rules/json/index.d.ts +0 -5
- package/dist/eslint/pasika/rules/json/index.js +0 -9
- package/dist/eslint/pasika/rules/json/no-cache-flag.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-cache-flag.js +0 -45
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.d.ts +0 -10
- package/dist/eslint/pasika/rules/json/no-vulyk-dependency.js +0 -41
- package/dist/eslint/pasika/rules/json/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/json/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/jsx-hygiene.d.ts +0 -9
- package/dist/eslint/pasika/rules/jsx-hygiene.js +0 -108
- package/dist/eslint/pasika/rules/locale-dotted-path.d.ts +0 -10
- package/dist/eslint/pasika/rules/locale-dotted-path.js +0 -65
- package/dist/eslint/pasika/rules/locale-placement.d.ts +0 -13
- package/dist/eslint/pasika/rules/locale-placement.js +0 -163
- package/dist/eslint/pasika/rules/locales-location.d.ts +0 -11
- package/dist/eslint/pasika/rules/locales-location.js +0 -56
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/doc-kind-suffix.js +0 -29
- package/dist/eslint/pasika/rules/md/example-heading-description.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/example-heading-description.js +0 -30
- package/dist/eslint/pasika/rules/md/glossary-term-linking.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/glossary-term-linking.js +0 -99
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/guide-folder-entry-point.js +0 -46
- package/dist/eslint/pasika/rules/md/guide-link-anchors.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-link-anchors.js +0 -50
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-overview-no-links.js +0 -51
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-states-no-requirement.js +0 -26
- package/dist/eslint/pasika/rules/md/guide-step-single-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-link.js +0 -48
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/guide-step-single-sentence.js +0 -42
- package/dist/eslint/pasika/rules/md/helpers.d.ts +0 -25
- package/dist/eslint/pasika/rules/md/helpers.js +0 -37
- package/dist/eslint/pasika/rules/md/index.d.ts +0 -27
- package/dist/eslint/pasika/rules/md/index.js +0 -53
- package/dist/eslint/pasika/rules/md/no-cross-document-link.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-cross-document-link.js +0 -36
- package/dist/eslint/pasika/rules/md/no-nested-how-to.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-nested-how-to.js +0 -39
- package/dist/eslint/pasika/rules/md/no-template-prompt.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/no-template-prompt.js +0 -22
- package/dist/eslint/pasika/rules/md/overview-length.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-length.js +0 -47
- package/dist/eslint/pasika/rules/md/overview-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/overview-present.js +0 -41
- package/dist/eslint/pasika/rules/md/policy-no-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-no-examples.js +0 -28
- package/dist/eslint/pasika/rules/md/policy-single-document.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-single-document.js +0 -34
- package/dist/eslint/pasika/rules/md/policy-subject-headings.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/policy-subject-headings.js +0 -58
- package/dist/eslint/pasika/rules/md/project-index.d.ts +0 -17
- package/dist/eslint/pasika/rules/md/project-index.js +0 -67
- package/dist/eslint/pasika/rules/md/reference-block-headings.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-block-headings.js +0 -31
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.d.ts +0 -6
- package/dist/eslint/pasika/rules/md/reference-no-rfc-vocabulary.js +0 -26
- package/dist/eslint/pasika/rules/md/requirement-present.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/requirement-present.js +0 -41
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rfc-only-in-bullets.js +0 -46
- package/dist/eslint/pasika/rules/md/rule-paired-examples.d.ts +0 -5
- package/dist/eslint/pasika/rules/md/rule-paired-examples.js +0 -36
- package/dist/eslint/pasika/rules/md/rule-tester.d.ts +0 -11
- package/dist/eslint/pasika/rules/md/rule-tester.js +0 -21
- package/dist/eslint/pasika/rules/md/support-document-placement.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/support-document-placement.js +0 -36
- package/dist/eslint/pasika/rules/md/title-matches-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/md/title-matches-file-name.js +0 -46
- package/dist/eslint/pasika/rules/named-exports.d.ts +0 -2
- package/dist/eslint/pasika/rules/named-exports.js +0 -49
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-arbitrary-tailwind.js +0 -97
- package/dist/eslint/pasika/rules/no-eslint-disable.d.ts +0 -10
- package/dist/eslint/pasika/rules/no-eslint-disable.js +0 -33
- package/dist/eslint/pasika/rules/no-mixed-concerns.d.ts +0 -9
- package/dist/eslint/pasika/rules/no-mixed-concerns.js +0 -71
- package/dist/eslint/pasika/rules/no-util-barrel.d.ts +0 -2
- package/dist/eslint/pasika/rules/no-util-barrel.js +0 -48
- package/dist/eslint/pasika/rules/pure-function-extract.d.ts +0 -9
- package/dist/eslint/pasika/rules/pure-function-extract.js +0 -92
- package/dist/eslint/pasika/rules/shared-style-dedup.d.ts +0 -13
- package/dist/eslint/pasika/rules/shared-style-dedup.js +0 -91
- package/dist/eslint/pasika/rules/stay-flat.d.ts +0 -12
- package/dist/eslint/pasika/rules/stay-flat.js +0 -81
- package/dist/eslint/pasika/rules/support-file-placement.d.ts +0 -15
- package/dist/eslint/pasika/rules/support-file-placement.js +0 -70
- package/dist/eslint/pasika/rules/support-folder-shape.d.ts +0 -2
- package/dist/eslint/pasika/rules/support-folder-shape.js +0 -59
- package/dist/eslint/pasika/rules/type-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/type-extraction.js +0 -85
- package/dist/eslint/pasika/rules/ui-state.d.ts +0 -9
- package/dist/eslint/pasika/rules/ui-state.js +0 -99
- package/dist/eslint/pasika/rules/util-file-name.d.ts +0 -2
- package/dist/eslint/pasika/rules/util-file-name.js +0 -48
- package/dist/eslint/pasika/rules/value-extraction.d.ts +0 -13
- package/dist/eslint/pasika/rules/value-extraction.js +0 -48
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/** The four document kinds the documentation guide defines. */
|
|
2
|
-
export type DocKind = "guide" | "rule" | "reference" | "policy";
|
|
3
|
-
/** Words that carry requirement strength, longest first so `MUST NOT` wins over `MUST`. */
|
|
4
|
-
declare const RFC_2119: RegExp;
|
|
5
|
-
export interface ParsedRequirement {
|
|
6
|
-
/** Canonical text used for identity. */
|
|
7
|
-
text: string;
|
|
8
|
-
hash: string;
|
|
9
|
-
/** The bullet as written, without its list marker. */
|
|
10
|
-
raw: string;
|
|
11
|
-
line: number;
|
|
12
|
-
}
|
|
13
|
-
export interface ParsedStep {
|
|
14
|
-
line: number;
|
|
15
|
-
text: string;
|
|
16
|
-
/** Documents this step links, as written. */
|
|
17
|
-
links: string[];
|
|
18
|
-
}
|
|
19
|
-
export interface ParsedDoc {
|
|
20
|
-
/** Absolute path on disk. */
|
|
21
|
-
filePath: string;
|
|
22
|
-
/** Path relative to the docs root, with forward slashes. */
|
|
23
|
-
doc: string;
|
|
24
|
-
fileName: string;
|
|
25
|
-
/** Undefined when no file-name suffix identifies the kind. */
|
|
26
|
-
kind?: DocKind;
|
|
27
|
-
title: string;
|
|
28
|
-
/** First non-empty prose line under the title. */
|
|
29
|
-
overview?: string;
|
|
30
|
-
/** Prose lines with fenced code blocks blanked and code spans unwrapped, indexed from 0. */
|
|
31
|
-
prose: string[];
|
|
32
|
-
/**
|
|
33
|
-
* Prose lines with code spans removed rather than unwrapped, so a backticked
|
|
34
|
-
* keyword counts as naming the keyword rather than using it.
|
|
35
|
-
*/
|
|
36
|
-
proseWithoutCode: string[];
|
|
37
|
-
requirements: ParsedRequirement[];
|
|
38
|
-
/** Numbered list items, which only a Guide is expected to have. */
|
|
39
|
-
steps: ParsedStep[];
|
|
40
|
-
/** `## Incorrect`/`## Correct` headings in document order. */
|
|
41
|
-
exampleHeadings: {
|
|
42
|
-
line: number;
|
|
43
|
-
text: string;
|
|
44
|
-
}[];
|
|
45
|
-
/** Every link to another Markdown document, outside fenced code. */
|
|
46
|
-
docLinks: {
|
|
47
|
-
line: number;
|
|
48
|
-
target: string;
|
|
49
|
-
}[];
|
|
50
|
-
/** `## ` section headings, excluding the example headings. */
|
|
51
|
-
sectionHeadings: {
|
|
52
|
-
line: number;
|
|
53
|
-
text: string;
|
|
54
|
-
}[];
|
|
55
|
-
}
|
|
56
|
-
export declare function parseDoc(filePath: string, docsRoot: string): ParsedDoc;
|
|
57
|
-
export declare function parseDocs(docsRoot: string): ParsedDoc[];
|
|
58
|
-
export { RFC_2119 };
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { hashRequirement, normalizeRequirement } from "./normalize.js";
|
|
4
|
-
const KIND_BY_SUFFIX = [
|
|
5
|
-
["-rule.md", "rule"],
|
|
6
|
-
["-guide.md", "guide"],
|
|
7
|
-
["-reference.md", "reference"],
|
|
8
|
-
["-policy.md", "policy"],
|
|
9
|
-
];
|
|
10
|
-
/** Words that carry requirement strength, longest first so `MUST NOT` wins over `MUST`. */
|
|
11
|
-
const RFC_2119 = /\b(?<keyword>MUST NOT|MUST|SHOULD NOT|SHOULD|MAY)\b/;
|
|
12
|
-
function listMarkdownFiles(dir) {
|
|
13
|
-
return readdirSync(dir).flatMap((entry) => {
|
|
14
|
-
const entryPath = path.join(dir, entry);
|
|
15
|
-
if (statSync(entryPath).isDirectory()) {
|
|
16
|
-
// Underscore folders hold templates and other support assets, not documents.
|
|
17
|
-
return entry.startsWith("_") ? [] : listMarkdownFiles(entryPath);
|
|
18
|
-
}
|
|
19
|
-
return entry.endsWith(".md") ? [entryPath] : [];
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Blanks fenced code blocks and unwraps inline code spans, keeping one entry per
|
|
24
|
-
* source line so reported line numbers still match the file.
|
|
25
|
-
*/
|
|
26
|
-
function toProse(body, codeSpanReplacement) {
|
|
27
|
-
let insideFence = false;
|
|
28
|
-
return body.split("\n").map((line) => {
|
|
29
|
-
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
30
|
-
insideFence = !insideFence;
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
if (insideFence)
|
|
34
|
-
return "";
|
|
35
|
-
return line.replaceAll(/`(?<content>[^`]*)`/g, codeSpanReplacement);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
function findDocLinks(line) {
|
|
39
|
-
return [...line.matchAll(/\]\((?<target>[^)]*\.md[^)]*)\)/g)].map((match) => match.groups?.target ?? "");
|
|
40
|
-
}
|
|
41
|
-
export function parseDoc(filePath, docsRoot) {
|
|
42
|
-
const body = readFileSync(filePath, "utf8");
|
|
43
|
-
const sourceLines = body.split("\n");
|
|
44
|
-
const prose = toProse(body, "$<content>");
|
|
45
|
-
const proseWithoutCode = toProse(body, "");
|
|
46
|
-
const fileName = path.basename(filePath);
|
|
47
|
-
const kind = KIND_BY_SUFFIX.find(([suffix]) => fileName.endsWith(suffix))?.[1];
|
|
48
|
-
const requirements = [];
|
|
49
|
-
const steps = [];
|
|
50
|
-
const exampleHeadings = [];
|
|
51
|
-
const sectionHeadings = [];
|
|
52
|
-
const docLinks = [];
|
|
53
|
-
prose.forEach((line, index) => {
|
|
54
|
-
const lineNumber = index + 1;
|
|
55
|
-
for (const target of findDocLinks(line)) {
|
|
56
|
-
docLinks.push({ line: lineNumber, target });
|
|
57
|
-
}
|
|
58
|
-
if (/^\s*[-*]\s/.test(line) && RFC_2119.test(line)) {
|
|
59
|
-
const text = normalizeRequirement(line);
|
|
60
|
-
// The raw bullet as written in the source file (code spans and links intact),
|
|
61
|
-
// so a registry entry's text is greppable in the doc it came from.
|
|
62
|
-
const raw = (sourceLines[index] ?? "").replace(/^\s*[-*]\s+/, "");
|
|
63
|
-
requirements.push({ text, hash: hashRequirement(text), raw, line: lineNumber });
|
|
64
|
-
}
|
|
65
|
-
if (/^\s*\d+\.\s/.test(line)) {
|
|
66
|
-
steps.push({ line: lineNumber, text: line.replace(/^\s*\d+\.\s+/, ""), links: findDocLinks(line) });
|
|
67
|
-
}
|
|
68
|
-
if (/^## (?:Incorrect|Correct)\b/.test(line)) {
|
|
69
|
-
exampleHeadings.push({ line: lineNumber, text: line.slice(3).trim() });
|
|
70
|
-
}
|
|
71
|
-
else if (line.startsWith("## ")) {
|
|
72
|
-
sectionHeadings.push({ line: lineNumber, text: line.slice(3).trim() });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
const title = /^# (?<title>.+)$/m.exec(body)?.groups?.title?.trim() ?? "";
|
|
76
|
-
const overview = prose.slice(1).find((line) => line.trim() !== "" && !line.startsWith("#"));
|
|
77
|
-
return {
|
|
78
|
-
filePath,
|
|
79
|
-
doc: path.relative(docsRoot, filePath).split(path.sep).join("/"),
|
|
80
|
-
fileName,
|
|
81
|
-
kind,
|
|
82
|
-
title,
|
|
83
|
-
overview,
|
|
84
|
-
prose,
|
|
85
|
-
proseWithoutCode,
|
|
86
|
-
requirements,
|
|
87
|
-
steps,
|
|
88
|
-
exampleHeadings,
|
|
89
|
-
docLinks,
|
|
90
|
-
sectionHeadings,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
export function parseDocs(docsRoot) {
|
|
94
|
-
return listMarkdownFiles(docsRoot)
|
|
95
|
-
.sort((a, b) => a.localeCompare(b))
|
|
96
|
-
.map((filePath) => parseDoc(filePath, docsRoot));
|
|
97
|
-
}
|
|
98
|
-
export { RFC_2119 };
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The enforcement registry — the record of how every documented requirement is
|
|
3
|
-
* checked.
|
|
4
|
-
*
|
|
5
|
-
* A requirement is identified by the hash of its canonical text, not by a
|
|
6
|
-
* hand-written id. Rewording a requirement therefore changes its hash, which
|
|
7
|
-
* makes `pasika coverage` report it as changed until someone re-confirms that
|
|
8
|
-
* the recorded enforcement still covers it.
|
|
9
|
-
*/
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
export declare const enforcementKindSchema: z.ZodEnum<{
|
|
12
|
-
eslint: "eslint";
|
|
13
|
-
doctor: "doctor";
|
|
14
|
-
judgment: "judgment";
|
|
15
|
-
permission: "permission";
|
|
16
|
-
planned: "planned";
|
|
17
|
-
}>;
|
|
18
|
-
export declare const requirementSchema: z.ZodObject<{
|
|
19
|
-
doc: z.ZodString;
|
|
20
|
-
text: z.ZodString;
|
|
21
|
-
hash: z.ZodString;
|
|
22
|
-
kind: z.ZodEnum<{
|
|
23
|
-
eslint: "eslint";
|
|
24
|
-
doctor: "doctor";
|
|
25
|
-
judgment: "judgment";
|
|
26
|
-
permission: "permission";
|
|
27
|
-
planned: "planned";
|
|
28
|
-
}>;
|
|
29
|
-
ref: z.ZodOptional<z.ZodString>;
|
|
30
|
-
note: z.ZodOptional<z.ZodString>;
|
|
31
|
-
}, z.core.$strip>;
|
|
32
|
-
export declare const registrySchema: z.ZodObject<{
|
|
33
|
-
requirements: z.ZodArray<z.ZodObject<{
|
|
34
|
-
doc: z.ZodString;
|
|
35
|
-
text: z.ZodString;
|
|
36
|
-
hash: z.ZodString;
|
|
37
|
-
kind: z.ZodEnum<{
|
|
38
|
-
eslint: "eslint";
|
|
39
|
-
doctor: "doctor";
|
|
40
|
-
judgment: "judgment";
|
|
41
|
-
permission: "permission";
|
|
42
|
-
planned: "planned";
|
|
43
|
-
}>;
|
|
44
|
-
ref: z.ZodOptional<z.ZodString>;
|
|
45
|
-
note: z.ZodOptional<z.ZodString>;
|
|
46
|
-
}, z.core.$strip>>;
|
|
47
|
-
}, z.core.$strip>;
|
|
48
|
-
export type EnforcementKind = z.infer<typeof enforcementKindSchema>;
|
|
49
|
-
export type Requirement = z.infer<typeof requirementSchema>;
|
|
50
|
-
export type Registry = z.infer<typeof registrySchema>;
|
|
51
|
-
/** Every kind, in the order the coverage summary prints them. */
|
|
52
|
-
export declare const ENFORCEMENT_KINDS: EnforcementKind[];
|
|
53
|
-
/** The kinds that count as mechanically enforced today. */
|
|
54
|
-
export declare const MECHANICAL_KINDS: EnforcementKind[];
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The enforcement registry — the record of how every documented requirement is
|
|
3
|
-
* checked.
|
|
4
|
-
*
|
|
5
|
-
* A requirement is identified by the hash of its canonical text, not by a
|
|
6
|
-
* hand-written id. Rewording a requirement therefore changes its hash, which
|
|
7
|
-
* makes `pasika coverage` report it as changed until someone re-confirms that
|
|
8
|
-
* the recorded enforcement still covers it.
|
|
9
|
-
*/
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
export const enforcementKindSchema = z.enum([
|
|
12
|
-
/** An ESLint rule reports it. */
|
|
13
|
-
"eslint",
|
|
14
|
-
/** A `pasika doctor` check reports it. */
|
|
15
|
-
"doctor",
|
|
16
|
-
/** No mechanical check can decide it; a reviewer or agent applies it. */
|
|
17
|
-
"judgment",
|
|
18
|
-
/** The requirement grants permission, so there is nothing to check. */
|
|
19
|
-
"permission",
|
|
20
|
-
/** A mechanical check is possible but not written yet. */
|
|
21
|
-
"planned",
|
|
22
|
-
]);
|
|
23
|
-
export const requirementSchema = z.object({
|
|
24
|
-
/** Document path relative to the docs root. */
|
|
25
|
-
doc: z.string(),
|
|
26
|
-
/** Canonical requirement text: links flattened, code spans unwrapped, whitespace collapsed. */
|
|
27
|
-
text: z.string(),
|
|
28
|
-
/** Short hash of `text`. */
|
|
29
|
-
hash: z.string(),
|
|
30
|
-
kind: enforcementKindSchema,
|
|
31
|
-
/**
|
|
32
|
-
* Identifier of the check that covers this requirement: an ESLint rule id or
|
|
33
|
-
* a doctor check id. Several ids are comma-separated.
|
|
34
|
-
* Absent for judgment and permission.
|
|
35
|
-
*/
|
|
36
|
-
ref: z.string().optional(),
|
|
37
|
-
/**
|
|
38
|
-
* For `judgment`, why no mechanical check can decide it. For `planned`, the
|
|
39
|
-
* check that should cover it. For `eslint` and `doctor`, what the existing
|
|
40
|
-
* check does not cover.
|
|
41
|
-
*/
|
|
42
|
-
note: z.string().optional(),
|
|
43
|
-
});
|
|
44
|
-
export const registrySchema = z.object({
|
|
45
|
-
requirements: z.array(requirementSchema),
|
|
46
|
-
});
|
|
47
|
-
/** Every kind, in the order the coverage summary prints them. */
|
|
48
|
-
export const ENFORCEMENT_KINDS = ["eslint", "doctor", "planned", "judgment", "permission"];
|
|
49
|
-
/** The kinds that count as mechanically enforced today. */
|
|
50
|
-
export const MECHANICAL_KINDS = ["eslint", "doctor"];
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local AST node types for the parser-specific shapes ESLint's ESTree types
|
|
3
|
-
* do not carry: JSX nodes and TypeScript type nodes.
|
|
4
|
-
*
|
|
5
|
-
* Every extra field is optional so that any `Rule.Node` is assignable to these
|
|
6
|
-
* types — the handler parameters of the rules must accept whatever the parser
|
|
7
|
-
* hands them. Narrowing happens with `type`-discriminant checks inside each
|
|
8
|
-
* rule, exactly as in `no-arbitrary-tailwind.ts`.
|
|
9
|
-
*/
|
|
10
|
-
import type { Rule } from "eslint";
|
|
11
|
-
import type * as ESTree from "estree";
|
|
12
|
-
/** JSXIdentifier — the parser-specific name node of a JSX element or attribute. */
|
|
13
|
-
export interface JsxIdentifier {
|
|
14
|
-
type: "JSXIdentifier";
|
|
15
|
-
name: string;
|
|
16
|
-
}
|
|
17
|
-
/** JSXMemberExpression — `<Foo.Bar>`, used by element and attribute names. */
|
|
18
|
-
export interface JsxMemberExpression {
|
|
19
|
-
type: "JSXMemberExpression";
|
|
20
|
-
object: JsxIdentifier | JsxMemberExpression;
|
|
21
|
-
property: JsxIdentifier;
|
|
22
|
-
}
|
|
23
|
-
/** JSXExpressionContainer — the `{expression}` part of a JSX attribute value. */
|
|
24
|
-
export interface JsxExpressionContainer {
|
|
25
|
-
type: "JSXExpressionContainer";
|
|
26
|
-
expression: ESTree.Expression;
|
|
27
|
-
}
|
|
28
|
-
/** JSXAttribute — `name="value"` or `name={expression}`. */
|
|
29
|
-
export type JsxAttribute = Omit<Rule.Node, "type"> & {
|
|
30
|
-
type: string;
|
|
31
|
-
name: JsxIdentifier;
|
|
32
|
-
value: ESTree.Literal | JsxExpressionContainer | null;
|
|
33
|
-
parent?: JsxOpeningElementNode;
|
|
34
|
-
};
|
|
35
|
-
/** JSXSpreadAttribute — `{...props}`. */
|
|
36
|
-
export interface JsxSpreadAttribute {
|
|
37
|
-
type: "JSXSpreadAttribute";
|
|
38
|
-
name?: never;
|
|
39
|
-
value?: never;
|
|
40
|
-
argument: ESTree.Expression;
|
|
41
|
-
}
|
|
42
|
-
/** Anything that can appear inside `attributes` of a JSX opening element. */
|
|
43
|
-
export type JsxAttributeLike = JsxAttribute | JsxSpreadAttribute;
|
|
44
|
-
/** JSXOpeningElement — the part before the closing `>` of an element. */
|
|
45
|
-
export interface JsxOpeningElement {
|
|
46
|
-
type: "JSXOpeningElement";
|
|
47
|
-
name: JsxIdentifier | JsxMemberExpression;
|
|
48
|
-
attributes: JsxAttributeLike[];
|
|
49
|
-
}
|
|
50
|
-
/** A node that may be a JSX element — used for parents and helper parameters. */
|
|
51
|
-
export type MaybeJsxElement = Rule.Node & {
|
|
52
|
-
openingElement?: JsxOpeningElement;
|
|
53
|
-
};
|
|
54
|
-
/** JSXElement as received by a `JSXElement` visitor. */
|
|
55
|
-
export type JsxElementNode = MaybeJsxElement & {
|
|
56
|
-
parent?: MaybeJsxElement;
|
|
57
|
-
};
|
|
58
|
-
/** JSXAttribute as received by a `JSXAttribute` visitor, with its parent chain. */
|
|
59
|
-
export type JsxAttributeNode = Rule.Node & {
|
|
60
|
-
name?: JsxIdentifier;
|
|
61
|
-
value?: ESTree.Literal | JsxExpressionContainer | null;
|
|
62
|
-
parent?: JsxOpeningElementNode;
|
|
63
|
-
};
|
|
64
|
-
/** JSXOpeningElement reached through a parent chain. */
|
|
65
|
-
export type JsxOpeningElementNode = Rule.Node & {
|
|
66
|
-
name?: JsxIdentifier;
|
|
67
|
-
attributes?: JsxAttributeLike[];
|
|
68
|
-
parent?: JsxElementNode;
|
|
69
|
-
};
|
|
70
|
-
/**
|
|
71
|
-
* FunctionDeclaration as seen by a rule visitor. ESLint's ESTree typing says
|
|
72
|
-
* `id` is always present, but an anonymous `export default function () {}`
|
|
73
|
-
* reaches the visitor with `id: null`, so the field is kept nullable.
|
|
74
|
-
*/
|
|
75
|
-
export type FunctionDeclarationNode = Rule.Node & {
|
|
76
|
-
id?: {
|
|
77
|
-
name?: string;
|
|
78
|
-
} | null;
|
|
79
|
-
body?: ESTree.BlockStatement;
|
|
80
|
-
};
|
|
81
|
-
/**
|
|
82
|
-
* TSTypeAliasDeclaration — `type X = ...`, from the typescript-eslint parser.
|
|
83
|
-
*
|
|
84
|
-
* Kept as a plain `Rule.Node` intersection so it can be passed to
|
|
85
|
-
* `context.report`, which requires an ESTree node.
|
|
86
|
-
*/
|
|
87
|
-
export type TsTypeAliasDeclarationNode = Rule.Node & {
|
|
88
|
-
id?: {
|
|
89
|
-
type?: string;
|
|
90
|
-
name?: string;
|
|
91
|
-
};
|
|
92
|
-
typeAnnotation?: TsTypeNode;
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* A member of a type literal (TSPropertySignature, TSIndexSignature, ...).
|
|
96
|
-
*
|
|
97
|
-
* The `type` field is widened (via Omit) so it can be compared against the
|
|
98
|
-
* parser-specific "TSPropertySignature" string that ESTree's union lacks.
|
|
99
|
-
*/
|
|
100
|
-
export type TsTypeElementNode = Omit<Rule.Node, "type"> & {
|
|
101
|
-
type: string;
|
|
102
|
-
key?: {
|
|
103
|
-
type?: string;
|
|
104
|
-
name?: string;
|
|
105
|
-
};
|
|
106
|
-
typeAnnotation?: TsTypeAnnotationNode;
|
|
107
|
-
};
|
|
108
|
-
/** TSTypeAnnotation — the `: T` wrapper around a type node. */
|
|
109
|
-
export type TsTypeAnnotationNode = Omit<Rule.Node, "type"> & {
|
|
110
|
-
type: string;
|
|
111
|
-
typeAnnotation?: TsTypeNode;
|
|
112
|
-
};
|
|
113
|
-
/** Any TypeScript type node the rules read (TSTypeLiteral, TSUnionType, ...). */
|
|
114
|
-
export type TsTypeNode = Omit<Rule.Node, "type"> & {
|
|
115
|
-
type: string;
|
|
116
|
-
members?: TsTypeElementNode[];
|
|
117
|
-
types?: TsTypeNode[];
|
|
118
|
-
literal?: {
|
|
119
|
-
type?: string;
|
|
120
|
-
value?: unknown;
|
|
121
|
-
};
|
|
122
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import type { ProjectIndex } from "./index.js";
|
|
2
|
-
export declare const SUPPORT_FOLDERS: Set<string>;
|
|
3
|
-
/** Path of a file relative to the source root, as segments. */
|
|
4
|
-
export declare function segmentsOf(file: string, sourceRoot: string): string[];
|
|
5
|
-
/** Folder of a file relative to the source root, as segments. */
|
|
6
|
-
export declare function folderSegmentsOf(file: string, sourceRoot: string): string[];
|
|
7
|
-
export declare const isUnderApp: (segments: string[]) => boolean;
|
|
8
|
-
export declare const isConfigModule: (segments: string[]) => boolean;
|
|
9
|
-
export declare const isUnderCompositions: (segments: string[]) => boolean;
|
|
10
|
-
export interface ComponentPlacement {
|
|
11
|
-
/** Consumers that count toward the calculation, as absolute paths. */
|
|
12
|
-
countedConsumers: string[];
|
|
13
|
-
/** Folder the component belongs in, relative to the source root. */
|
|
14
|
-
expectedFolder: string[];
|
|
15
|
-
/** Why the expected folder is what it is, for the report. */
|
|
16
|
-
reason: "ccf" | "across-features" | "across-layers";
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Resolves where a component belongs from the files that import it.
|
|
20
|
-
*
|
|
21
|
-
* Imports from `src/app/` and from configuration modules drop out, consumers
|
|
22
|
-
* under `src/compositions/` count only when every consumer is there, and a
|
|
23
|
-
* result of `src/features/` becomes `src/shared/` because no feature may import
|
|
24
|
-
* from another. Returns undefined when no consumer counts, which is the case the
|
|
25
|
-
* "lives in the feature it represents" requirement covers instead.
|
|
26
|
-
*/
|
|
27
|
-
export declare function resolveComponentPlacement(componentFile: string, index: ProjectIndex): ComponentPlacement | undefined;
|
|
28
|
-
export declare const formatFolder: (folder: string[]) => string;
|
|
29
|
-
export interface SupportPlacement {
|
|
30
|
-
countedConsumers: string[];
|
|
31
|
-
expectedFolder: string[];
|
|
32
|
-
reason: "app-consumer" | "config-module" | "ccf" | "across-features" | "across-layers";
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolves where a support file belongs from the files that import it.
|
|
36
|
-
*
|
|
37
|
-
* A consumer inside a support folder is owned by that folder's parent, so the
|
|
38
|
-
* calculation lands on the scope that uses the file rather than on a sibling
|
|
39
|
-
* support folder. A consumer under `src/app/` forces the root support folder, a
|
|
40
|
-
* set of consumers inside one configuration module keeps the file in that module,
|
|
41
|
-
* and consumers spanning features land in the root support folder.
|
|
42
|
-
*/
|
|
43
|
-
export declare function resolveSupportPlacement(supportFile: string, supportFolder: string, index: ProjectIndex): SupportPlacement | undefined;
|
|
44
|
-
/**
|
|
45
|
-
* Consumers for a message. A widely used file can have dozens, so the list is
|
|
46
|
-
* capped: the point is to show where the requirement comes from, not to enumerate.
|
|
47
|
-
*/
|
|
48
|
-
export declare function describeConsumers(consumers: string[], sourceRoot: string): string;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
export const SUPPORT_FOLDERS = new Set(["hooks", "types", "schemas", "constants", "utils"]);
|
|
3
|
-
/** Path of a file relative to the source root, as segments. */
|
|
4
|
-
export function segmentsOf(file, sourceRoot) {
|
|
5
|
-
const relative = path.relative(sourceRoot, file);
|
|
6
|
-
return relative.startsWith("..") ? [] : relative.split(path.sep);
|
|
7
|
-
}
|
|
8
|
-
/** Folder of a file relative to the source root, as segments. */
|
|
9
|
-
export function folderSegmentsOf(file, sourceRoot) {
|
|
10
|
-
return segmentsOf(file, sourceRoot).slice(0, -1);
|
|
11
|
-
}
|
|
12
|
-
export const isUnderApp = (segments) => segments[0] === "app";
|
|
13
|
-
export const isConfigModule = (segments) => segments[0] === "config";
|
|
14
|
-
export const isUnderCompositions = (segments) => segments[0] === "compositions";
|
|
15
|
-
/** The longest folder prefix every path shares. */
|
|
16
|
-
function commonPrefix(folders) {
|
|
17
|
-
if (folders.length === 0)
|
|
18
|
-
return [];
|
|
19
|
-
const [first = []] = folders;
|
|
20
|
-
const shared = [];
|
|
21
|
-
for (const [depth, segment] of first.entries()) {
|
|
22
|
-
if (!folders.every((folder) => folder[depth] === segment))
|
|
23
|
-
break;
|
|
24
|
-
shared.push(segment);
|
|
25
|
-
}
|
|
26
|
-
return shared;
|
|
27
|
-
}
|
|
28
|
-
/** Walks out of any trailing support folders, since a component never lives in one. */
|
|
29
|
-
function outOfSupportFolders(folder) {
|
|
30
|
-
const result = [...folder];
|
|
31
|
-
while (result.length > 0 && SUPPORT_FOLDERS.has(result[result.length - 1] ?? ""))
|
|
32
|
-
result.pop();
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Resolves where a component belongs from the files that import it.
|
|
37
|
-
*
|
|
38
|
-
* Imports from `src/app/` and from configuration modules drop out, consumers
|
|
39
|
-
* under `src/compositions/` count only when every consumer is there, and a
|
|
40
|
-
* result of `src/features/` becomes `src/shared/` because no feature may import
|
|
41
|
-
* from another. Returns undefined when no consumer counts, which is the case the
|
|
42
|
-
* "lives in the feature it represents" requirement covers instead.
|
|
43
|
-
*/
|
|
44
|
-
export function resolveComponentPlacement(componentFile, index) {
|
|
45
|
-
const consumers = [...(index.consumers.get(componentFile) ?? [])];
|
|
46
|
-
const relevant = consumers.filter((consumer) => {
|
|
47
|
-
const segments = segmentsOf(consumer, index.sourceRoot);
|
|
48
|
-
return segments.length > 0 && !isUnderApp(segments) && !isConfigModule(segments);
|
|
49
|
-
});
|
|
50
|
-
if (relevant.length === 0)
|
|
51
|
-
return undefined;
|
|
52
|
-
const outsideCompositions = relevant.filter((consumer) => !isUnderCompositions(segmentsOf(consumer, index.sourceRoot)));
|
|
53
|
-
const counted = outsideCompositions.length > 0 ? outsideCompositions : relevant;
|
|
54
|
-
const shared = outOfSupportFolders(commonPrefix(counted.map((consumer) => folderSegmentsOf(consumer, index.sourceRoot))));
|
|
55
|
-
// A shared folder of `features` or of the source root itself means no single
|
|
56
|
-
// feature owns the component, so it belongs to the shared layer.
|
|
57
|
-
if (shared.length === 1 && shared[0] === "features") {
|
|
58
|
-
return { countedConsumers: counted, expectedFolder: ["shared"], reason: "across-features" };
|
|
59
|
-
}
|
|
60
|
-
if (shared.length === 0) {
|
|
61
|
-
return { countedConsumers: counted, expectedFolder: ["shared"], reason: "across-layers" };
|
|
62
|
-
}
|
|
63
|
-
return { countedConsumers: counted, expectedFolder: shared, reason: "ccf" };
|
|
64
|
-
}
|
|
65
|
-
export const formatFolder = (folder) => `src/${folder.join("/")}/`;
|
|
66
|
-
/** The folder that owns a consumer: its own folder, stepped out of any support folder. */
|
|
67
|
-
function owningFolderOf(consumer, sourceRoot) {
|
|
68
|
-
return outOfSupportFolders(folderSegmentsOf(consumer, sourceRoot));
|
|
69
|
-
}
|
|
70
|
-
/** The configuration module a file belongs to. `config/<name>/...` only: `config/<file>.ts` is not a module. */
|
|
71
|
-
const configModuleOf = (segments) => isConfigModule(segments) && segments.length >= 3 ? segments[1] : undefined;
|
|
72
|
-
/**
|
|
73
|
-
* Resolves where a support file belongs from the files that import it.
|
|
74
|
-
*
|
|
75
|
-
* A consumer inside a support folder is owned by that folder's parent, so the
|
|
76
|
-
* calculation lands on the scope that uses the file rather than on a sibling
|
|
77
|
-
* support folder. A consumer under `src/app/` forces the root support folder, a
|
|
78
|
-
* set of consumers inside one configuration module keeps the file in that module,
|
|
79
|
-
* and consumers spanning features land in the root support folder.
|
|
80
|
-
*/
|
|
81
|
-
export function resolveSupportPlacement(supportFile, supportFolder, index) {
|
|
82
|
-
const consumers = [...(index.consumers.get(supportFile) ?? [])].filter((consumer) => segmentsOf(consumer, index.sourceRoot).length > 0);
|
|
83
|
-
if (consumers.length === 0)
|
|
84
|
-
return undefined;
|
|
85
|
-
const consumerSegments = consumers.map((consumer) => segmentsOf(consumer, index.sourceRoot));
|
|
86
|
-
if (consumerSegments.some((segments) => isUnderApp(segments))) {
|
|
87
|
-
return { countedConsumers: consumers, expectedFolder: [supportFolder], reason: "app-consumer" };
|
|
88
|
-
}
|
|
89
|
-
const configModules = new Set(consumerSegments.map((segments) => configModuleOf(segments)));
|
|
90
|
-
const [onlyConfigModule] = [...configModules];
|
|
91
|
-
if (configModules.size === 1 && onlyConfigModule !== undefined) {
|
|
92
|
-
return {
|
|
93
|
-
countedConsumers: consumers,
|
|
94
|
-
expectedFolder: ["config", onlyConfigModule, supportFolder],
|
|
95
|
-
reason: "config-module",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
const shared = commonPrefix(consumers.map((consumer) => owningFolderOf(consumer, index.sourceRoot)));
|
|
99
|
-
if (shared.length === 1 && shared[0] === "features") {
|
|
100
|
-
return { countedConsumers: consumers, expectedFolder: [supportFolder], reason: "across-features" };
|
|
101
|
-
}
|
|
102
|
-
if (shared.length === 0) {
|
|
103
|
-
return { countedConsumers: consumers, expectedFolder: [supportFolder], reason: "across-layers" };
|
|
104
|
-
}
|
|
105
|
-
return { countedConsumers: consumers, expectedFolder: [...shared, supportFolder], reason: "ccf" };
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Consumers for a message. A widely used file can have dozens, so the list is
|
|
109
|
-
* capped: the point is to show where the requirement comes from, not to enumerate.
|
|
110
|
-
*/
|
|
111
|
-
export function describeConsumers(consumers, sourceRoot) {
|
|
112
|
-
const shown = 3;
|
|
113
|
-
const names = consumers
|
|
114
|
-
.map((consumer) => path.relative(path.dirname(sourceRoot), consumer).split(path.sep).join("/"))
|
|
115
|
-
.sort((left, right) => left.localeCompare(right));
|
|
116
|
-
if (names.length <= shown)
|
|
117
|
-
return names.join(", ");
|
|
118
|
-
return `${names.slice(0, shown).join(", ")} and ${String(names.length - shown)} more`;
|
|
119
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type ParsedModule } from "./parse-module.js";
|
|
2
|
-
export interface ProjectIndex {
|
|
3
|
-
sourceRoot: string;
|
|
4
|
-
/** Absolute file path to its parsed module. */
|
|
5
|
-
modules: Map<string, ParsedModule>;
|
|
6
|
-
/** Absolute file path to the files that import it. */
|
|
7
|
-
consumers: Map<string, Set<string>>;
|
|
8
|
-
/** `file` and name joined by a NUL, to the files that import that name from it. */
|
|
9
|
-
symbolConsumers: Map<string, Set<string>>;
|
|
10
|
-
}
|
|
11
|
-
export declare const symbolKey: (file: string, name: string) => string;
|
|
12
|
-
/** Resolves an import specifier to a file inside the source tree, if it points at one. */
|
|
13
|
-
export declare function resolveSpecifier(fromFile: string, specifier: string, sourceRoot: string): string | undefined;
|
|
14
|
-
/**
|
|
15
|
-
* The project index for a source tree, rebuilt only when the tree changed.
|
|
16
|
-
* Returns undefined when the tree does not exist, which is how a repository
|
|
17
|
-
* without a `src/` folder opts out of every cross-file rule.
|
|
18
|
-
*/
|
|
19
|
-
export declare function getProjectIndex(sourceRoot: string): ProjectIndex | undefined;
|
|
20
|
-
/** Drops the memoized index. Used by tests that write a fresh tree per case. */
|
|
21
|
-
export declare function clearProjectIndex(): void;
|