@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
@@ -0,0 +1,408 @@
1
+ import type { Baseline } from "./baseline.ts";
2
+ import type { DiagramFormat, DiagramNodes } from "./core/index.ts";
3
+ import type { EvaluateResult, RawPattern, Rule as CoreRule, RuleError, StaleEntry, Violation } from "./core/index.ts";
4
+ import { Scope } from "./custom.ts";
5
+ import type { Filter, Filters } from "./filters.ts";
6
+ import type { ListNames } from "./custom.ts";
7
+ import { KINDS, MARKERS } from "./frontend/imports.ts";
8
+ import { ItemSelection } from "./items.ts";
9
+ import type { Kind, MarkerName as Marker } from "./frontend/imports.ts";
10
+ import type { Project } from "./frontend/project.ts";
11
+ /** A package, a subpath of a package, or one export of it, as a target of a rule. */
12
+ export declare class PackageTarget {
13
+ /** @internal */
14
+ readonly segments: RawPattern;
15
+ /** @internal */
16
+ constructor(segments: RawPattern);
17
+ /** One export of the package: `pkg("lodash").export("debounce")`. Use `default` for the default export. */
18
+ export(name: string): PackageTarget;
19
+ }
20
+ /**
21
+ * A package as a target: `pkg("lodash")`, `pkg("@scope/name")`, `pkg("lodash/fp")` for a
22
+ * subpath, `pkg("node:fs")` or `pkg("fs")` for a module of Node. The target covers all
23
+ * subpaths and all exports of the package. `*` matches a part of the name: `pkg("@aws-sdk/*")`.
24
+ */
25
+ export declare function pkg(specifier: string): PackageTarget;
26
+ /**
27
+ * A global of the host as a target: `globalApi("fetch")`, `globalApi("process")`, or one member
28
+ * of it, `globalApi("process.env")`. The target of a global covers its members. A use of a
29
+ * global counts as an import of it, of the kind `global`: see {@link KINDS}. `*` matches a
30
+ * part of the name: `globalApi("*Storage")`.
31
+ *
32
+ * A global is a name that no file of the project declares and that the ECMAScript library
33
+ * does not declare: the globals of Node, of the browser and of other hosts, such as
34
+ * `process`, `fetch`, `window`, `document` and `localStorage`. `Math`, `Date`, `JSON` and
35
+ * `Promise` are part of the language and are no globals here.
36
+ */
37
+ export declare function globalApi(name: string): PackageTarget;
38
+ /** A file pattern, a package or a global. */
39
+ export type Target = string | PackageTarget;
40
+ /** A selection of files. */
41
+ export declare class Files {
42
+ /** @internal */
43
+ readonly include: readonly string[] | undefined;
44
+ /** @internal */
45
+ readonly excluded: readonly string[] | undefined;
46
+ /** @internal */
47
+ constructor(include: readonly string[] | undefined, excluded: readonly string[] | undefined);
48
+ }
49
+ /** A set of forbidden targets. */
50
+ export declare class Targets {
51
+ /** @internal */
52
+ readonly anyOf: readonly Target[];
53
+ /** @internal */
54
+ readonly unverified: readonly Target[];
55
+ /** @internal */
56
+ constructor(anyOf: readonly Target[], unverified: readonly Target[]);
57
+ }
58
+ /** A list of patterns that form a union. */
59
+ export declare class AnyOf<T extends Target = Target> {
60
+ /** @internal */
61
+ readonly patterns: readonly T[];
62
+ /** @internal */
63
+ constructor(patterns: readonly T[]);
64
+ /** Removes the files that match one of `patterns` from the selection. */
65
+ except(this: AnyOf<string>, ...patterns: string[]): Files;
66
+ /** Adds targets that are not checked for existence. See {@link unverified}. */
67
+ andUnverified(...patterns: Target[]): Targets;
68
+ }
69
+ /** The union of some patterns: files for `files(..)`, targets for `mustNotImport(..)`. */
70
+ export declare function anyOf(...patterns: string[]): AnyOf<string>;
71
+ export declare function anyOf(...patterns: Target[]): AnyOf<Target>;
72
+ /** All files of the project. */
73
+ export declare function all(): Files;
74
+ /** All files of the project except those that match one of the patterns. */
75
+ export declare function allExcept(...patterns: string[]): Files;
76
+ /**
77
+ * Targets that are not checked for existence. Use this only for files of the project that
78
+ * the compiler does not load, such as generated files that are not there yet.
79
+ */
80
+ export declare function unverified(...patterns: Target[]): Targets;
81
+ /** Starts a rule. The name is printed with every violation. */
82
+ export declare function rule(name: string): NamedRule;
83
+ /** A rule with a name. Say which files it applies to. */
84
+ export declare class NamedRule {
85
+ private readonly name;
86
+ /** @internal */
87
+ constructor(name: string);
88
+ /** The files that the rule constrains: `anyOf(..)`, `all()`, `allExcept(..)` or `anyOf(..).except(..)`. */
89
+ files(files: AnyOf<string> | Files): ScopedRule;
90
+ /**
91
+ * The items that these files declare at their top level: functions, classes, interfaces,
92
+ * types, enums, variables and namespaces. Narrow them with the filters of
93
+ * {@link ItemSelection}, then say what they must meet, for example with
94
+ * {@link ItemSelection.mustResideIn}.
95
+ */
96
+ items(files: AnyOf<string> | Files): ItemSelection;
97
+ }
98
+ /**
99
+ * A rule with a name and files. Say what the files must or must not do, for example with
100
+ * `mustNotImport`, `mustOnlyImport` or `mustBeAcyclic`.
101
+ */
102
+ export declare class ScopedRule {
103
+ private readonly name;
104
+ private readonly selection;
105
+ /** @internal */
106
+ constructor(name: string, selection: Files);
107
+ /**
108
+ * The files must not import any of the targets, in any form: `import`, `import type`,
109
+ * `import()`, `require`, or `export .. from`. An import through a barrel file counts as
110
+ * an import of the file that declares the name.
111
+ */
112
+ mustNotImport(targets: AnyOf | Targets): ImportRule;
113
+ /**
114
+ * Every import of the files must lead to one of the targets. An import is allowed when the
115
+ * module in the code is allowed, or when the file that declares the name is.
116
+ *
117
+ * The list must name everything the files import, also packages and modules of Node:
118
+ * `pkg("*")` is every package, and `pkg("node:*")` is every module of Node.
119
+ */
120
+ mustOnlyImport(targets: AnyOf | Targets): ImportRule;
121
+ /**
122
+ * The files must not form a cycle: no file may reach itself through the imports between
123
+ * them. Each tangle of files is reported once, with one of its cycles.
124
+ */
125
+ mustBeAcyclic(): ImportRule;
126
+ /**
127
+ * Like {@link ScopedRule.mustBeAcyclic}, between slices. The slice of a file is the
128
+ * shortest start of its path that matches one of the patterns: with `src/features/*`, the
129
+ * file `src/features/cart/view/list.ts` is in the slice `src/features/cart`. Imports inside
130
+ * a slice do not count, and a file without a slice is left out.
131
+ */
132
+ mustBeAcyclicBetween(slices: AnyOf<string>): ImportRule;
133
+ /**
134
+ * No file of one slice may import a file of another slice. Slices are as in
135
+ * {@link ScopedRule.mustBeAcyclicBetween}: with `src/features/*`, no feature may import
136
+ * another feature.
137
+ *
138
+ * A file that the rule does not select is in no slice. Leave a shared part out with
139
+ * `allExcept("src/features/shared/**")`.
140
+ */
141
+ mustBeIndependentBetween(slices: AnyOf<string>): ImportRule;
142
+ /**
143
+ * The files must not contain any of the markers: `console`, a use of the global
144
+ * `console`, `debugger`, a `debugger` statement, and `unresolved-import`, an import of a
145
+ * relative path, a `paths` alias or a `#name` of `imports` that leads to no file. A local
146
+ * variable named `console` is no marker. An import of a package is never an `unresolved-import`:
147
+ * refpact does not check that a package is installed.
148
+ *
149
+ * ```ts
150
+ * rule("every import resolves").files(all()).mustNotContain("unresolved-import").check(app);
151
+ * ```
152
+ */
153
+ mustNotContain(...markers: Marker[]): ImportRule;
154
+ /**
155
+ * Each file must have at most this number of lines. The blank lines and the comments
156
+ * count.
157
+ *
158
+ * ```ts
159
+ * rule("no file is longer than 400 lines").files(anyOf("src/**")).mustNotExceedLines(400).check(app);
160
+ * ```
161
+ */
162
+ mustNotExceedLines(lines: number): ImportRule;
163
+ /**
164
+ * Each file must be imported by another file. Leave the entry points out of the files:
165
+ * `allExcept("src/main.ts")`.
166
+ */
167
+ mustBeImported(): ImportRule;
168
+ /**
169
+ * No chain of imports may lead from the files to one of the targets: not the files
170
+ * themselves, and not a file that they import, at any distance. The report shows the
171
+ * shortest chain.
172
+ */
173
+ mustNotReach(targets: AnyOf | Targets): ImportRule;
174
+ /** A chain of imports must lead from one of the root files to each file. Give the entry points as roots. */
175
+ mustBeReachableFrom(roots: AnyOf<string>): ImportRule;
176
+ /**
177
+ * Only the importers, and the files of the rule themselves, may import the files of the
178
+ * rule. Every other file that imports one of them breaks the rule, also through a barrel
179
+ * file. Importers match files as the files of a rule do.
180
+ */
181
+ mustOnlyBeImportedBy(importers: AnyOf<string>): ImportRule;
182
+ /**
183
+ * Each file must be one that a place matches. The layers use it for `everyFileInALayer`.
184
+ *
185
+ * @internal
186
+ */
187
+ mustBeIn(places: readonly string[]): ImportRule;
188
+ /**
189
+ * Every package that the files import must be in the `package.json` that the file belongs
190
+ * to: the nearest one above it. Any of `dependencies`, `devDependencies`,
191
+ * `peerDependencies` and `optionalDependencies` counts. A type import of `name` is also
192
+ * declared by `@types/name`. A package may import itself by its own name.
193
+ *
194
+ * The project must be loaded with `readDependencies`: the rule reads the references that
195
+ * the `package.json` files give, as a check of your own can.
196
+ */
197
+ mustOnlyImportDeclaredPackages(): ImportRule;
198
+ /**
199
+ * The files must not import a package that their `package.json` has only in
200
+ * `devDependencies`. Select the files that are shipped: `allExcept("**\/*.test.ts")`.
201
+ * The project must be loaded with `readDependencies`.
202
+ */
203
+ mustNotImportDevDependencies(): ImportRule;
204
+ /**
205
+ * The files must pass a check of your own: a function that gets the {@link Scope} of the
206
+ * rule and returns the violations. The rule selects the files, applies its filters and its
207
+ * exemptions, and reports and baselines the violations, as for the rules above.
208
+ *
209
+ * A function cannot match a pattern, so the check names the lists of patterns that it
210
+ * needs, and the rule matches them: see {@link CheckPatterns}. A pattern that matches
211
+ * nothing is an error, as it is for `mustNotImport`.
212
+ *
213
+ * ```ts
214
+ * rule("a file imports at most ten files")
215
+ * .files(anyOf("src/**"))
216
+ * .mustSatisfy((scope) =>
217
+ * scope.subjects
218
+ * .filter((file) => scope.imports.filter((seen) => seen.file === file).length > 10)
219
+ * .map((file) => scope.violationOfFile(file, "imports more than ten files")),
220
+ * )
221
+ * .check(app);
222
+ * ```
223
+ */
224
+ mustSatisfy(check: (scope: Scope) => readonly Violation[], patterns?: CheckPatterns): ImportRule;
225
+ private targets;
226
+ private rule;
227
+ private custom;
228
+ }
229
+ /**
230
+ * The lists of patterns that a check of the user needs. The rule matches them, and the
231
+ * {@link Scope} of the check gives the result by the name of each list.
232
+ *
233
+ * ```ts
234
+ * rule("the database is imported with its types only")
235
+ * .files(anyOf("src/**"))
236
+ * .mustSatisfy(
237
+ * (scope) =>
238
+ * scope.imports
239
+ * .filter((seen) => seen.subject && seen.hits["db"] !== undefined && seen.reference.kind !== "import-type")
240
+ * .map((seen) => scope.violationAt(seen.fact, "imports a value of the database")),
241
+ * { targets: { db: anyOf("src/db/**") } },
242
+ * )
243
+ * .check(app);
244
+ * ```
245
+ */
246
+ export interface CheckPatterns {
247
+ /**
248
+ * Lists of targets, as in `mustNotImport`. Each import in the scope has the first of its
249
+ * paths that a list matches in {@link SeenImport.hits}, by the name of the list.
250
+ */
251
+ readonly targets?: Readonly<Record<string, AnyOf | Targets>>;
252
+ /**
253
+ * Lists of file patterns, which match as the files of a rule do. {@link Scope.matched}
254
+ * gives the files that a list matches, by the name of the list.
255
+ */
256
+ readonly files?: Readonly<Record<string, AnyOf<string>>>;
257
+ }
258
+ /** The check of a rule with a custom check: what finds the violations in what the rule sees. @internal */
259
+ export interface Finder {
260
+ readonly names: ListNames;
261
+ readonly find: (scope: Scope, project: Project) => readonly Violation[];
262
+ }
263
+ /**
264
+ * Why a rule did not pass.
265
+ *
266
+ * - `violated`: the code breaks the rule.
267
+ * - `invalid`: the rule cannot fail as written, for example because a pattern matches nothing.
268
+ * - `stale-baseline`: the baseline accepts violations that the code no longer has.
269
+ * - `baseline`: the baseline file cannot be read or written.
270
+ * - `malformed`: a rule or a group of rules cannot fail as written, for example layers with one layer only.
271
+ * - `several`: several rules of one group did not pass.
272
+ */
273
+ export type Failure = {
274
+ readonly type: "violated";
275
+ /** The name of the rule. */
276
+ readonly rule: string;
277
+ /** Why the rule exists, when it says so. See {@link ImportRule.because}. */
278
+ readonly reason?: string;
279
+ /** The violations that no baseline accepts. */
280
+ readonly violations: readonly Violation[];
281
+ /** The report. */
282
+ readonly message: string;
283
+ } | {
284
+ readonly type: "invalid";
285
+ /** The name of the rule. */
286
+ readonly rule: string;
287
+ /** Every reason, in the order of the specification. */
288
+ readonly errors: readonly RuleError[];
289
+ /** The report. */
290
+ readonly message: string;
291
+ } | {
292
+ readonly type: "stale-baseline";
293
+ /** The name of the rule. */
294
+ readonly rule: string;
295
+ /** The baseline file. */
296
+ readonly file: string;
297
+ /** The entries that accept too much. */
298
+ readonly entries: readonly StaleEntry[];
299
+ /** The report. */
300
+ readonly message: string;
301
+ } | {
302
+ readonly type: "baseline";
303
+ /** What went wrong. */
304
+ readonly message: string;
305
+ } | {
306
+ readonly type: "malformed";
307
+ /** The name of the rule or of the group. */
308
+ readonly rule: string;
309
+ /** What is wrong with it. */
310
+ readonly message: string;
311
+ } | {
312
+ readonly type: "several";
313
+ /** The failures of the rules, in the order of the rules. */
314
+ readonly failures: readonly Failure[];
315
+ /** The reports of all failures. */
316
+ readonly message: string;
317
+ };
318
+ /** The error that `check` throws. Its message is the report. */
319
+ export declare class ArchitectureError extends Error {
320
+ /** Why the rule did not pass. */
321
+ readonly failure: Failure;
322
+ constructor(failure: Failure);
323
+ }
324
+ /**
325
+ * A complete rule: a plain value that can be checked against a project. Its filters are
326
+ * those of {@link Filters}, such as `because`, `asWarning` and `ignoringKinds`.
327
+ */
328
+ export interface ImportRule extends Filters<ImportRule> {
329
+ }
330
+ export declare class ImportRule {
331
+ private readonly rule;
332
+ /** The check of a rule with a custom check. */
333
+ private readonly finder;
334
+ /** @internal */
335
+ constructor(rule: CoreRule, finder?: Finder);
336
+ /** The name of the rule. */
337
+ get name(): string;
338
+ /** @internal The rule with one more filter. */
339
+ filtered(filter: Filter): ImportRule;
340
+ /**
341
+ * Names the properties of imports that a check of your own reads, such as `license`. A
342
+ * name that the load does not produce is then an error, as a kind that it does not produce
343
+ * is: a check that reads `licence` for `license`, or the properties of a load without
344
+ * `readDependencies`, cannot pass because it finds nothing.
345
+ */
346
+ readingProperties(...names: string[]): ImportRule;
347
+ /**
348
+ * Evaluates the rule and returns the violations, which may be none, or the errors of the
349
+ * rule. A rule about `package.json` throws an {@link ArchitectureError} when no
350
+ * `package.json` can make it fail.
351
+ */
352
+ evaluate(project: Project): EvaluateResult;
353
+ /**
354
+ * Evaluates the rule and returns the failure, or `undefined` when the rule passes. The
355
+ * violations of a warning are reported with `console.warn` and are no failure: see
356
+ * {@link ImportRule.asWarning}.
357
+ */
358
+ verify(project: Project): Failure | undefined;
359
+ /** The violations, or the reason why the rule cannot be evaluated. */
360
+ private found;
361
+ /**
362
+ * A diagram of what the rule sees, in Mermaid (the default) or DOT. The nodes are the files
363
+ * of the rule, or its slices when the rule is about slices, such as
364
+ * {@link ScopedRule.mustBeAcyclicBetween}. An edge is an import between two nodes that the
365
+ * filters and the exemptions of the rule let pass. The files and imports of a cycle are
366
+ * red. The constraint of the rule is not evaluated, so a rule of any kind draws its files.
367
+ * Throws an {@link ArchitectureError} when the rule cannot be evaluated.
368
+ *
369
+ * A Markdown viewer that knows Mermaid, such as GitHub's, draws the text in a block
370
+ * ` ```mermaid `.
371
+ *
372
+ * ```ts
373
+ * const text = rule("the features").files(all()).mustBeAcyclicBetween(anyOf("src/features/*")).diagram(app);
374
+ * writeFileSync("docs/features.md", `\`\`\`mermaid\n${text}\`\`\`\n`);
375
+ * ```
376
+ */
377
+ diagram(project: Project, format?: DiagramFormat): string;
378
+ /** @internal The diagram of the rule with these nodes. */
379
+ draw(project: Project, nodes: DiagramNodes, format: DiagramFormat): string;
380
+ /** Throws an {@link ArchitectureError} when the rule is violated or cannot be evaluated. */
381
+ check(project: Project): void;
382
+ /**
383
+ * Like {@link ImportRule.verify}, but violations that the baseline file accepts do not fail.
384
+ *
385
+ * A baseline is used only through this method and {@link ImportRule.checkWithBaseline}. It
386
+ * can only shrink: an entry that accepts more violations than the code has is a failure.
387
+ * When the environment variable `REFPACT_BASELINE` is `update`, this writes the violations
388
+ * of the rule to the file and passes.
389
+ */
390
+ verifyWithBaseline(project: Project, baseline: Baseline): Failure | undefined;
391
+ /**
392
+ * Throws an {@link ArchitectureError} when the rule has violations that the baseline file
393
+ * does not accept, when the baseline has stale entries, or when the rule cannot be evaluated.
394
+ */
395
+ checkWithBaseline(project: Project, baseline: Baseline): void;
396
+ /**
397
+ * Writes the current violations of this rule to the baseline file. Entries of other rules
398
+ * in the file are kept. Returns the failure, or `undefined` when the file is written.
399
+ */
400
+ writeBaseline(project: Project, baseline: Baseline): Failure | undefined;
401
+ /** The failure of the violations. A warning reports them and fails not. */
402
+ private violated;
403
+ private violationsForBaseline;
404
+ /** The rule as the core sees it. */
405
+ toCore(): CoreRule;
406
+ }
407
+ export { KINDS, MARKERS };
408
+ export type { Kind, Marker };