@nielspeter/eess 0.2.1 → 0.4.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.
- package/README.md +3 -3
- package/dist/assertion-advice.d.ts +26 -0
- package/dist/assertion-advice.d.ts.map +1 -0
- package/dist/assertion-advice.js +29 -0
- package/dist/assertion-advice.js.map +1 -0
- package/dist/baseline.d.ts +69 -4
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +119 -13
- package/dist/baseline.js.map +1 -1
- package/dist/cache-registry.d.ts +14 -0
- package/dist/cache-registry.d.ts.map +1 -0
- package/dist/cache-registry.js +37 -0
- package/dist/cache-registry.js.map +1 -0
- package/dist/cardinality.d.ts +35 -0
- package/dist/cardinality.d.ts.map +1 -0
- package/dist/cardinality.js +65 -0
- package/dist/cardinality.js.map +1 -0
- package/dist/check-options.d.ts +14 -0
- package/dist/check-options.d.ts.map +1 -1
- package/dist/comment-suppression.d.ts +60 -0
- package/dist/comment-suppression.d.ts.map +1 -0
- package/dist/comment-suppression.js +89 -0
- package/dist/comment-suppression.js.map +1 -0
- package/dist/condition.d.ts +6 -0
- package/dist/condition.d.ts.map +1 -1
- package/dist/correspondence-core.d.ts +35 -0
- package/dist/correspondence-core.d.ts.map +1 -0
- package/dist/correspondence-core.js +38 -0
- package/dist/correspondence-core.js.map +1 -0
- package/dist/correspondence.d.ts +27 -5
- package/dist/correspondence.d.ts.map +1 -1
- package/dist/correspondence.js +39 -5
- package/dist/correspondence.js.map +1 -1
- package/dist/dedupe-config-findings.d.ts +56 -0
- package/dist/dedupe-config-findings.d.ts.map +1 -0
- package/dist/dedupe-config-findings.js +74 -0
- package/dist/dedupe-config-findings.js.map +1 -0
- package/dist/define.d.ts +44 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +45 -4
- package/dist/define.js.map +1 -1
- package/dist/diff-aware.d.ts +8 -0
- package/dist/diff-aware.d.ts.map +1 -1
- package/dist/diff-aware.js +11 -2
- package/dist/diff-aware.js.map +1 -1
- package/dist/diff-disclosure.d.ts +72 -0
- package/dist/diff-disclosure.d.ts.map +1 -0
- package/dist/diff-disclosure.js +110 -0
- package/dist/diff-disclosure.js.map +1 -0
- package/dist/disk-set.d.ts +27 -0
- package/dist/disk-set.d.ts.map +1 -0
- package/dist/disk-set.js +12 -0
- package/dist/disk-set.js.map +1 -0
- package/dist/edge-coverage.d.ts +96 -0
- package/dist/edge-coverage.d.ts.map +1 -0
- package/dist/edge-coverage.js +108 -0
- package/dist/edge-coverage.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute-rule.d.ts +11 -5
- package/dist/execute-rule.d.ts.map +1 -1
- package/dist/execute-rule.js +87 -19
- package/dist/execute-rule.js.map +1 -1
- package/dist/format-github.d.ts.map +1 -1
- package/dist/format-github.js +37 -5
- package/dist/format-github.js.map +1 -1
- package/dist/format-json.d.ts +83 -1
- package/dist/format-json.d.ts.map +1 -1
- package/dist/format-json.js +70 -9
- package/dist/format-json.js.map +1 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +40 -8
- package/dist/format.js.map +1 -1
- package/dist/glob-site.d.ts +232 -0
- package/dist/glob-site.d.ts.map +1 -0
- package/dist/glob-site.js +156 -0
- package/dist/glob-site.js.map +1 -0
- package/dist/identity-root.d.ts +55 -0
- package/dist/identity-root.d.ts.map +1 -0
- package/dist/identity-root.js +170 -0
- package/dist/identity-root.js.map +1 -0
- package/dist/index.d.ts +41 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- package/dist/owns-empty-discovery.d.ts +5 -0
- package/dist/owns-empty-discovery.d.ts.map +1 -0
- package/dist/owns-empty-discovery.js +77 -0
- package/dist/owns-empty-discovery.js.map +1 -0
- package/dist/path-universe.d.ts +44 -0
- package/dist/path-universe.d.ts.map +1 -0
- package/dist/path-universe.js +21 -0
- package/dist/path-universe.js.map +1 -0
- package/dist/predicate.d.ts +33 -0
- package/dist/predicate.d.ts.map +1 -1
- package/dist/preset-dispatch.d.ts +19 -0
- package/dist/preset-dispatch.d.ts.map +1 -1
- package/dist/preset-dispatch.js +43 -3
- package/dist/preset-dispatch.js.map +1 -1
- package/dist/project-relative.d.ts +25 -0
- package/dist/project-relative.d.ts.map +1 -0
- package/dist/project-relative.js +54 -0
- package/dist/project-relative.js.map +1 -0
- package/dist/report.d.ts +11 -5
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +17 -0
- package/dist/report.js.map +1 -1
- package/dist/rule-builder-like.d.ts +11 -0
- package/dist/rule-builder-like.d.ts.map +1 -0
- package/dist/rule-builder-like.js +2 -0
- package/dist/rule-builder-like.js.map +1 -0
- package/dist/rule-builder.d.ts +109 -84
- package/dist/rule-builder.d.ts.map +1 -1
- package/dist/rule-builder.js +202 -158
- package/dist/rule-builder.js.map +1 -1
- package/dist/rule-description.d.ts +33 -1
- package/dist/rule-description.d.ts.map +1 -1
- package/dist/rule-description.js +17 -1
- package/dist/rule-description.js.map +1 -1
- package/dist/rule-metadata.d.ts +20 -3
- package/dist/rule-metadata.d.ts.map +1 -1
- package/dist/selection-memo.d.ts +47 -0
- package/dist/selection-memo.d.ts.map +1 -0
- package/dist/selection-memo.js +68 -0
- package/dist/selection-memo.js.map +1 -0
- package/dist/shallow-clone.d.ts +20 -0
- package/dist/shallow-clone.d.ts.map +1 -0
- package/dist/shallow-clone.js +27 -0
- package/dist/shallow-clone.js.map +1 -0
- package/dist/stderr.d.ts +16 -0
- package/dist/stderr.d.ts.map +1 -0
- package/dist/stderr.js +73 -0
- package/dist/stderr.js.map +1 -0
- package/dist/terminal-builder.d.ts +209 -9
- package/dist/terminal-builder.d.ts.map +1 -1
- package/dist/terminal-builder.js +264 -20
- package/dist/terminal-builder.js.map +1 -1
- package/dist/type-guards.d.ts +38 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +42 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/unsuppressable.d.ts +54 -0
- package/dist/unsuppressable.d.ts.map +1 -0
- package/dist/unsuppressable.js +76 -0
- package/dist/unsuppressable.js.map +1 -0
- package/dist/vacuity-findings.d.ts +55 -0
- package/dist/vacuity-findings.d.ts.map +1 -0
- package/dist/vacuity-findings.js +124 -0
- package/dist/vacuity-findings.js.map +1 -0
- package/dist/violation.d.ts +152 -4
- package/dist/violation.d.ts.map +1 -1
- package/dist/violation.js +262 -1
- package/dist/violation.js.map +1 -1
- package/package.json +1 -1
package/dist/format.js
CHANGED
|
@@ -1,16 +1,37 @@
|
|
|
1
|
+
import { remedyRepeatsMessage } from './violation.js';
|
|
1
2
|
import { bold, red, cyan, dim } from './ansi.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
/** Format a single violation into a rich terminal section. */
|
|
4
5
|
function formatSingleViolation(v, index, total, cwd, showCodeFrames, reason) {
|
|
5
6
|
const counter = bold(red(`Architecture Violation [${String(index + 1)} of ${String(total)}]`));
|
|
6
7
|
const ruleLine = ` ${dim('Rule:')} ${v.rule}`;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// `message` is printed for BOTH shapes, and it used to be printed for neither
|
|
9
|
+
// — a located violation rendered only `file:line — element`, so the one
|
|
10
|
+
// sentence saying what is actually wrong was dropped from the default surface
|
|
11
|
+
// for every ordinary violation. Nothing failed when this was fixed, because
|
|
12
|
+
// nothing pinned it either way; `formatViolationsPlain` had always printed it,
|
|
13
|
+
// so the two formatters disagreed about what a violation IS.
|
|
14
|
+
//
|
|
15
|
+
// A config-level finding has no source location: `path.relative(cwd, '')`
|
|
16
|
+
// renders as the cwd and ':0' is noise, so it gets the message alone.
|
|
17
|
+
const location = v.file
|
|
18
|
+
? ` ${v.message}\n ${cyan(path.relative(cwd, v.file) + ':' + String(v.line))} ${dim('—')} ${v.element}`
|
|
19
|
+
: ` ${v.message}`;
|
|
10
20
|
const codeLine = showCodeFrames && v.codeFrame ? `\n${v.codeFrame}` : '';
|
|
11
21
|
const whyText = v.because ?? reason;
|
|
12
22
|
const whyLine = whyText ? ` ${dim('Why:')} ${whyText}` : '';
|
|
13
|
-
|
|
23
|
+
// Suppressed whenever the `location` slot above already printed this exact
|
|
24
|
+
// text, which is now BOTH shapes — see the note there. It used to be
|
|
25
|
+
// `!v.file && remedyRepeatsMessage(v)`, and that asymmetry was load-bearing
|
|
26
|
+
// and pinned, because a located violation did not render `message` at all: its
|
|
27
|
+
// `Fix:` line was the remedy's only appearance. Rendering `message` for both
|
|
28
|
+
// shapes removed the premise, and the `!v.file` half became a defect —
|
|
29
|
+
// measured, two occurrences of the remedy for a located finding whose
|
|
30
|
+
// suggestion is its message.
|
|
31
|
+
//
|
|
32
|
+
// So do not "restore" the asymmetry from the old comment: check whether this
|
|
33
|
+
// formatter still prints `message` for a located violation first.
|
|
34
|
+
const fixLine = v.suggestion && !remedyRepeatsMessage(v) ? ` ${dim('Fix:')} ${v.suggestion}` : '';
|
|
14
35
|
const docsLine = v.docs ? ` ${dim('Docs:')} ${v.docs}` : '';
|
|
15
36
|
const parts = [counter, '', ruleLine, '', location];
|
|
16
37
|
if (codeLine)
|
|
@@ -41,8 +62,10 @@ export function formatViolations(violations, reason, options) {
|
|
|
41
62
|
/**
|
|
42
63
|
* Format violations into a plain-text string (no ANSI codes).
|
|
43
64
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
65
|
+
* Public export, for callers that aggregate violations themselves and need
|
|
66
|
+
* output free of ANSI codes — serialized, logged to a file, or embedded in
|
|
67
|
+
* another tool's report. `ArchRuleError.message` is a one-line summary and does
|
|
68
|
+
* not use this; the run's detail is written by `writeReport`.
|
|
46
69
|
*/
|
|
47
70
|
export function formatViolationsPlain(violations, reason) {
|
|
48
71
|
if (violations.length === 0)
|
|
@@ -51,12 +74,21 @@ export function formatViolationsPlain(violations, reason) {
|
|
|
51
74
|
const reasonLine = reason ? `\nReason: ${reason}` : '';
|
|
52
75
|
const details = violations
|
|
53
76
|
.map((v, i) => {
|
|
77
|
+
// A configuration finding has no source location — it reports that a RULE
|
|
78
|
+
// enforces nothing, not that a line is wrong — so it carries `file: ''`
|
|
79
|
+
// and a `line` that means nothing. Rendering it produced a bare `(:1)`
|
|
80
|
+
// (bug 0047). The rich formatter and the GitHub formatter
|
|
81
|
+
// (`format-github.ts:58`) both already special-case this; plain and JSON
|
|
82
|
+
// were the two that did not.
|
|
83
|
+
const where = v.file === '' ? '' : ` (${v.file}:${String(v.line)})`;
|
|
54
84
|
const parts = [
|
|
55
|
-
` [${String(i + 1)}/${String(violations.length)}] ${v.element}: ${v.message}
|
|
85
|
+
` [${String(i + 1)}/${String(violations.length)}] ${v.element}: ${v.message}${where}`,
|
|
56
86
|
];
|
|
57
87
|
if (v.codeFrame)
|
|
58
88
|
parts.push(v.codeFrame);
|
|
59
|
-
|
|
89
|
+
// This format always renders `message`, so a remedy identical to it is
|
|
90
|
+
// already shown — see `remedyRepeatsMessage`.
|
|
91
|
+
if (v.suggestion && !remedyRepeatsMessage(v))
|
|
60
92
|
parts.push(` Fix: ${v.suggestion}`);
|
|
61
93
|
return parts.join('\n');
|
|
62
94
|
})
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAChD,OAAO,IAAI,MAAM,WAAW,CAAA;AAY5B,8DAA8D;AAC9D,SAAS,qBAAqB,CAC5B,CAAgB,EAChB,KAAa,EACb,KAAa,EACb,GAAW,EACX,cAAuB,EACvB,MAA0B;IAE1B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,2BAA2B,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;IAC9F,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;IAC9C,MAAM,
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declaration side of plan 0069: what a glob is, and how a set of globs
|
|
3
|
+
* combines, so that "this rule can never match anything" is answerable without
|
|
4
|
+
* running the rule.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here evaluates. Evaluation lives in `glob-diagnosis.ts`, against the
|
|
7
|
+
* path universe; this module only describes.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Which string a glob is matched against.
|
|
11
|
+
*
|
|
12
|
+
* This names **the target**, never the API. `SmellBuilder.inFolder()` matches
|
|
13
|
+
* the full file path despite its name (`src/smells/duplicate-bodies.ts`), and
|
|
14
|
+
* exactly one selector in `src/` — `resideInFolder` — matches a directory.
|
|
15
|
+
* Two revisions of the plan's own census got this wrong by reading the method
|
|
16
|
+
* name, and each time reported a vacuous rule as satisfiable.
|
|
17
|
+
*
|
|
18
|
+
* - `file-path` — the whole absolute path of the file
|
|
19
|
+
* - `parent-dir` — the immediate parent directory of the file, and only the
|
|
20
|
+
* immediate parent: `resideInFolder` tests
|
|
21
|
+
* `filePath.substring(0, filePath.lastIndexOf('/'))`
|
|
22
|
+
* - `import-target` — a resolved module path or a bare specifier. Never
|
|
23
|
+
* checked against the path universe: `node_modules` is
|
|
24
|
+
* outside the project by construction, so checking it would
|
|
25
|
+
* fail every correct dependency rule in existence
|
|
26
|
+
* - `specifier` — a raw module specifier
|
|
27
|
+
* - `literal` — matched against source text, not a path
|
|
28
|
+
*/
|
|
29
|
+
export type GlobKind = 'file-path' | 'parent-dir' | 'import-target' | 'specifier' | 'literal';
|
|
30
|
+
/**
|
|
31
|
+
* Where in a rule the glob was written, which decides whether being
|
|
32
|
+
* unsatisfiable is a fault.
|
|
33
|
+
*
|
|
34
|
+
* - `selector` — narrows the subject set. Unsatisfiable ⇒ the rule can never
|
|
35
|
+
* have subjects
|
|
36
|
+
* - `discovery` — defines a population (slices, layers, boundary folders).
|
|
37
|
+
* Unsatisfiable ⇒ nothing to check
|
|
38
|
+
* - `condition` — asserts something about subjects. Unsatisfiable is
|
|
39
|
+
* indistinguishable from an armed tripwire that has not fired
|
|
40
|
+
* - `exclusion` — subtracts. An exclusion matching zero is remedy-optional
|
|
41
|
+
* (proposal 006) and never a fault
|
|
42
|
+
*/
|
|
43
|
+
export type GlobPosition = 'selector' | 'discovery' | 'condition' | 'exclusion';
|
|
44
|
+
/**
|
|
45
|
+
* Is a dead glob at this position a **fault**?
|
|
46
|
+
*
|
|
47
|
+
* One owner for a decision that was written twice, inversely, and disagreed —
|
|
48
|
+
* [ts-archunit plan 0080](https://github.com/nielspeter/ts-archunit/blob/main/plans/completed/0080-admit-discovery-globs-to-the-dead-glob-gate.md).
|
|
49
|
+
*
|
|
50
|
+
* | site | said | i.e. |
|
|
51
|
+
* | --- | --- | --- |
|
|
52
|
+
* | `diagnose.ts` | skip `exclusion` and `condition` | selector **and discovery** are faults |
|
|
53
|
+
* | `terminal-builder.ts` | skip anything but `selector` | only selector is |
|
|
54
|
+
*
|
|
55
|
+
* So `doctor` reported a dead `discovery` glob and the check that gates the build
|
|
56
|
+
* did not — the divergence bug 0040's silence half is made of. Two hand-maintained
|
|
57
|
+
* inverse lists that must agree is the shape ADR-009 rule 5 keeps charging this
|
|
58
|
+
* project for; a shared predicate is the whole fix.
|
|
59
|
+
*
|
|
60
|
+
* **`selector` and `discovery` are faults.** Both name what a rule will judge, so
|
|
61
|
+
* a dead one means the rule judges nothing.
|
|
62
|
+
*
|
|
63
|
+
* **`condition` and `exclusion` are not.** A condition glob is an assertion
|
|
64
|
+
* *about* the subjects — `onlyImportFrom('**\/domain/**')` matching nothing is a
|
|
65
|
+
* satisfied rule, not a broken one (bug 0014) — and an exclusion glob matching
|
|
66
|
+
* nothing is an unused exemption, which `.excluding()` reports on its own terms.
|
|
67
|
+
*
|
|
68
|
+
* ## A `switch`, not an `||`, and that is the point
|
|
69
|
+
*
|
|
70
|
+
* It shipped as `position === 'selector' || position === 'discovery'` — an
|
|
71
|
+
* allow-list with no exhaustiveness check. Review measured the consequence: a
|
|
72
|
+
* **fifth** `GlobPosition` added to the union above compiles clean and leaves the
|
|
73
|
+
* whole suite green, silently a non-fault in all three consumers. A dead glob
|
|
74
|
+
* written at it would be invisible in `doctor` *and* in the build — which is the
|
|
75
|
+
* original divergence's failure direction, one level up, in the predicate that
|
|
76
|
+
* exists to remove it.
|
|
77
|
+
*
|
|
78
|
+
* The predicate's own test enumerates the four known values, which is a
|
|
79
|
+
* restatement of the implementation and structurally cannot cover a fifth. The
|
|
80
|
+
* `never` assignment below can, and it fails at compile time rather than at
|
|
81
|
+
* review time.
|
|
82
|
+
*/
|
|
83
|
+
export declare function isFaultPosition(position: GlobPosition): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Which set of paths the glob is written against.
|
|
86
|
+
*
|
|
87
|
+
* **This affects the verdict**, and the earlier claim here that it was
|
|
88
|
+
* message-only is withdrawn. Satisfiability is still taken against the union
|
|
89
|
+
* of the views for the glob's `kind`, but the ANCHOR check consults `base`:
|
|
90
|
+
* an unanchored glob can never match an absolute path, so for
|
|
91
|
+
* `base: 'absolute'` it is dead however the project is shaped — while for a
|
|
92
|
+
* base whose entry point rewrites or relativises the glob, an unanchored
|
|
93
|
+
* spelling is correct and telling the author to anchor it would break a
|
|
94
|
+
* working rule.
|
|
95
|
+
*
|
|
96
|
+
* The union alone was tried and was a false green on the commonest real
|
|
97
|
+
* mistake: the tsconfig-relative view accepts `'src/domain/**'`, which
|
|
98
|
+
* `resideInFolder` can never match. Unanchored globs are the entire subject of
|
|
99
|
+
* the 0.18.1 release, so a design that quietly calls them satisfiable defeats
|
|
100
|
+
* its own purpose.
|
|
101
|
+
*
|
|
102
|
+
* So a mis-declared `base` CAN cause a red build. It is set beside `kind` by
|
|
103
|
+
* the same code, from what the entry point does rather than from intent, and
|
|
104
|
+
* `tests/core/glob-declaration.test.ts` asserts the equivalent spellings agree.
|
|
105
|
+
*/
|
|
106
|
+
export type GlobBase = 'absolute' | 'tsconfig-relative' | 'normalized';
|
|
107
|
+
/**
|
|
108
|
+
* A glob as a predicate, condition or builder declares it.
|
|
109
|
+
*
|
|
110
|
+
* This is the author-facing half. `position` and `origin` are deliberately
|
|
111
|
+
* absent: the code that mints a site — inside `resideInFolder()`, several
|
|
112
|
+
* frames below any builder — cannot know either, and the builder stamps them
|
|
113
|
+
* on. Exposing them here would also let a rule author write
|
|
114
|
+
* `position: 'exclusion'` and permanently exempt their own predicate from the
|
|
115
|
+
* check, with no signal that they had.
|
|
116
|
+
*/
|
|
117
|
+
export interface DeclaredGlob {
|
|
118
|
+
readonly glob: string;
|
|
119
|
+
readonly kind: GlobKind;
|
|
120
|
+
/** Defaults to `'positive'`. A negative site can never be dead — see `GlobNode`. */
|
|
121
|
+
readonly polarity?: 'positive' | 'negative';
|
|
122
|
+
/**
|
|
123
|
+
* Defaults to `'absolute'`. **Affects the verdict** — see `GlobBase`. An
|
|
124
|
+
* unanchored glob is dead for `'absolute'` and correct for a base whose
|
|
125
|
+
* entry point rewrites or relativises it.
|
|
126
|
+
*/
|
|
127
|
+
readonly base?: GlobBase;
|
|
128
|
+
}
|
|
129
|
+
/** A declared glob after a builder has stamped on what only it knows. */
|
|
130
|
+
export type GlobSite = DeclaredGlob & {
|
|
131
|
+
readonly position: GlobPosition;
|
|
132
|
+
/** For the message: `resideInFolder("**\/src/x/**") in rule "adr005/no-any"`. */
|
|
133
|
+
readonly origin: string;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* A predicate or condition that declares no globs.
|
|
137
|
+
*
|
|
138
|
+
* Retained in the tree rather than dropped, and never dead. Dropping is safe
|
|
139
|
+
* under `all` — `some(dead)` is monotone — but `not()` turns an `all` into an
|
|
140
|
+
* `any`, and under `any` dropping an opaque child is what makes
|
|
141
|
+
* `or(deadGlob, exportSymbolNamed('Foo'))` red a working rule. Since most
|
|
142
|
+
* predicates declare no globs, that would have been the commonest failure of
|
|
143
|
+
* the whole mechanism.
|
|
144
|
+
*
|
|
145
|
+
* Retaining it costs no detection, removes the need for a separate `or()`
|
|
146
|
+
* propagation rule, and makes an empty node unreachable — which matters
|
|
147
|
+
* because `[].every()` is `true` and would otherwise fault a rule containing
|
|
148
|
+
* no globs at all. Verified exhaustively by
|
|
149
|
+
* `spikes/0069-tree-model-check.mjs`: 0 false reds and 0 missed emptiness
|
|
150
|
+
* across every expression of at most three combinator nodes.
|
|
151
|
+
*/
|
|
152
|
+
export interface OpaqueGlob {
|
|
153
|
+
readonly opaque: true;
|
|
154
|
+
}
|
|
155
|
+
/** A leaf of a glob tree: a declaration of some kind, or an opaque input. */
|
|
156
|
+
export type GlobLeaf<L> = L | OpaqueGlob;
|
|
157
|
+
/**
|
|
158
|
+
* How a set of globs combines.
|
|
159
|
+
*
|
|
160
|
+
* - `all` — every child must match for the whole to match, so the whole is
|
|
161
|
+
* dead if **any** child is dead. `and()`.
|
|
162
|
+
* - `any` — one child matching is enough, so the whole is dead only if
|
|
163
|
+
* **every** child is dead. `or()`, a variadic predicate
|
|
164
|
+
* (`importFrom(...globs)` is `matchers.some`), repeated `.inFolder()` calls.
|
|
165
|
+
*
|
|
166
|
+
* A preset's option list is **not** an `any` node: both shipped presets fan
|
|
167
|
+
* out one rule per glob (`src/presets/layered.ts`, `src/presets/boundaries.ts`),
|
|
168
|
+
* so one dead layer glob is one vacuous rule, and `any` would say "no fault
|
|
169
|
+
* unless every layer is dead" — a false green inside a preset. Each generated
|
|
170
|
+
* builder declares its own root instead.
|
|
171
|
+
*/
|
|
172
|
+
export interface GlobTree<L> {
|
|
173
|
+
readonly op: 'any' | 'all';
|
|
174
|
+
readonly children: readonly (GlobTree<L> | GlobLeaf<L>)[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* What a predicate, condition or builder method declares.
|
|
178
|
+
*
|
|
179
|
+
* Its leaves are `DeclaredGlob`, which has no `position` and no `origin`, so
|
|
180
|
+
* a rule author **cannot** express "this is an exclusion" and thereby exempt
|
|
181
|
+
* their own predicate from the check. That is a type-level guarantee rather
|
|
182
|
+
* than a convention.
|
|
183
|
+
*/
|
|
184
|
+
export type DeclaredGlobs = GlobTree<DeclaredGlob>;
|
|
185
|
+
/** A declared tree after a builder has stamped position and origin onto it. */
|
|
186
|
+
export type GlobNode = GlobTree<GlobSite>;
|
|
187
|
+
/** How many real declarations a tree holds. Opaque leaves do not count. */
|
|
188
|
+
export declare function countDeclaredGlobs<L extends object>(tree: GlobTree<L>): number;
|
|
189
|
+
/** Narrow a tree position to an interior node. */
|
|
190
|
+
export declare function isGlobNode<L extends object>(value: GlobTree<L> | GlobLeaf<L>): value is GlobTree<L>;
|
|
191
|
+
/** Narrow a leaf to the opaque case. */
|
|
192
|
+
export declare function isOpaqueGlob<L extends object>(value: GlobTree<L> | GlobLeaf<L>): value is OpaqueGlob;
|
|
193
|
+
/**
|
|
194
|
+
* A variadic declaration: any one of these globs matching is enough.
|
|
195
|
+
*
|
|
196
|
+
* `importFrom(...globs)` is `matchers.some`, so the set is dead only when
|
|
197
|
+
* every glob in it is — which is exactly `any`. Getting this wrong in the
|
|
198
|
+
* other direction is what the 0.18.1 withdrawal was.
|
|
199
|
+
*/
|
|
200
|
+
export declare function globAnyOf(globs: readonly string[], kind: GlobKind, base?: GlobBase): DeclaredGlobs;
|
|
201
|
+
/** A single declaration as a one-element `any` tree. */
|
|
202
|
+
export declare function globNode<L extends object>(leaf: L): GlobTree<L>;
|
|
203
|
+
/**
|
|
204
|
+
* Stamp a declared tree with what only the builder knows.
|
|
205
|
+
*
|
|
206
|
+
* `origin` is a function of the declaration so each site can name itself —
|
|
207
|
+
* `resideInFolder("**\/src/x/**")` — rather than every site in one predicate
|
|
208
|
+
* sharing a single label.
|
|
209
|
+
*/
|
|
210
|
+
export declare function stampGlobs(declared: DeclaredGlobs, position: GlobPosition, origin: (glob: DeclaredGlob) => string): GlobNode;
|
|
211
|
+
/**
|
|
212
|
+
* Negate a glob tree: invert `op` at every node **and** flip `polarity` at
|
|
213
|
+
* every site.
|
|
214
|
+
*
|
|
215
|
+
* A polarity flip alone is not enough, and the shortfall is not exotic — it is
|
|
216
|
+
* reachable through public exports, since `and()` returns a `Predicate<T>` and
|
|
217
|
+
* `not()` takes one. `not(and(live, not(dead)))` selects a non-empty set and
|
|
218
|
+
* would be reported dead; `not(or(live, not(dead)))` selects nothing and would
|
|
219
|
+
* be missed. Inverting `op` as well is the standard negation-normal-form
|
|
220
|
+
* push-down and fixes both directions, leaving every simpler case unchanged:
|
|
221
|
+
* `not(not(dead))` still faults, `not(and(a, b))` still does not.
|
|
222
|
+
*/
|
|
223
|
+
export declare function negateGlobs<L extends DeclaredGlob>(tree: GlobTree<L>): GlobTree<L>;
|
|
224
|
+
/**
|
|
225
|
+
* Combine inputs into one node, treating a missing declaration as opaque.
|
|
226
|
+
*
|
|
227
|
+
* Every input contributes exactly one child, so the arity of the node always
|
|
228
|
+
* matches the arity of the combinator — which is what keeps `negateGlobs`
|
|
229
|
+
* sound and empty nodes unreachable.
|
|
230
|
+
*/
|
|
231
|
+
export declare function combineGlobs<L extends object>(op: 'any' | 'all', inputs: readonly (GlobTree<L> | undefined)[]): GlobTree<L>;
|
|
232
|
+
//# sourceMappingURL=glob-site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-site.d.ts","sourceRoot":"","sources":["../src/glob-site.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The declaration side of plan 0069: what a glob is, and how a set of globs
|
|
3
|
+
* combines, so that "this rule can never match anything" is answerable without
|
|
4
|
+
* running the rule.
|
|
5
|
+
*
|
|
6
|
+
* Nothing here evaluates. Evaluation lives in `glob-diagnosis.ts`, against the
|
|
7
|
+
* path universe; this module only describes.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Is a dead glob at this position a **fault**?
|
|
11
|
+
*
|
|
12
|
+
* One owner for a decision that was written twice, inversely, and disagreed —
|
|
13
|
+
* [ts-archunit plan 0080](https://github.com/nielspeter/ts-archunit/blob/main/plans/completed/0080-admit-discovery-globs-to-the-dead-glob-gate.md).
|
|
14
|
+
*
|
|
15
|
+
* | site | said | i.e. |
|
|
16
|
+
* | --- | --- | --- |
|
|
17
|
+
* | `diagnose.ts` | skip `exclusion` and `condition` | selector **and discovery** are faults |
|
|
18
|
+
* | `terminal-builder.ts` | skip anything but `selector` | only selector is |
|
|
19
|
+
*
|
|
20
|
+
* So `doctor` reported a dead `discovery` glob and the check that gates the build
|
|
21
|
+
* did not — the divergence bug 0040's silence half is made of. Two hand-maintained
|
|
22
|
+
* inverse lists that must agree is the shape ADR-009 rule 5 keeps charging this
|
|
23
|
+
* project for; a shared predicate is the whole fix.
|
|
24
|
+
*
|
|
25
|
+
* **`selector` and `discovery` are faults.** Both name what a rule will judge, so
|
|
26
|
+
* a dead one means the rule judges nothing.
|
|
27
|
+
*
|
|
28
|
+
* **`condition` and `exclusion` are not.** A condition glob is an assertion
|
|
29
|
+
* *about* the subjects — `onlyImportFrom('**\/domain/**')` matching nothing is a
|
|
30
|
+
* satisfied rule, not a broken one (bug 0014) — and an exclusion glob matching
|
|
31
|
+
* nothing is an unused exemption, which `.excluding()` reports on its own terms.
|
|
32
|
+
*
|
|
33
|
+
* ## A `switch`, not an `||`, and that is the point
|
|
34
|
+
*
|
|
35
|
+
* It shipped as `position === 'selector' || position === 'discovery'` — an
|
|
36
|
+
* allow-list with no exhaustiveness check. Review measured the consequence: a
|
|
37
|
+
* **fifth** `GlobPosition` added to the union above compiles clean and leaves the
|
|
38
|
+
* whole suite green, silently a non-fault in all three consumers. A dead glob
|
|
39
|
+
* written at it would be invisible in `doctor` *and* in the build — which is the
|
|
40
|
+
* original divergence's failure direction, one level up, in the predicate that
|
|
41
|
+
* exists to remove it.
|
|
42
|
+
*
|
|
43
|
+
* The predicate's own test enumerates the four known values, which is a
|
|
44
|
+
* restatement of the implementation and structurally cannot cover a fifth. The
|
|
45
|
+
* `never` assignment below can, and it fails at compile time rather than at
|
|
46
|
+
* review time.
|
|
47
|
+
*/
|
|
48
|
+
export function isFaultPosition(position) {
|
|
49
|
+
switch (position) {
|
|
50
|
+
case 'selector':
|
|
51
|
+
case 'discovery':
|
|
52
|
+
return true;
|
|
53
|
+
case 'condition':
|
|
54
|
+
case 'exclusion':
|
|
55
|
+
return false;
|
|
56
|
+
default: {
|
|
57
|
+
// A new position must say which it is. Deciding by default is how the two
|
|
58
|
+
// inverse lists came to disagree in the first place.
|
|
59
|
+
const exhaustive = position;
|
|
60
|
+
return exhaustive;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** How many real declarations a tree holds. Opaque leaves do not count. */
|
|
65
|
+
export function countDeclaredGlobs(tree) {
|
|
66
|
+
let total = 0;
|
|
67
|
+
for (const child of tree.children) {
|
|
68
|
+
if (isGlobNode(child))
|
|
69
|
+
total += countDeclaredGlobs(child);
|
|
70
|
+
else if (!isOpaqueGlob(child))
|
|
71
|
+
total++;
|
|
72
|
+
}
|
|
73
|
+
return total;
|
|
74
|
+
}
|
|
75
|
+
/** Narrow a tree position to an interior node. */
|
|
76
|
+
export function isGlobNode(value) {
|
|
77
|
+
return 'op' in value;
|
|
78
|
+
}
|
|
79
|
+
/** Narrow a leaf to the opaque case. */
|
|
80
|
+
export function isOpaqueGlob(value) {
|
|
81
|
+
return 'opaque' in value;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A variadic declaration: any one of these globs matching is enough.
|
|
85
|
+
*
|
|
86
|
+
* `importFrom(...globs)` is `matchers.some`, so the set is dead only when
|
|
87
|
+
* every glob in it is — which is exactly `any`. Getting this wrong in the
|
|
88
|
+
* other direction is what the 0.18.1 withdrawal was.
|
|
89
|
+
*/
|
|
90
|
+
export function globAnyOf(globs, kind, base) {
|
|
91
|
+
return { op: 'any', children: globs.map((glob) => ({ glob, kind, base })) };
|
|
92
|
+
}
|
|
93
|
+
/** A single declaration as a one-element `any` tree. */
|
|
94
|
+
export function globNode(leaf) {
|
|
95
|
+
return { op: 'any', children: [leaf] };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Stamp a declared tree with what only the builder knows.
|
|
99
|
+
*
|
|
100
|
+
* `origin` is a function of the declaration so each site can name itself —
|
|
101
|
+
* `resideInFolder("**\/src/x/**")` — rather than every site in one predicate
|
|
102
|
+
* sharing a single label.
|
|
103
|
+
*/
|
|
104
|
+
export function stampGlobs(declared, position, origin) {
|
|
105
|
+
return {
|
|
106
|
+
op: declared.op,
|
|
107
|
+
children: declared.children.map((child) => {
|
|
108
|
+
if (isGlobNode(child))
|
|
109
|
+
return stampGlobs(child, position, origin);
|
|
110
|
+
if (isOpaqueGlob(child))
|
|
111
|
+
return child;
|
|
112
|
+
return { ...child, position, origin: origin(child) };
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Negate a glob tree: invert `op` at every node **and** flip `polarity` at
|
|
118
|
+
* every site.
|
|
119
|
+
*
|
|
120
|
+
* A polarity flip alone is not enough, and the shortfall is not exotic — it is
|
|
121
|
+
* reachable through public exports, since `and()` returns a `Predicate<T>` and
|
|
122
|
+
* `not()` takes one. `not(and(live, not(dead)))` selects a non-empty set and
|
|
123
|
+
* would be reported dead; `not(or(live, not(dead)))` selects nothing and would
|
|
124
|
+
* be missed. Inverting `op` as well is the standard negation-normal-form
|
|
125
|
+
* push-down and fixes both directions, leaving every simpler case unchanged:
|
|
126
|
+
* `not(not(dead))` still faults, `not(and(a, b))` still does not.
|
|
127
|
+
*/
|
|
128
|
+
export function negateGlobs(tree) {
|
|
129
|
+
return {
|
|
130
|
+
op: tree.op === 'all' ? 'any' : 'all',
|
|
131
|
+
children: tree.children.map((child) => isGlobNode(child) ? negateGlobs(child) : negateLeaf(child)),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function negateLeaf(leaf) {
|
|
135
|
+
if (isOpaqueGlob(leaf))
|
|
136
|
+
return leaf;
|
|
137
|
+
return {
|
|
138
|
+
...leaf,
|
|
139
|
+
polarity: (leaf.polarity ?? 'positive') === 'positive' ? 'negative' : 'positive',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Combine inputs into one node, treating a missing declaration as opaque.
|
|
144
|
+
*
|
|
145
|
+
* Every input contributes exactly one child, so the arity of the node always
|
|
146
|
+
* matches the arity of the combinator — which is what keeps `negateGlobs`
|
|
147
|
+
* sound and empty nodes unreachable.
|
|
148
|
+
*/
|
|
149
|
+
export function combineGlobs(op, inputs) {
|
|
150
|
+
return {
|
|
151
|
+
op,
|
|
152
|
+
children: inputs.map((input) => input ?? OPAQUE),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const OPAQUE = { opaque: true };
|
|
156
|
+
//# sourceMappingURL=glob-site.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob-site.js","sourceRoot":"","sources":["../src/glob-site.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuCH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,UAAU,eAAe,CAAC,QAAsB;IACpD,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,UAAU,CAAC;QAChB,KAAK,WAAW;YACd,OAAO,IAAI,CAAA;QACb,KAAK,WAAW,CAAC;QACjB,KAAK,WAAW;YACd,OAAO,KAAK,CAAA;QACd,OAAO,CAAC,CAAC,CAAC;YACR,0EAA0E;YAC1E,qDAAqD;YACrD,MAAM,UAAU,GAAU,QAAQ,CAAA;YAClC,OAAO,UAAU,CAAA;QACnB,CAAC;IACH,CAAC;AACH,CAAC;AAiHD,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAmB,IAAiB;IACpE,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,UAAU,CAAC,KAAK,CAAC;YAAE,KAAK,IAAI,kBAAkB,CAAC,KAAK,CAAC,CAAA;aACpD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;YAAE,KAAK,EAAE,CAAA;IACxC,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,UAAU,CACxB,KAAgC;IAEhC,OAAO,IAAI,IAAI,KAAK,CAAA;AACtB,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,YAAY,CAC1B,KAAgC;IAEhC,OAAO,QAAQ,IAAI,KAAK,CAAA;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,KAAwB,EACxB,IAAc,EACd,IAAe;IAEf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAA;AAC7E,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,QAAQ,CAAmB,IAAO;IAChD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,CAAA;AACxC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CACxB,QAAuB,EACvB,QAAsB,EACtB,MAAsC;IAEtC,OAAO;QACL,EAAE,EAAE,QAAQ,CAAC,EAAE;QACf,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACxC,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAA;YACjE,IAAI,YAAY,CAAC,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAA;YACrC,OAAO,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;QACtD,CAAC,CAAC;KACH,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,WAAW,CAAyB,IAAiB;IACnE,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK;QACrC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACpC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAC3D;KACF,CAAA;AACH,CAAC;AAED,SAAS,UAAU,CAAyB,IAAiB;IAC3D,IAAI,YAAY,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,OAAO;QACL,GAAG,IAAI;QACP,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,IAAI,UAAU,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;KACjF,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,EAAiB,EACjB,MAA4C;IAE5C,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,MAAM,CAAC;KACjD,CAAA;AACH,CAAC;AAED,MAAM,MAAM,GAAe,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the identity root for a baseline: the repository or workspace root
|
|
3
|
+
* containing `startDir`.
|
|
4
|
+
*
|
|
5
|
+
* Resolution order, all **nearest-first**:
|
|
6
|
+
*
|
|
7
|
+
* 1. `.git` — the definitive repository root. Tested with `existsSync`, not
|
|
8
|
+
* `isDirectory`: in a worktree or submodule it is a *file* holding a gitdir
|
|
9
|
+
* pointer, and a worktree is precisely the "same code, different absolute
|
|
10
|
+
* path" case this module exists to survive.
|
|
11
|
+
* 2. a `package.json` declaring `workspaces` — a monorepo root without git.
|
|
12
|
+
* 3. any `package.json` — a single-package project.
|
|
13
|
+
* 4. `startDir` itself, when nothing is found. That degrades to the previous
|
|
14
|
+
* behaviour for that one path rather than guessing.
|
|
15
|
+
*
|
|
16
|
+
* Nearest, **not** outermost. Outermost is tempting for monorepos, but it is
|
|
17
|
+
* wrong the moment any ancestor is also a repository — a home directory under
|
|
18
|
+
* dotfiles version control is the common case — because the root would then sit
|
|
19
|
+
* above the checkout and the "relative" path would still carry
|
|
20
|
+
* `Documents/Projects/…`, which differs per machine. It reproduces the bug it
|
|
21
|
+
* is meant to fix. The nearest `.git` is the repository root by definition, and
|
|
22
|
+
* in a monorepo that is already the workspace root, so nearest costs nothing.
|
|
23
|
+
*/
|
|
24
|
+
export declare function discoverIdentityRoot(startDir: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Convert a filesystem path to its portable form: relative to the identity
|
|
27
|
+
* root, with forward slashes.
|
|
28
|
+
*
|
|
29
|
+
* A path outside the root is returned unchanged — relativising it would encode
|
|
30
|
+
* the root's *depth* (`../../..`), which varies by machine and would reproduce
|
|
31
|
+
* the very bug this fixes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toPortablePath(filePath: string, root: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Replace every occurrence of the identity root inside free text with a stable
|
|
36
|
+
* token.
|
|
37
|
+
*
|
|
38
|
+
* Rule descriptions, element names and messages are prose: producers
|
|
39
|
+
* interpolate `getFilePath()` into them (`src/smells/duplicate-bodies.ts`,
|
|
40
|
+
* `src/presets/boundaries.ts`, and any user-written `defineCondition`). Since
|
|
41
|
+
* the text is unstructured, this substitutes the known root prefix rather than
|
|
42
|
+
* trying to parse paths out of it — a regex would also rewrite the `/src`
|
|
43
|
+
* inside a doubled-star glob, which is not a path at all, and relativising it
|
|
44
|
+
* would encode the root's depth.
|
|
45
|
+
*
|
|
46
|
+
* Substring replacement is sufficient because the only machine-dependent part
|
|
47
|
+
* of an interpolated path IS the root prefix; everything after it is a property
|
|
48
|
+
* of the repository.
|
|
49
|
+
*
|
|
50
|
+
* Only the *root* is separator-normalized, never the surrounding text: rule
|
|
51
|
+
* descriptions embed regex sources (`/\bparse/`), and rewriting their
|
|
52
|
+
* backslashes would let two distinct rules collide on one identity.
|
|
53
|
+
*/
|
|
54
|
+
export declare function normalizeIdentityText(text: string, root: string): string;
|
|
55
|
+
//# sourceMappingURL=identity-root.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity-root.d.ts","sourceRoot":"","sources":["../src/identity-root.ts"],"names":[],"mappings":"AAsBA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAuB7D;AAmDD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUxE"}
|