@aoelite/refpact-ts 0.0.0-stage → 0.1.2

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/LICENSE +21 -0
  2. package/README.md +38 -2
  3. package/dist/baseline.d.ts +26 -0
  4. package/dist/baseline.js +65 -0
  5. package/dist/core/index.d.ts +79 -0
  6. package/dist/core/index.js +90 -0
  7. package/dist/core/model.d.ts +340 -0
  8. package/dist/core/model.js +1 -0
  9. package/dist/core/results.d.ts +237 -0
  10. package/dist/core/results.js +1 -0
  11. package/dist/core/wasm.d.ts +7 -0
  12. package/dist/core/wasm.js +67 -0
  13. package/dist/custom.d.ts +129 -0
  14. package/dist/custom.js +217 -0
  15. package/dist/filters.d.ts +73 -0
  16. package/dist/filters.js +39 -0
  17. package/dist/frontend/checks.d.ts +15 -0
  18. package/dist/frontend/checks.js +12 -0
  19. package/dist/frontend/classic.d.ts +3 -0
  20. package/dist/frontend/classic.js +105 -0
  21. package/dist/frontend/compiler.d.ts +93 -0
  22. package/dist/frontend/compiler.js +116 -0
  23. package/dist/frontend/components.d.ts +29 -0
  24. package/dist/frontend/components.js +111 -0
  25. package/dist/frontend/declarations.d.ts +45 -0
  26. package/dist/frontend/declarations.js +158 -0
  27. package/dist/frontend/dependencies.d.ts +53 -0
  28. package/dist/frontend/dependencies.js +337 -0
  29. package/dist/frontend/imports.d.ts +149 -0
  30. package/dist/frontend/imports.js +765 -0
  31. package/dist/frontend/native.d.ts +3 -0
  32. package/dist/frontend/native.js +165 -0
  33. package/dist/frontend/paths.d.ts +10 -0
  34. package/dist/frontend/paths.js +18 -0
  35. package/dist/frontend/project.d.ts +118 -0
  36. package/dist/frontend/project.js +775 -0
  37. package/dist/group.d.ts +74 -0
  38. package/dist/group.js +106 -0
  39. package/dist/index.d.ts +35 -0
  40. package/dist/index.js +27 -0
  41. package/dist/items.d.ts +133 -0
  42. package/dist/items.js +179 -0
  43. package/dist/layers.d.ts +112 -0
  44. package/dist/layers.js +215 -0
  45. package/dist/onion.d.ts +78 -0
  46. package/dist/onion.js +96 -0
  47. package/dist/patterns.d.ts +14 -0
  48. package/dist/patterns.js +15 -0
  49. package/dist/rules.d.ts +408 -0
  50. package/dist/rules.js +655 -0
  51. package/package.json +52 -4
  52. package/wasm/refpact_core.wasm +0 -0
