@aoelite/refpact-ts 0.0.0-stage → 0.2.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/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 +88 -0
  6. package/dist/core/index.js +105 -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 +268 -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 +166 -0
  14. package/dist/custom.js +239 -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 +665 -0
  51. package/package.json +52 -4
  52. package/wasm/refpact_core.wasm +0 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 AoElite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,39 @@
1
- # Temporary Holding Version
1
+ # refpact
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Architecture tests for TypeScript 6 and 7: rules about what files may import, checked as
4
+ ordinary tests under Vitest or `node --test`. The compiler of your project resolves each
5
+ import.
6
+
7
+ ```sh
8
+ pnpm add --save-dev @aoelite/refpact-ts
9
+ ```
10
+
11
+ ```ts
12
+ import { test } from "vitest";
13
+ import { anyOf, loadProject, pkg, rule } from "@aoelite/refpact-ts";
14
+
15
+ const app = loadProject("tsconfig.json");
16
+
17
+ test("the user interface does not import the database", () => {
18
+ rule("the user interface does not import the database")
19
+ .files(anyOf("src/ui/**"))
20
+ .mustNotImport(anyOf("src/db/**", pkg("pg")))
21
+ .check(app);
22
+ });
23
+ ```
24
+
25
+ `check` throws an error, and the message of the error is the report:
26
+
27
+ ```text
28
+ rule `the user interface does not import the database` is violated in 1 place(s):
29
+ src/ui/view.ts:3:10: [the user interface does not import the database] src/ui/view.ts uses src/db/client.ts#query (import, written as src/db/index.ts#query)
30
+ ```
31
+
32
+ - [The TypeScript guide](https://github.com/AoElite/RefPact/blob/main/docs/typescript.md):
33
+ how to install the package, every rule, and the limits of the analysis.
34
+ - [All that you can check](https://github.com/AoElite/RefPact#what-you-can-check), also in
35
+ Rust.
36
+
37
+ ## License
38
+
39
+ MIT
@@ -0,0 +1,26 @@
1
+ import type { BaselineEntry, PathFormat, Violation } from "./core/index.ts";
2
+ import type { Failure } from "./rules.ts";
3
+ /**
4
+ * A file of violations that the project accepts for now.
5
+ *
6
+ * A rule uses a baseline only when it is checked with `checkWithBaseline`. Plain `check`
7
+ * never reads one.
8
+ *
9
+ * The file is text, one accepted violation per line, without line numbers of the source.
10
+ * Commit it. To write it, run the tests once with `REFPACT_BASELINE=update`.
11
+ */
12
+ export declare class Baseline {
13
+ /** The absolute path of the file. */
14
+ readonly file: string;
15
+ /** @internal */
16
+ constructor(file: string);
17
+ /** @internal The entries, or the reason why the file cannot be read. A file that is not there is empty. */
18
+ read(): readonly BaselineEntry[] | Failure;
19
+ /** @internal Replaces the entries of one rule with its current violations. */
20
+ write(rule: string, violations: readonly Violation[], format: PathFormat): Failure | undefined;
21
+ }
22
+ /**
23
+ * The baseline in the file at `file`. A relative path is relative to the working directory;
24
+ * in a test, give a path from `import.meta.dirname`.
25
+ */
26
+ export declare function baseline(file: string): Baseline;
@@ -0,0 +1,65 @@
1
+ /** The baseline file. */
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { baselineEntries, formatBaseline, parseBaseline } from "./core/index.js";
5
+ /**
6
+ * A file of violations that the project accepts for now.
7
+ *
8
+ * A rule uses a baseline only when it is checked with `checkWithBaseline`. Plain `check`
9
+ * never reads one.
10
+ *
11
+ * The file is text, one accepted violation per line, without line numbers of the source.
12
+ * Commit it. To write it, run the tests once with `REFPACT_BASELINE=update`.
13
+ */
14
+ export class Baseline {
15
+ /** The absolute path of the file. */
16
+ file;
17
+ /** @internal */
18
+ constructor(file) {
19
+ this.file = path.resolve(file);
20
+ }
21
+ /** @internal The entries, or the reason why the file cannot be read. A file that is not there is empty. */
22
+ read() {
23
+ if (!existsSync(this.file)) {
24
+ return [];
25
+ }
26
+ let text;
27
+ try {
28
+ text = readFileSync(this.file, "utf8");
29
+ }
30
+ catch (error) {
31
+ return { type: "baseline", message: `refpact: cannot read ${this.file}: ${error.message}` };
32
+ }
33
+ const parsed = parseBaseline(text);
34
+ if (!parsed.ok) {
35
+ return { type: "baseline", message: `${this.file}:${parsed.line}: this line of the baseline is not valid` };
36
+ }
37
+ return parsed.entries;
38
+ }
39
+ /** @internal Replaces the entries of one rule with its current violations. */
40
+ write(rule, violations, format) {
41
+ const entries = this.read();
42
+ if (!Array.isArray(entries)) {
43
+ return entries;
44
+ }
45
+ const kept = entries.filter((entry) => entry.rule !== rule);
46
+ try {
47
+ mkdirSync(path.dirname(this.file), { recursive: true });
48
+ // Written beside the file and moved, so that a reader never sees half a file.
49
+ const partial = `${this.file}.partial`;
50
+ writeFileSync(partial, formatBaseline([...kept, ...baselineEntries(violations, format)]));
51
+ renameSync(partial, this.file);
52
+ }
53
+ catch (error) {
54
+ return { type: "baseline", message: `refpact: cannot write ${this.file}: ${error.message}` };
55
+ }
56
+ return undefined;
57
+ }
58
+ }
59
+ /**
60
+ * The baseline in the file at `file`. A relative path is relative to the working directory;
61
+ * in a test, give a path from `import.meta.dirname`.
62
+ */
63
+ export function baseline(file) {
64
+ return new Baseline(file);
65
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The language-neutral core: rules evaluated over facts, reports and baselines.
3
+ *
4
+ * The core is the Rust crate `refpact-core`, run as WebAssembly. This module turns its JSON
5
+ * requests (SPEC section 8) into functions. A fact set crosses into the core once, on its
6
+ * first evaluation, and stays there while the object lives, so each further rule sends only
7
+ * the rule.
8
+ */
9
+ import type { FactSet, Path, RawPattern, Rule, Severity } from "./model.ts";
10
+ import type { Applied, BaselineEntry, BaselineKey, EvaluateResult, GraphResult, ParsedBaseline, PathFormat, RuleError, SelectResult, StaleEntry, TanglesResult, Violation } from "./results.ts";
11
+ export type * from "./model.ts";
12
+ export type * from "./results.ts";
13
+ /** Evaluates one rule against one fact set (SPEC section 4). */
14
+ export declare function evaluate(rule: Rule, facts: FactSet): EvaluateResult;
15
+ /**
16
+ * Applies the subjects, the filters and the exemptions of a rule with a custom check, and
17
+ * answers what the rule sees (SPEC section 8), so that the caller finds the violations. The
18
+ * check is the constraint of the rule, or the demand of a rule about items.
19
+ */
20
+ export declare function select(rule: Rule, facts: FactSet): SelectResult;
21
+ /**
22
+ * The edges of a graph of what a rule sees, as the core makes them (SPEC section 8): the unit
23
+ * graph, or with `subjects` the graph of the subjects.
24
+ */
25
+ export declare function graph(rule: Rule, facts: FactSet, subjects: boolean): GraphResult;
26
+ /** The tangles of the graph that {@link graph} gives. The core searches them only for this request. */
27
+ export declare function tangles(rule: Rule, facts: FactSet, subjects: boolean): TanglesResult;
28
+ /** The text form of a diagram: a Mermaid flowchart, or a Graphviz graph in DOT. */
29
+ export type DiagramFormat = "mermaid" | "dot";
30
+ /** The diagram of a rule, or the reasons why the rule cannot be evaluated. */
31
+ export type DiagramResult = {
32
+ /** The rule can be evaluated. */
33
+ readonly ok: true;
34
+ /** The diagram. */
35
+ readonly text: string;
36
+ } | {
37
+ /** The rule cannot be evaluated. */
38
+ readonly ok: false;
39
+ /** The reasons, in the order of the specification. */
40
+ readonly errors: readonly RuleError[];
41
+ };
42
+ /** A layer of a diagram of layers (SPEC 4.17). */
43
+ export interface DiagramLayer {
44
+ /** The name of the layer, which is the label of its node. */
45
+ readonly name: string;
46
+ /** The units of the layer: unit patterns, which match units exactly. */
47
+ readonly units: readonly RawPattern[];
48
+ /** The layers that the layer must not use, by their position in the list of layers. */
49
+ readonly mustNotUse: readonly number[];
50
+ }
51
+ /**
52
+ * The nodes of a diagram: the subjects of the rule when this is `undefined`, the slices of
53
+ * the subjects, or the layers from the top to the bottom.
54
+ */
55
+ export type DiagramNodes = {
56
+ /** The slice patterns, as for `must-be-acyclic` with slices. */
57
+ readonly slices: readonly RawPattern[];
58
+ } | {
59
+ /** The layers, from the top to the bottom. */
60
+ readonly layers: readonly DiagramLayer[];
61
+ } | undefined;
62
+ /**
63
+ * The diagram of what a rule sees (SPEC 4.17): its subjects, the slices or the layers, and the
64
+ * references between them. The constraint of the rule is not evaluated.
65
+ */
66
+ export declare function diagram(rule: Rule, facts: FactSet, nodes: DiagramNodes, format?: DiagramFormat): DiagramResult;
67
+ /** Prints a path as reports print it. */
68
+ export declare function printPath(path: Path, format: PathFormat): string;
69
+ /** One line for one violation: file, line, column, rule name, unit and offending path. */
70
+ export declare function renderViolation(violation: Violation, format: PathFormat): string;
71
+ /** All violations of one rule, one per line, with a heading, and the reason of the rule when it has one. */
72
+ export declare function renderViolations(rule: string, violations: readonly Violation[], format: PathFormat, reason?: string, severity?: Severity): string;
73
+ /** One sentence for one error. */
74
+ export declare function renderError(error: RuleError): string;
75
+ /** All errors of one rule, one per line, with a heading. */
76
+ export declare function renderErrors(rule: string, errors: readonly RuleError[]): string;
77
+ /** The report of the stale entries of one rule in a baseline file. */
78
+ export declare function renderStale(rule: string, file: string, entries: readonly StaleEntry[]): string;
79
+ /** The `unit` and `what` of the key of a violation (SPEC 7.1). */
80
+ export declare function baselineKey(violation: Violation, format: PathFormat): BaselineKey;
81
+ /** One entry for each key of the violations, with the number of violations that have it. */
82
+ export declare function baselineEntries(violations: readonly Violation[], format: PathFormat): BaselineEntry[];
83
+ /** Removes the violations of one rule that the baseline accepts (SPEC 7.3). */
84
+ export declare function applyBaseline(rule: string, violations: readonly Violation[], baseline: readonly BaselineEntry[], format: PathFormat): Applied;
85
+ /** Writes the text form of a baseline, sorted, so that a change shows as a small difference. */
86
+ export declare function formatBaseline(entries: readonly BaselineEntry[]): string;
87
+ /** Reads the text form of a baseline (SPEC 7.2). */
88
+ export declare function parseBaseline(text: string): ParsedBaseline;
@@ -0,0 +1,105 @@
1
+ import { call, currentGeneration } from "./wasm.js";
2
+ const loaded = new WeakMap();
3
+ const unload = new FinalizationRegistry(({ handle, generation }) => {
4
+ if (generation === currentGeneration()) {
5
+ call(JSON.stringify({ op: "drop-facts", handle }));
6
+ }
7
+ });
8
+ /** The handle of a fact set in the core, after loading it when it is not loaded. */
9
+ function handleOf(facts) {
10
+ const generation = currentGeneration();
11
+ const known = loaded.get(facts);
12
+ if (known !== undefined && known.generation === generation) {
13
+ return known.handle;
14
+ }
15
+ const { handle } = JSON.parse(call(`{"op":"load-facts","facts":${JSON.stringify(facts)}}`));
16
+ const entry = { handle, generation };
17
+ loaded.set(facts, entry);
18
+ unload.register(facts, entry);
19
+ return handle;
20
+ }
21
+ /** Only the fields of a path format, so that a fact set given as the format does not cross whole. */
22
+ function formatOf(format) {
23
+ return {
24
+ separator: format.separator,
25
+ ...(format.attachedPrefixes !== undefined && { attachedPrefixes: format.attachedPrefixes }),
26
+ ...(format.hiddenPrefixes !== undefined && { hiddenPrefixes: format.hiddenPrefixes }),
27
+ };
28
+ }
29
+ function request(op, body) {
30
+ return JSON.parse(call(JSON.stringify({ op, ...body })));
31
+ }
32
+ /** Evaluates one rule against one fact set (SPEC section 4). */
33
+ export function evaluate(rule, facts) {
34
+ return request("evaluate", { handle: handleOf(facts), rule });
35
+ }
36
+ /**
37
+ * Applies the subjects, the filters and the exemptions of a rule with a custom check, and
38
+ * answers what the rule sees (SPEC section 8), so that the caller finds the violations. The
39
+ * check is the constraint of the rule, or the demand of a rule about items.
40
+ */
41
+ export function select(rule, facts) {
42
+ return request("select", { handle: handleOf(facts), rule });
43
+ }
44
+ /**
45
+ * The edges of a graph of what a rule sees, as the core makes them (SPEC section 8): the unit
46
+ * graph, or with `subjects` the graph of the subjects.
47
+ */
48
+ export function graph(rule, facts, subjects) {
49
+ return request("graph", { handle: handleOf(facts), rule, subjects });
50
+ }
51
+ /** The tangles of the graph that {@link graph} gives. The core searches them only for this request. */
52
+ export function tangles(rule, facts, subjects) {
53
+ return request("graph", { handle: handleOf(facts), rule, subjects, tangles: true });
54
+ }
55
+ /**
56
+ * The diagram of what a rule sees (SPEC 4.17): its subjects, the slices or the layers, and the
57
+ * references between them. The constraint of the rule is not evaluated.
58
+ */
59
+ export function diagram(rule, facts, nodes, format = "mermaid") {
60
+ return request("diagram", { handle: handleOf(facts), rule, ...nodes, format });
61
+ }
62
+ /** Prints a path as reports print it. */
63
+ export function printPath(path, format) {
64
+ return request("print-path", { path, format: formatOf(format) }).text;
65
+ }
66
+ /** One line for one violation: file, line, column, rule name, unit and offending path. */
67
+ export function renderViolation(violation, format) {
68
+ return request("render-violation", { violation, format: formatOf(format) }).text;
69
+ }
70
+ /** All violations of one rule, one per line, with a heading, and the reason of the rule when it has one. */
71
+ export function renderViolations(rule, violations, format, reason, severity = "error") {
72
+ return request("render-violations", { rule, reason, severity, violations, format: formatOf(format) }).text;
73
+ }
74
+ /** One sentence for one error. */
75
+ export function renderError(error) {
76
+ return request("render-error", { error }).text;
77
+ }
78
+ /** All errors of one rule, one per line, with a heading. */
79
+ export function renderErrors(rule, errors) {
80
+ return request("render-errors", { rule, errors }).text;
81
+ }
82
+ /** The report of the stale entries of one rule in a baseline file. */
83
+ export function renderStale(rule, file, entries) {
84
+ return request("render-stale", { rule, file, entries }).text;
85
+ }
86
+ /** The `unit` and `what` of the key of a violation (SPEC 7.1). */
87
+ export function baselineKey(violation, format) {
88
+ return request("baseline-key", { violation, format: formatOf(format) });
89
+ }
90
+ /** One entry for each key of the violations, with the number of violations that have it. */
91
+ export function baselineEntries(violations, format) {
92
+ return request("baseline-entries", { violations, format: formatOf(format) }).entries;
93
+ }
94
+ /** Removes the violations of one rule that the baseline accepts (SPEC 7.3). */
95
+ export function applyBaseline(rule, violations, baseline, format) {
96
+ return request("apply-baseline", { rule, violations, entries: baseline, format: formatOf(format) });
97
+ }
98
+ /** Writes the text form of a baseline, sorted, so that a change shows as a small difference. */
99
+ export function formatBaseline(entries) {
100
+ return request("format-baseline", { entries }).text;
101
+ }
102
+ /** Reads the text form of a baseline (SPEC 7.2). */
103
+ export function parseBaseline(text) {
104
+ return request("parse-baseline", { text });
105
+ }