@@ -0,0 +1,237 @@
1
+ /** What the core gives back: violations, errors, and baselines (SPEC sections 4, 5 and 7). */
2
+ import type { Certainty, FactSet, Path } from "./model.ts";
3
+ /** The violations in fact order, or the reasons why the rule cannot be evaluated. */
4
+ export type EvaluateResult = {
5
+ readonly ok: true;
6
+ /** The violations in fact order. May be empty. */
7
+ readonly violations: readonly Violation[];
8
+ } | {
9
+ readonly ok: false;
10
+ /** The reasons, in the order of the specification. */
11
+ readonly errors: readonly RuleError[];
12
+ };
13
+ /** One place where the code breaks a rule. */
14
+ export interface Violation {
15
+ /** The name of the rule. */
16
+ readonly rule: string;
17
+ /** The unit that breaks the rule. */
18
+ readonly unit: Path;
19
+ /**
20
+ * The file of the place. For a violation about a unit as a whole, the file of the first
21
+ * fact of the unit, or empty.
22
+ */
23
+ readonly file: string;
24
+ /** The line, or 0 for a violation about a unit as a whole. */
25
+ readonly line: number;
26
+ /** The column, or 0 for a violation about a unit as a whole. */
27
+ readonly column: number;
28
+ /** The note of the fact, when it has one. */
29
+ readonly note?: string;
30
+ /** What the unit does. */
31
+ readonly detail: Detail;
32
+ }
33
+ /**
34
+ * What a violation is about.
35
+ *
36
+ * - `uses`: the unit refers to a path that the rule does not allow.
37
+ * - `contains`: the unit contains a marker that the rule forbids.
38
+ * - `cycle`: one cycle of a tangle, which is a group of nodes that all reach each other.
39
+ * - `unused`: nothing outside the unit and the units below it refers to it.
40
+ * - `reaches`: a chain of references leads from the unit to a forbidden target.
41
+ * - `unreachable`: no chain of references leads from a root to the unit.
42
+ * - `outside`: the unit is in none of the places of the rule.
43
+ * - `item`: a declaration that does not meet a rule about items.
44
+ * - `custom`: what a check of the user found.
45
+ */
46
+ export type Detail = {
47
+ readonly type: "uses";
48
+ /** For a rule about items: the item whose code has the reference. */
49
+ readonly item?: Path;
50
+ /** The forbidden path. */
51
+ readonly target: Path;
52
+ /** The path as the code names it, when it differs from `target`. */
53
+ readonly written?: Path;
54
+ /** The kind of the reference. */
55
+ readonly kind: string;
56
+ /** Whether the front end proved the path. */
57
+ readonly certainty: Certainty;
58
+ } | {
59
+ readonly type: "contains";
60
+ /** The marker. */
61
+ readonly marker: string;
62
+ } | {
63
+ readonly type: "cycle";
64
+ /** The edges of the cycle, in order. The last step leads back to the first node. */
65
+ readonly steps: readonly Step[];
66
+ /** The number of nodes in the tangle. */
67
+ readonly size: number;
68
+ } | {
69
+ readonly type: "unused";
70
+ } | {
71
+ readonly type: "reaches";
72
+ /** The edges from the unit to the unit that uses the target. Empty when the unit uses the target itself. */
73
+ readonly steps: readonly Step[];
74
+ /** The use of the target at the end of the chain. */
75
+ readonly end: End;
76
+ } | {
77
+ readonly type: "unreachable";
78
+ } | {
79
+ readonly type: "outside";
80
+ } | {
81
+ readonly type: "item";
82
+ /** The path of the item. */
83
+ readonly item: Path;
84
+ /** The kind of the item. */
85
+ readonly kind: string;
86
+ } | {
87
+ /** The unit, or one declaration of it, has more lines than the rule allows (SPEC 4.16). */
88
+ readonly type: "too-long";
89
+ /** For a rule about items: the path of the item. */
90
+ readonly item?: Path;
91
+ /** For a rule about items: the kind of the item. */
92
+ readonly kind?: string;
93
+ /** The number of lines. */
94
+ readonly lines: number;
95
+ /** The number of lines that the rule allows. */
96
+ readonly limit: number;
97
+ } | {
98
+ readonly type: "custom";
99
+ /** What the unit does, printed after the unit. */
100
+ readonly message: string;
101
+ /** What a baseline counts the violation as. Absent: the message. */
102
+ readonly key?: string;
103
+ };
104
+ /** What a rule sees (SPEC section 8), or the reasons why the rule cannot be evaluated. Units, facts and declarations are given by their position in the fact set. */
105
+ export type SelectResult = {
106
+ /** The rule can be evaluated. */
107
+ readonly ok: true;
108
+ /** The units that the rule selects. */
109
+ readonly subjects: readonly number[];
110
+ /** For each list of unit patterns of the check: the units that it matches. */
111
+ readonly units: readonly (readonly number[])[];
112
+ /** The references that the rule sees, of all units, in the order of the facts. */
113
+ readonly references: readonly SelectedReference[];
114
+ /** The markers of the subjects that the rule sees. */
115
+ readonly markers: readonly number[];
116
+ /** The declarations of the subjects that the rule sees. */
117
+ readonly declarations: readonly number[];
118
+ /** For a rule about items: the declarations that its filters select. */
119
+ readonly items?: readonly number[];
120
+ } | {
121
+ /** The rule cannot be evaluated. */
122
+ readonly ok: false;
123
+ /** The reasons, in the order of the specification. */
124
+ readonly errors: readonly RuleError[];
125
+ };
126
+ /** A reference that a rule sees. */
127
+ export interface SelectedReference {
128
+ /** The position of the fact. */
129
+ readonly fact: number;
130
+ /** The unit of the fact. */
131
+ readonly unit: number;
132
+ /** For each path of the reference, `[target, ...aliases]`: the unit that owns it, or `null`. */
133
+ readonly owners: readonly (number | null)[];
134
+ /**
135
+ * For each list of targets of the check: the first path that it matches, by its place in
136
+ * `[target, ...aliases]`, or `null`. Absent when no list matches a path.
137
+ */
138
+ readonly hits?: readonly (number | null)[];
139
+ /** For a rule about items: the reference is inside an item that the filters select. */
140
+ readonly inside?: true;
141
+ }
142
+ /** The reference at the end of a chain: the use of the forbidden target. */
143
+ export interface End {
144
+ /** The unit that uses the target. */
145
+ readonly unit: Path;
146
+ /** The file of the use. */
147
+ readonly file: string;
148
+ /** The line of the use. */
149
+ readonly line: number;
150
+ /** The column of the use. */
151
+ readonly column: number;
152
+ /** The forbidden path. */
153
+ readonly target: Path;
154
+ /** The kind of the reference. */
155
+ readonly kind: string;
156
+ }
157
+ /** One edge of a cycle or of a chain, with the reference that makes it. */
158
+ export interface Step {
159
+ /** The node the edge leaves. */
160
+ readonly from: Path;
161
+ /** The node the edge leads to. */
162
+ readonly to: Path;
163
+ /** The file of the reference. */
164
+ readonly file: string;
165
+ /** The line of the reference. */
166
+ readonly line: number;
167
+ /** The column of the reference. */
168
+ readonly column: number;
169
+ /** The path the reference names. */
170
+ readonly target: Path;
171
+ /** The kind of the reference. */
172
+ readonly kind: string;
173
+ }
174
+ /** Why a rule cannot be evaluated (SPEC section 5). */
175
+ export interface RuleError {
176
+ /** The check that failed. */
177
+ readonly code: ErrorCode;
178
+ /** The part of the rule or of the fact set that the error is about, when there is one. */
179
+ readonly role?: Role;
180
+ /** The offending value, such as a pattern, when there is one. */
181
+ readonly value?: string;
182
+ }
183
+ /** The check that failed (SPEC section 5). */
184
+ export type ErrorCode = "invalid-facts" | "empty-name" | "empty-list" | "invalid-pattern" | "pattern-matches-nothing" | "no-subjects" | "too-few-nodes" | "unknown-marker" | "unknown-kind" | "unknown-tag" | "unused-exemption" | "unknown-item-kind" | "unknown-visibility" | "unknown-relation" | "unknown-modifier" | "unknown-property";
185
+ /** The part of a rule or of a fact set that an error is about. */
186
+ export type Role = "unit" | "subject" | "subject-exception" | "target" | "unverified-target" | "marker" | "slice" | "root" | "kind" | "tag" | "place" | "user" | "exemption-from" | "exemption-to" | "exemption" | "name" | "item-kind" | "visibility" | "relation" | "related" | "required-name" | "required" | "modifier" | "required-modifier" | "layer" | "property";
187
+ /**
188
+ * How reports and baselines print paths (SPEC section 3). A fact set is a path format, so a
189
+ * report can take the fact set itself.
190
+ */
191
+ export type PathFormat = Pick<FactSet, "separator" | "attachedPrefixes" | "hiddenPrefixes">;
192
+ /** One line of a baseline: `count` accepted violations with one key. */
193
+ export interface BaselineEntry {
194
+ /** The name of the rule. */
195
+ readonly rule: string;
196
+ /** The unit, printed as in reports. */
197
+ readonly unit: string;
198
+ /** What the violations are, as SPEC section 7 writes it for each kind of violation. */
199
+ readonly what: string;
200
+ /** The number of accepted violations with this key. */
201
+ readonly count: number;
202
+ }
203
+ /** An entry that accepts more violations than the code has. */
204
+ export interface StaleEntry {
205
+ /** The unit of the entry. */
206
+ readonly unit: string;
207
+ /** What the entry accepts, as in {@link BaselineEntry.what}. */
208
+ readonly what: string;
209
+ /** The number of violations that the baseline accepts for this key. */
210
+ readonly count: number;
211
+ /** The number of violations that the code has. */
212
+ readonly found: number;
213
+ }
214
+ /** The entries, or the number of the first invalid line, from 1. */
215
+ export type ParsedBaseline = {
216
+ readonly ok: true;
217
+ /** The entries, in the order of the text. */
218
+ readonly entries: readonly BaselineEntry[];
219
+ } | {
220
+ readonly ok: false;
221
+ /** The number of the first invalid line, from 1. */
222
+ readonly line: number;
223
+ };
224
+ /** The result of `applyBaseline`. */
225
+ export interface Applied {
226
+ /** The violations that the baseline does not accept. */
227
+ readonly remaining: readonly Violation[];
228
+ /** The entries that accept more violations than the code has. */
229
+ readonly stale: readonly StaleEntry[];
230
+ }
231
+ /** The key of a violation in a baseline. */
232
+ export interface BaselineKey {
233
+ /** The unit, printed as in reports. */
234
+ readonly unit: string;
235
+ /** What the violation is, as SPEC section 7 writes it. */
236
+ readonly what: string;
237
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ /** The instance that the handles of fact sets belong to. */
2
+ export declare function currentGeneration(): number;
3
+ /**
4
+ * Sends one request and returns the answer as JSON text. Throws when the core finds the
5
+ * request not valid, which is a bug of this package and not of the rule.
6
+ */
7
+ export declare function call(request: string): string;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The core of refpact, the Rust crate `refpact-core`, as WebAssembly (SPEC section 8).
3
+ *
4
+ * The module is `wasm/refpact_core.wasm` beside `src` and `dist`, built by `cargo xtask wasm`.
5
+ * It is compiled once, on the first call, and speaks JSON requests. This is the only file of
6
+ * the core that reads a file.
7
+ */
8
+ import { readFileSync } from "node:fs";
9
+ import { fileURLToPath } from "node:url";
10
+ /** The version of the wire format that this package speaks. */
11
+ const VERSION = 1;
12
+ let core;
13
+ /** Counts the instances. A handle of a fact set is valid only in the instance that made it. */
14
+ let generation = 0;
15
+ const encoder = new TextEncoder();
16
+ const decoder = new TextDecoder();
17
+ function instance() {
18
+ if (core !== undefined) {
19
+ return core;
20
+ }
21
+ const url = new URL("../../wasm/refpact_core.wasm", import.meta.url);
22
+ let bytes;
23
+ try {
24
+ bytes = readFileSync(url);
25
+ }
26
+ catch (cause) {
27
+ throw new Error(`refpact: the core is missing at ${fileURLToPath(url)}. Build it with \`cargo xtask wasm\`.`, { cause });
28
+ }
29
+ const made = new WebAssembly.Instance(new WebAssembly.Module(bytes), {}).exports;
30
+ core = made;
31
+ generation += 1;
32
+ const { version } = JSON.parse(call('{"op":"version"}'));
33
+ if (version !== VERSION) {
34
+ core = undefined;
35
+ throw new Error(`refpact: the core speaks version ${version} of the wire format, not ${VERSION}. Build it again with \`cargo xtask wasm\`.`);
36
+ }
37
+ return made;
38
+ }
39
+ /** The instance that the handles of fact sets belong to. */
40
+ export function currentGeneration() {
41
+ instance();
42
+ return generation;
43
+ }
44
+ /**
45
+ * Sends one request and returns the answer as JSON text. Throws when the core finds the
46
+ * request not valid, which is a bug of this package and not of the rule.
47
+ */
48
+ export function call(request) {
49
+ const exports = instance();
50
+ const bytes = encoder.encode(request);
51
+ const address = exports.refpact_alloc(bytes.length);
52
+ new Uint8Array(exports.memory.buffer, address, bytes.length).set(bytes);
53
+ let status;
54
+ try {
55
+ status = exports.refpact_call(address, bytes.length);
56
+ }
57
+ catch (error) {
58
+ // A trap leaves the instance in an unknown state. The next call starts a new one.
59
+ core = undefined;
60
+ throw error;
61
+ }
62
+ const answer = decoder.decode(new Uint8Array(exports.memory.buffer, exports.refpact_answer(), exports.refpact_answer_len()));
63
+ if (status !== 0) {
64
+ throw new Error(`refpact: the core rejected a request: ${answer}`);
65
+ }
66
+ return answer;
67
+ }
@@ -0,0 +1,129 @@
1
+ /** Checks of the user: what a rule sees, and how its violations are made (SPEC 4.15). */
2
+ import type { Declaration, Detail, Fact, FactSet, Path, Reference, SelectResult, Step, Violation } from "./core/index.ts";
3
+ /** An import that the rule sees: the filters and the exemptions of the rule let it pass. */
4
+ export interface SeenImport {
5
+ /** The fact of the import. */
6
+ readonly fact: Fact;
7
+ /** The reference of the fact. */
8
+ readonly reference: Reference;
9
+ /** The file that imports. */
10
+ readonly file: Path;
11
+ /** Whether the rule selects the file that imports. */
12
+ readonly subject: boolean;
13
+ /** The paths of the import, `[target, ...aliases]`. */
14
+ readonly paths: readonly Path[];
15
+ /** For each path, the file that has it, or `undefined` for a package or a module of Node. */
16
+ readonly owners: readonly (Path | undefined)[];
17
+ /**
18
+ * For each list of targets that the check names: the first path of the import that the
19
+ * list matches. A list that matches no path of the import has no entry.
20
+ */
21
+ readonly hits: Readonly<Record<string, Path>>;
22
+ }
23
+ /** A marker of a selected file that the rule sees. */
24
+ export interface SeenMarker {
25
+ /** The fact of the marker. */
26
+ readonly fact: Fact;
27
+ /** The file. */
28
+ readonly file: Path;
29
+ /** The marker. */
30
+ readonly marker: string;
31
+ }
32
+ /** An edge of the graph of files, with the first import that makes it. */
33
+ export interface ImportEdge {
34
+ /** The file that the edge leads to. */
35
+ readonly to: Path;
36
+ /** The fact of the import. */
37
+ readonly fact: Fact;
38
+ /** The path of the import that leads to the file. */
39
+ readonly target: Path;
40
+ }
41
+ /** The names of the lists of patterns of a check, in the order of the lists in the request. @internal */
42
+ export interface ListNames {
43
+ readonly targets: readonly string[];
44
+ readonly files: readonly string[];
45
+ }
46
+ type Selection = Extract<SelectResult, {
47
+ ok: true;
48
+ }>;
49
+ /**
50
+ * What a check of the user gets: the files that the rule selects and what the rule sees. The
51
+ * rule has applied its filters and its exemptions.
52
+ */
53
+ export declare class Scope {
54
+ /** The name of the rule. */
55
+ readonly ruleName: string;
56
+ /** The facts of the project. */
57
+ readonly facts: FactSet;
58
+ /** The files that the rule selects, in order. */
59
+ readonly subjects: readonly Path[];
60
+ /** The imports that the rule sees, of all files, in the order of the facts. */
61
+ readonly imports: readonly SeenImport[];
62
+ /** The markers of the selected files that the rule sees. */
63
+ readonly markers: readonly SeenMarker[];
64
+ /** The declarations of the selected files that the rule sees. */
65
+ readonly declarations: readonly Declaration[];
66
+ /** @internal */
67
+ protected readonly selection: Selection;
68
+ private readonly selected;
69
+ private readonly lists;
70
+ private firstFiles;
71
+ private made;
72
+ /** @internal */
73
+ constructor(ruleName: string, facts: FactSet, selection: Selection, names: ListNames);
74
+ /** Whether the rule selects the file. */
75
+ selects(file: Path): boolean;
76
+ /**
77
+ * The files that a list of file patterns of the check matches, by the name of the list.
78
+ * The files match as the files of a rule do. Throws when the check names no such list.
79
+ */
80
+ matched(list: string): readonly Path[];
81
+ /**
82
+ * The graph of all files, selected or not, from the imports that the rule sees. An import
83
+ * leads to the file of each of its paths, so an import through a barrel file leads to the
84
+ * barrel and to the file that declares the name. An import of the own file is no edge.
85
+ */
86
+ graph(): Graph;
87
+ /**
88
+ * A violation at the place of a fact, with its note. A message gives a `custom` detail,
89
+ * which a baseline counts by `key`, or by the message when there is no key.
90
+ */
91
+ violationAt(fact: Fact, detail: string | Detail, key?: string): Violation;
92
+ /** A violation at the place of a declaration. */
93
+ violationOfItem(declaration: Declaration, detail: string | Detail, baselineKey?: string): Violation;
94
+ /** A violation about a file as a whole, with no line. */
95
+ violationOfFile(file: Path, detail: string | Detail, baselineKey?: string): Violation;
96
+ }
97
+ /**
98
+ * What a check of items gets: the {@link Scope} of the rule, and the items that the filters
99
+ * of the rule select.
100
+ */
101
+ export declare class ItemScope extends Scope {
102
+ /** The items that the rule selects, in order. */
103
+ readonly items: readonly Declaration[];
104
+ /**
105
+ * The imports that the rule sees inside the selected items. A front end that does not say
106
+ * which item holds an import gives none: the TypeScript front end is one.
107
+ */
108
+ readonly itemImports: readonly SeenImport[];
109
+ /** @internal */
110
+ constructor(ruleName: string, facts: FactSet, selection: Selection, names: ListNames);
111
+ }
112
+ /** The graph of the files of a project: see {@link Scope.graph}. */
113
+ export declare class Graph {
114
+ private readonly units;
115
+ private readonly numbers;
116
+ /** For each file, by its number: the edges, each with the number of the file that it leads to. */
117
+ private readonly edges;
118
+ /** @internal */
119
+ constructor(facts: FactSet, selection: Selection);
120
+ /** The edges of a file, in the order of their first imports. A path that is no file has none. */
121
+ edgesOf(file: Path): readonly ImportEdge[];
122
+ /**
123
+ * A shortest chain of imports from a file to a file that is an end, as the steps of a
124
+ * report. No steps: the file itself is an end. `undefined`: no chain leads to an end. The
125
+ * search is breadth-first with the edges of each file in their order.
126
+ */
127
+ chain(from: Path, isEnd: (file: Path) => boolean): Step[] | undefined;
128
+ }
129
+ export {};
package/dist/custom.js ADDED
@@ -0,0 +1,217 @@
1
+ /**
2
+ * What a check of the user gets: the files that the rule selects and what the rule sees. The
3
+ * rule has applied its filters and its exemptions.
4
+ */
5
+ export class Scope {
6
+ /** The name of the rule. */
7
+ ruleName;
8
+ /** The facts of the project. */
9
+ facts;
10
+ /** The files that the rule selects, in order. */
11
+ subjects;
12
+ /** The imports that the rule sees, of all files, in the order of the facts. */
13
+ imports;
14
+ /** The markers of the selected files that the rule sees. */
15
+ markers;
16
+ /** The declarations of the selected files that the rule sees. */
17
+ declarations;
18
+ /** @internal */
19
+ selection;
20
+ selected;
21
+ lists;
22
+ firstFiles;
23
+ made;
24
+ /** @internal */
25
+ constructor(ruleName, facts, selection, names) {
26
+ this.ruleName = ruleName;
27
+ this.facts = facts;
28
+ this.selection = selection;
29
+ const units = facts.units;
30
+ this.subjects = selection.subjects.map((unit) => units[unit]);
31
+ this.selected = new Set(this.subjects.map(key));
32
+ this.lists = new Map(names.files.map((name, list) => [name, (selection.units[list] ?? []).map((unit) => units[unit])]));
33
+ const subjectSet = new Set(selection.subjects);
34
+ this.imports = selection.references.map((seen) => {
35
+ const fact = facts.facts[seen.fact];
36
+ const reference = fact.what;
37
+ const paths = [reference.target, ...reference.aliases];
38
+ const hits = {};
39
+ seen.hits?.forEach((hit, list) => {
40
+ if (hit !== null) {
41
+ hits[names.targets[list]] = paths[hit];
42
+ }
43
+ });
44
+ return {
45
+ fact,
46
+ reference,
47
+ file: units[seen.unit],
48
+ subject: subjectSet.has(seen.unit),
49
+ paths,
50
+ owners: seen.owners.map((owner) => (owner === null ? undefined : units[owner])),
51
+ hits,
52
+ };
53
+ });
54
+ this.markers = selection.markers.map((position) => {
55
+ const fact = facts.facts[position];
56
+ return { fact, file: fact.unit, marker: fact.what.marker };
57
+ });
58
+ const declarations = facts.declarations ?? [];
59
+ this.declarations = selection.declarations.map((position) => declarations[position]);
60
+ }
61
+ /** Whether the rule selects the file. */
62
+ selects(file) {
63
+ return this.selected.has(key(file));
64
+ }
65
+ /**
66
+ * The files that a list of file patterns of the check matches, by the name of the list.
67
+ * The files match as the files of a rule do. Throws when the check names no such list.
68
+ */
69
+ matched(list) {
70
+ const files = this.lists.get(list);
71
+ if (files === undefined) {
72
+ throw new TypeError(`refpact: rule \`${this.ruleName}\`: the check names no list of files \`${list}\``);
73
+ }
74
+ return files;
75
+ }
76
+ /**
77
+ * The graph of all files, selected or not, from the imports that the rule sees. An import
78
+ * leads to the file of each of its paths, so an import through a barrel file leads to the
79
+ * barrel and to the file that declares the name. An import of the own file is no edge.
80
+ */
81
+ graph() {
82
+ this.made ??= new Graph(this.facts, this.selection);
83
+ return this.made;
84
+ }
85
+ /**
86
+ * A violation at the place of a fact, with its note. A message gives a `custom` detail,
87
+ * which a baseline counts by `key`, or by the message when there is no key.
88
+ */
89
+ violationAt(fact, detail, key) {
90
+ return {
91
+ rule: this.ruleName,
92
+ unit: fact.unit,
93
+ file: fact.file,
94
+ line: fact.line,
95
+ column: fact.column,
96
+ ...(fact.note !== undefined ? { note: fact.note } : {}),
97
+ detail: custom(detail, key),
98
+ };
99
+ }
100
+ /** A violation at the place of a declaration. */
101
+ violationOfItem(declaration, detail, baselineKey) {
102
+ const { unit, file, line, column } = declaration;
103
+ return { rule: this.ruleName, unit, file, line, column, detail: custom(detail, baselineKey) };
104
+ }
105
+ /** A violation about a file as a whole, with no line. */
106
+ violationOfFile(file, detail, baselineKey) {
107
+ if (this.firstFiles === undefined) {
108
+ this.firstFiles = new Map();
109
+ for (const fact of this.facts.facts) {
110
+ const unit = key(fact.unit);
111
+ if (!this.firstFiles.has(unit)) {
112
+ this.firstFiles.set(unit, fact.file);
113
+ }
114
+ }
115
+ }
116
+ return { rule: this.ruleName, unit: file, file: this.firstFiles.get(key(file)) ?? "", line: 0, column: 0, detail: custom(detail, baselineKey) };
117
+ }
118
+ }
119
+ /**
120
+ * What a check of items gets: the {@link Scope} of the rule, and the items that the filters
121
+ * of the rule select.
122
+ */
123
+ export class ItemScope extends Scope {
124
+ /** The items that the rule selects, in order. */
125
+ items;
126
+ /**
127
+ * The imports that the rule sees inside the selected items. A front end that does not say
128
+ * which item holds an import gives none: the TypeScript front end is one.
129
+ */
130
+ itemImports;
131
+ /** @internal */
132
+ constructor(ruleName, facts, selection, names) {
133
+ super(ruleName, facts, selection, names);
134
+ const declarations = facts.declarations ?? [];
135
+ this.items = (selection.items ?? []).map((position) => declarations[position]);
136
+ this.itemImports = this.imports.filter((_, index) => selection.references[index]?.inside === true);
137
+ }
138
+ }
139
+ /** The graph of the files of a project: see {@link Scope.graph}. */
140
+ export class Graph {
141
+ units;
142
+ numbers;
143
+ /** For each file, by its number: the edges, each with the number of the file that it leads to. */
144
+ edges;
145
+ /** @internal */
146
+ constructor(facts, selection) {
147
+ this.units = facts.units;
148
+ this.numbers = new Map();
149
+ this.units.forEach((unit, number) => {
150
+ if (!this.numbers.has(key(unit))) {
151
+ this.numbers.set(key(unit), number);
152
+ }
153
+ });
154
+ this.edges = this.units.map(() => []);
155
+ const known = new Set();
156
+ const count = this.units.length;
157
+ for (const seen of selection.references) {
158
+ const path = seen.owners.findIndex((owner) => owner !== null);
159
+ const to = seen.owners[path];
160
+ if (to === undefined || to === null || to === seen.unit || known.has(seen.unit * count + to)) {
161
+ continue;
162
+ }
163
+ known.add(seen.unit * count + to);
164
+ const fact = facts.facts[seen.fact];
165
+ const reference = fact.what;
166
+ const target = [reference.target, ...reference.aliases][path];
167
+ this.edges[seen.unit].push({ to: this.units[to], node: to, fact, target });
168
+ }
169
+ }
170
+ /** The edges of a file, in the order of their first imports. A path that is no file has none. */
171
+ edgesOf(file) {
172
+ const node = this.numbers.get(key(file));
173
+ return node === undefined ? [] : this.edges[node];
174
+ }
175
+ /**
176
+ * A shortest chain of imports from a file to a file that is an end, as the steps of a
177
+ * report. No steps: the file itself is an end. `undefined`: no chain leads to an end. The
178
+ * search is breadth-first with the edges of each file in their order.
179
+ */
180
+ chain(from, isEnd) {
181
+ const start = this.numbers.get(key(from));
182
+ if (start === undefined) {
183
+ return undefined;
184
+ }
185
+ // For each file that the search found: the file and the edge that led to it.
186
+ const foundBy = new Map();
187
+ const queue = [start];
188
+ for (let next = 0; next < queue.length; next++) {
189
+ const node = queue[next];
190
+ if (isEnd(this.units[node])) {
191
+ const steps = [];
192
+ for (let current = node, found = foundBy.get(current); found !== undefined; current = found.from, found = foundBy.get(current)) {
193
+ const { fact, target } = found.edge;
194
+ const kind = fact.what.kind;
195
+ steps.push({ from: this.units[found.from], to: this.units[current], file: fact.file, line: fact.line, column: fact.column, target, kind });
196
+ }
197
+ return steps.reverse();
198
+ }
199
+ for (const edge of this.edges[node] ?? []) {
200
+ if (edge.node !== start && !foundBy.has(edge.node)) {
201
+ foundBy.set(edge.node, { from: node, edge });
202
+ queue.push(edge.node);
203
+ }
204
+ }
205
+ }
206
+ return undefined;
207
+ }
208
+ }
209
+ function key(path) {
210
+ return path.join("\u0000");
211
+ }
212
+ function custom(detail, key) {
213
+ if (typeof detail !== "string") {
214
+ return detail;
215
+ }
216
+ return { type: "custom", message: detail, ...(key !== undefined ? { key } : {}) };
217
+ }