@nielspeter/eess-crossvalidate 0.1.2 → 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.
- package/README.md +41 -1
- package/dist/gherkin-ts.d.ts +68 -0
- package/dist/gherkin-ts.d.ts.map +1 -0
- package/dist/gherkin-ts.js +161 -0
- package/dist/gherkin-ts.js.map +1 -0
- package/dist/it-title.d.ts +72 -0
- package/dist/it-title.d.ts.map +1 -0
- package/dist/it-title.js +115 -0
- package/dist/it-title.js.map +1 -0
- package/dist/md-ts.d.ts +10 -0
- package/dist/md-ts.d.ts.map +1 -1
- package/dist/md-ts.js +58 -12
- package/dist/md-ts.js.map +1 -1
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -34,10 +34,50 @@ AST-grounded: cited titles are resolved against actual `it()` call expressions
|
|
|
34
34
|
(via eess-ts's public API — no ts-morph here, per ADR-007), so it also catches
|
|
35
35
|
no-substitution template titles the text-level check misses.
|
|
36
36
|
|
|
37
|
+
## Markdown ↔ Gherkin
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { scenarioCitationsResolve } from '@nielspeter/eess-crossvalidate/md-gherkin'
|
|
41
|
+
import { corpus } from '@nielspeter/eess-md'
|
|
42
|
+
import { features } from '@nielspeter/eess-gherkin'
|
|
43
|
+
|
|
44
|
+
// every scenario a story cites — `checkout.feature` · 'Apply a valid code' —
|
|
45
|
+
// resolves to a real scenario in the feature set
|
|
46
|
+
scenarioCitationsResolve(
|
|
47
|
+
corpus({ roots: ['docs/**'] }),
|
|
48
|
+
features({ roots: ['features/**/*.feature'] }),
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Spec↔spec: a markdown story cites a behavior — a backticked `.feature` path plus
|
|
53
|
+
an optional quoted scenario title — and the citation is resolved against the
|
|
54
|
+
loaded feature set. Three failure modes fail the build: the feature file is
|
|
55
|
+
missing, the path is ambiguous, or the scenario title doesn't exist. The
|
|
56
|
+
`gherkin-ts` preset below is its code↔spec mirror.
|
|
57
|
+
|
|
58
|
+
## Gherkin ↔ TypeScript
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { scenarioTestsResolve } from '@nielspeter/eess-crossvalidate/gherkin-ts'
|
|
62
|
+
import { features } from '@nielspeter/eess-gherkin'
|
|
63
|
+
import { project } from '@nielspeter/eess-ts'
|
|
64
|
+
|
|
65
|
+
// every scenario a test cites — it('checkout.feature › Apply a valid code') —
|
|
66
|
+
// resolves to a real scenario in the feature set
|
|
67
|
+
scenarioTestsResolve(project('tsconfig.json'), features({ roots: ['features/**/*.feature'] }))
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The mirror image of md↔gherkin's story citations: here the citing side is the
|
|
71
|
+
**test AST** (`it('…')` titles, read via eess-ts's public API — no ts-morph, per
|
|
72
|
+
ADR-007). A scenario renamed or deleted out from under the test that cites it
|
|
73
|
+
fails the build. It resolves the citation; it does not claim the test exercises
|
|
74
|
+
the scenario's steps (Tier 2, still open).
|
|
75
|
+
|
|
37
76
|
## Peers
|
|
38
77
|
|
|
39
78
|
The dialects are optional peer dependencies — install the ones your presets use
|
|
40
|
-
(`@nielspeter/eess-ts`, `@nielspeter/eess-mermaid`, `@nielspeter/eess-md
|
|
79
|
+
(`@nielspeter/eess-ts`, `@nielspeter/eess-mermaid`, `@nielspeter/eess-md`,
|
|
80
|
+
`@nielspeter/eess-gherkin`).
|
|
41
81
|
|
|
42
82
|
## License
|
|
43
83
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type ArchViolation, type PresetReportOptions } from '@nielspeter/eess';
|
|
2
|
+
import { type ArchProject } from '@nielspeter/eess-ts';
|
|
3
|
+
import type { FeatureSet, GherkinScenario } from '@nielspeter/eess-gherkin';
|
|
4
|
+
export interface ScenarioTestsResolveOptions extends PresetReportOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Custom citation extractor for transition periods — receives one `it('…')`
|
|
7
|
+
* title and returns the scenario it cites, or `undefined` if the title is not
|
|
8
|
+
* a citation. Default: the frozen convention adapted to a single string —
|
|
9
|
+
* `` it('<path>.feature › <Scenario title>') `` (plan 0080).
|
|
10
|
+
*/
|
|
11
|
+
readonly extract?: (itTitle: string) => ExtractedTestCitation | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface ScenariosCoveredOptions extends PresetReportOptions {
|
|
14
|
+
/** Custom citation extractor — same contract as {@link ScenarioTestsResolveOptions.extract}. */
|
|
15
|
+
readonly extract?: (itTitle: string) => ExtractedTestCitation | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Require coverage only for scenarios matching this predicate — the opt-in
|
|
18
|
+
* scope. Default: every scenario. Use it to exclude un-implemented flows,
|
|
19
|
+
* e.g. `(s) => !s.tags.includes('wip')`.
|
|
20
|
+
*/
|
|
21
|
+
readonly include?: (scenario: GherkinScenario) => boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface ExtractedTestCitation {
|
|
24
|
+
/** The cited feature path, as written (may be a unique suffix). */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
/** The cited scenario title. */
|
|
27
|
+
readonly title: string;
|
|
28
|
+
}
|
|
29
|
+
/** The default `it()`-title convention: `<path>.feature › <title>`. */
|
|
30
|
+
declare function defaultExtract(itTitle: string): ExtractedTestCitation | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Cross-validate that every scenario citation in the *test* AST resolves against
|
|
33
|
+
* the loaded feature set — the mirror image of `scenarioCitationsResolve`, whose
|
|
34
|
+
* left side is the markdown corpus; here it is the project's `it('…')` titles.
|
|
35
|
+
* This is the code↔spec half plan 0069 deferred ("this one is spec↔spec only").
|
|
36
|
+
*
|
|
37
|
+
* A citation is an `it()` whose title matches the convention `<path>.feature ›
|
|
38
|
+
* <Scenario title>` (overridable via `extract`). Three failure modes are gated:
|
|
39
|
+
* the cited feature file doesn't exist in the set, the path is ambiguous
|
|
40
|
+
* (matches several files), or the cited scenario title doesn't exist in that
|
|
41
|
+
* file. A test whose title is not a citation is ignored.
|
|
42
|
+
*
|
|
43
|
+
* Resolves a citation; it does NOT claim the test exercises the scenario's
|
|
44
|
+
* steps — that is Tier 2, still open (plan 0079).
|
|
45
|
+
*/
|
|
46
|
+
export declare function scenarioTestsResolve(project: ArchProject, set: FeatureSet, options?: ScenarioTestsResolveOptions): ArchViolation[];
|
|
47
|
+
/**
|
|
48
|
+
* Cross-validate that every in-scope scenario is cited by at least one test —
|
|
49
|
+
* the coverage (right→left) direction, the complement of `scenarioTestsResolve`.
|
|
50
|
+
* A scenario shipped with no citing test fails; `include` narrows the scope so
|
|
51
|
+
* `@wip` / un-implemented flows do not force a red build.
|
|
52
|
+
*
|
|
53
|
+
* Assumes scenario titles are unique within a file — coverage keys on
|
|
54
|
+
* `relPath + title`, so two identically-titled scenarios share one key and
|
|
55
|
+
* citing one marks its twin covered. Pair with eess-gherkin's `haveUniqueTitles`
|
|
56
|
+
* to close that gap.
|
|
57
|
+
*
|
|
58
|
+
* Coverage means "a test cites this scenario", not "a test exercises its steps"
|
|
59
|
+
* (Tier 2, still open — plan 0079).
|
|
60
|
+
*/
|
|
61
|
+
export declare function scenariosCovered(project: ArchProject, set: FeatureSet, options?: ScenariosCoveredOptions): ArchViolation[];
|
|
62
|
+
/** Count citing tests / scenarios for a caller's non-vacuity summary line. */
|
|
63
|
+
export declare function scenarioTestStats(project: ArchProject, set: FeatureSet, options?: ScenarioTestsResolveOptions): {
|
|
64
|
+
citations: number;
|
|
65
|
+
scenarios: number;
|
|
66
|
+
};
|
|
67
|
+
export { defaultExtract };
|
|
68
|
+
//# sourceMappingURL=gherkin-ts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gherkin-ts.d.ts","sourceRoot":"","sources":["../src/gherkin-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC7F,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAG3E,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,qBAAqB,GAAG,SAAS,CAAA;CAC1E;AAED,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,gGAAgG;IAChG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,qBAAqB,GAAG,SAAS,CAAA;IACzE;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAA;CAC1D;AAED,MAAM,WAAW,qBAAqB;IACpC,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,gCAAgC;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAcD,uEAAuE;AACvE,iBAAS,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAM1E;AA+CD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,2BAAgC,GACxC,aAAa,EAAE,CA0CjB;AA+BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,uBAA4B,GACpC,aAAa,EAAE,CAgBjB;AAED,8EAA8E;AAC9E,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,2BAAgC,GACxC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAM1C;AAGD,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { finishPreset } from '@nielspeter/eess';
|
|
2
|
+
import { calls } from '@nielspeter/eess-ts';
|
|
3
|
+
import { itOrTestTitleOf } from './it-title.js';
|
|
4
|
+
// The gherkin↔ts citation convention (plan 0080): a test names the scenario it
|
|
5
|
+
// proves in its `it()` title — a feature path (unique `/`-boundary suffix, like
|
|
6
|
+
// md↔gherkin) then the scenario title, separated by `›` or `·`.
|
|
7
|
+
const IT_CITE_RE = /^(?<path>.*\.feature)\s*[›·]\s*(?<title>.+)$/;
|
|
8
|
+
const RULE = 'tests that cite a scenario should cite one that exists';
|
|
9
|
+
/** The default `it()`-title convention: `<path>.feature › <title>`. */
|
|
10
|
+
function defaultExtract(itTitle) {
|
|
11
|
+
const m = IT_CITE_RE.exec(itTitle);
|
|
12
|
+
const path = m?.groups?.path;
|
|
13
|
+
const title = m?.groups?.title;
|
|
14
|
+
if (path === undefined || title === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
return { path, title };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Read every `it('…')` title from the project via eess-ts's public call API —
|
|
20
|
+
* no ts-morph here, per ADR-007, exactly as `adrCitationsResolve` does. Sees
|
|
21
|
+
* `` it(`no-substitution template`) `` titles a source regex would miss. The
|
|
22
|
+
* title grammar itself lives in `it-title.ts`, shared with md↔ts (bug 0104).
|
|
23
|
+
*/
|
|
24
|
+
function itTitles(project) {
|
|
25
|
+
const allCalls = calls(project).select({
|
|
26
|
+
label: 'call',
|
|
27
|
+
identify: (c) => ({ name: c.getName() ?? '' }),
|
|
28
|
+
}).elements;
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const call of allCalls) {
|
|
31
|
+
// Accept `it(...)` / `test(...)` and their modifier forms (`it.only`,
|
|
32
|
+
// `it.skip`, `test.concurrent`, …) — the root callee is what matters, not
|
|
33
|
+
// the modifier. The gate binds a citation, it does not run the test, so a
|
|
34
|
+
// skipped test's citation is still checked (consistent with "cites, not
|
|
35
|
+
// exercises"). `it.each(table)(...)` is a call-of-a-call with a templated
|
|
36
|
+
// `%s` title — no static citation — so `root` is undefined and it is skipped.
|
|
37
|
+
const root = call.getObjectName() ?? call.getMethodName();
|
|
38
|
+
if (root !== 'it' && root !== 'test')
|
|
39
|
+
continue;
|
|
40
|
+
const title = itOrTestTitleOf(call.getName({ withArgument: 0 }) ?? '');
|
|
41
|
+
if (title === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
out.push({ title, file: call.getSourceFile().getFilePath(), line: call.getStartLineNumber() });
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Resolve a cited path against the set: exact relPath, or unique `/`-boundary suffix. */
|
|
48
|
+
function resolveFeature(path, set) {
|
|
49
|
+
const all = set.features().map((f) => f.relPath);
|
|
50
|
+
if (all.includes(path))
|
|
51
|
+
return [path];
|
|
52
|
+
return all.filter((rel) => rel.endsWith(`/${path}`));
|
|
53
|
+
}
|
|
54
|
+
const v = (site, message, because) => ({
|
|
55
|
+
rule: RULE,
|
|
56
|
+
ruleId: 'crossval/scenario-tests-resolve',
|
|
57
|
+
element: `${site.file}:${site.line}`,
|
|
58
|
+
file: site.file,
|
|
59
|
+
line: site.line,
|
|
60
|
+
message,
|
|
61
|
+
because,
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* Cross-validate that every scenario citation in the *test* AST resolves against
|
|
65
|
+
* the loaded feature set — the mirror image of `scenarioCitationsResolve`, whose
|
|
66
|
+
* left side is the markdown corpus; here it is the project's `it('…')` titles.
|
|
67
|
+
* This is the code↔spec half plan 0069 deferred ("this one is spec↔spec only").
|
|
68
|
+
*
|
|
69
|
+
* A citation is an `it()` whose title matches the convention `<path>.feature ›
|
|
70
|
+
* <Scenario title>` (overridable via `extract`). Three failure modes are gated:
|
|
71
|
+
* the cited feature file doesn't exist in the set, the path is ambiguous
|
|
72
|
+
* (matches several files), or the cited scenario title doesn't exist in that
|
|
73
|
+
* file. A test whose title is not a citation is ignored.
|
|
74
|
+
*
|
|
75
|
+
* Resolves a citation; it does NOT claim the test exercises the scenario's
|
|
76
|
+
* steps — that is Tier 2, still open (plan 0079).
|
|
77
|
+
*/
|
|
78
|
+
export function scenarioTestsResolve(project, set, options = {}) {
|
|
79
|
+
const extract = options.extract ?? defaultExtract;
|
|
80
|
+
const scenarioKeys = new Set(set.scenarios().map((s) => `${s.relPath} ${s.title}`));
|
|
81
|
+
const violations = [];
|
|
82
|
+
for (const site of itTitles(project)) {
|
|
83
|
+
const cite = extract(site.title);
|
|
84
|
+
if (cite === undefined)
|
|
85
|
+
continue; // not a scenario-citing test
|
|
86
|
+
const resolved = resolveFeature(cite.path, set);
|
|
87
|
+
if (resolved.length === 0) {
|
|
88
|
+
violations.push(v(site, `cites \`${cite.path}\` — no such feature file in the set`, 'a test citing a missing behavior spec is a dangling reference'));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (resolved.length > 1) {
|
|
92
|
+
violations.push(v(site, `cites \`${cite.path}\` — ambiguous, matches ${resolved.length} feature files (${resolved.join(', ')})`, 'an ambiguous citation cannot be mechanically resolved; cite a longer suffix'));
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const rel = resolved[0];
|
|
96
|
+
if (rel !== undefined && !scenarioKeys.has(`${rel} ${cite.title}`)) {
|
|
97
|
+
violations.push(v(site, `cites '${cite.title}' in \`${rel}\` — no such scenario in that feature file`, 'a renamed or deleted scenario must not silently orphan the test that proves it'));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return finishPreset(violations, options);
|
|
101
|
+
}
|
|
102
|
+
const COVER_RULE = 'every scenario should be proven by a citing test';
|
|
103
|
+
const sv = (scenario, message, because) => ({
|
|
104
|
+
rule: COVER_RULE,
|
|
105
|
+
ruleId: 'crossval/scenarios-covered',
|
|
106
|
+
element: `${scenario.relPath} › ${scenario.title}`,
|
|
107
|
+
file: scenario.file,
|
|
108
|
+
line: scenario.line,
|
|
109
|
+
message,
|
|
110
|
+
because,
|
|
111
|
+
});
|
|
112
|
+
/** The set of `relPath title` keys a test unambiguously cites in the project. */
|
|
113
|
+
function citedScenarioKeys(project, set, extract) {
|
|
114
|
+
const covered = new Set();
|
|
115
|
+
for (const site of itTitles(project)) {
|
|
116
|
+
const cite = extract(site.title);
|
|
117
|
+
if (cite === undefined)
|
|
118
|
+
continue;
|
|
119
|
+
const resolved = resolveFeature(cite.path, set);
|
|
120
|
+
const rel = resolved.length === 1 ? resolved[0] : undefined;
|
|
121
|
+
if (rel !== undefined)
|
|
122
|
+
covered.add(`${rel} ${cite.title}`);
|
|
123
|
+
}
|
|
124
|
+
return covered;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Cross-validate that every in-scope scenario is cited by at least one test —
|
|
128
|
+
* the coverage (right→left) direction, the complement of `scenarioTestsResolve`.
|
|
129
|
+
* A scenario shipped with no citing test fails; `include` narrows the scope so
|
|
130
|
+
* `@wip` / un-implemented flows do not force a red build.
|
|
131
|
+
*
|
|
132
|
+
* Assumes scenario titles are unique within a file — coverage keys on
|
|
133
|
+
* `relPath + title`, so two identically-titled scenarios share one key and
|
|
134
|
+
* citing one marks its twin covered. Pair with eess-gherkin's `haveUniqueTitles`
|
|
135
|
+
* to close that gap.
|
|
136
|
+
*
|
|
137
|
+
* Coverage means "a test cites this scenario", not "a test exercises its steps"
|
|
138
|
+
* (Tier 2, still open — plan 0079).
|
|
139
|
+
*/
|
|
140
|
+
export function scenariosCovered(project, set, options = {}) {
|
|
141
|
+
const extract = options.extract ?? defaultExtract;
|
|
142
|
+
const include = options.include ?? (() => true);
|
|
143
|
+
const covered = citedScenarioKeys(project, set, extract);
|
|
144
|
+
const violations = set
|
|
145
|
+
.scenarios()
|
|
146
|
+
.filter(include)
|
|
147
|
+
.filter((s) => !covered.has(`${s.relPath} ${s.title}`))
|
|
148
|
+
.map((s) => sv(s, 'no test cites this scenario', 'an unproven use-case flow is a spec with no gate behind it'));
|
|
149
|
+
return finishPreset(violations, options);
|
|
150
|
+
}
|
|
151
|
+
/** Count citing tests / scenarios for a caller's non-vacuity summary line. */
|
|
152
|
+
export function scenarioTestStats(project, set, options = {}) {
|
|
153
|
+
const extract = options.extract ?? defaultExtract;
|
|
154
|
+
return {
|
|
155
|
+
citations: itTitles(project).filter((s) => extract(s.title) !== undefined).length,
|
|
156
|
+
scenarios: set.scenarios().length,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
// Referenced by tests to keep the default convention itself under test.
|
|
160
|
+
export { defaultExtract };
|
|
161
|
+
//# sourceMappingURL=gherkin-ts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gherkin-ts.js","sourceRoot":"","sources":["../src/gherkin-ts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `it('…')` title grammar for this package — one definition, because both of
|
|
3
|
+
* its presets read it. Not the family's only copy: `eess-md`'s `adr.ts` carries a
|
|
4
|
+
* third, on the same ADR-citation contract, and cannot import this one (the
|
|
5
|
+
* dependency runs crossvalidate → md, and `eess/md-isolated` forbids the
|
|
6
|
+
* reverse). See bug 0111 for that, and 0114 for why the grammar arguably belongs
|
|
7
|
+
* behind eess-ts's engine boundary rather than here at all.
|
|
8
|
+
*
|
|
9
|
+
* `md-ts` (ADR citations) and `gherkin-ts` (scenario citations) both have to
|
|
10
|
+
* recover the title a test declares from source text, and both got it wrong the
|
|
11
|
+
* same way: `['"`]([^'"`]+)['"`]` ends the capture at **any** quote character,
|
|
12
|
+
* not at the one that opened the string. A character class cannot express "up to
|
|
13
|
+
* the *matching* delimiter" — the opening quote is matched but never captured,
|
|
14
|
+
* so the closing side has no way to require the same character.
|
|
15
|
+
*
|
|
16
|
+
* The cost is bug 0104, a false green: a single-quoted title containing a
|
|
17
|
+
* backtick — the ordinary way to name a symbol in prose, and this repo's own
|
|
18
|
+
* house style — truncated at that backtick, so
|
|
19
|
+
*
|
|
20
|
+
* it('catches `HACK` inside a body') ─┐
|
|
21
|
+
* it('catches `any` in a return position') ┴─→ both key on `catches `
|
|
22
|
+
*
|
|
23
|
+
* collapsed onto one key. Because the markdown side truncated identically, a
|
|
24
|
+
* citation to a test that had been renamed away still resolved — against a
|
|
25
|
+
* *different* test that happened to share the truncated prefix. The gate passed
|
|
26
|
+
* over exactly the drift it exists to catch.
|
|
27
|
+
*
|
|
28
|
+
* Capturing the delimiter as `q` and back-referencing it fixes both halves at
|
|
29
|
+
* once; the `\\.` alternative keeps an escaped delimiter inside the title.
|
|
30
|
+
*
|
|
31
|
+
* Titles are compared as **raw source text**, so `it('it\'s fine')` keys on
|
|
32
|
+
* `it\'s fine`, backslash included. That is a deliberate consequence of reading
|
|
33
|
+
* one grammar on both sides: an ADR cites what is written in the test file, not
|
|
34
|
+
* what the string evaluates to, and both sides read it identically.
|
|
35
|
+
*
|
|
36
|
+
* That has a cost worth knowing before you write a citation: a title's raw text
|
|
37
|
+
* is **prettier's to change**. `it("say 'hi' to \"them\"")` is reformatted to
|
|
38
|
+
* `it('say \'hi\' to "them"')` — same string, different source, different key —
|
|
39
|
+
* while the ADR's citation sits in a markdown code span prettier will not touch.
|
|
40
|
+
* A correct citation can go red after a routine `npm run format`. It fails loud
|
|
41
|
+
* rather than silent, which is the right direction, but prefer a title that
|
|
42
|
+
* needs no escaping at all.
|
|
43
|
+
*
|
|
44
|
+
* ## Two input classes, two grammars
|
|
45
|
+
*
|
|
46
|
+
* The anchored readers are handed an enriched call name built from ts-morph's
|
|
47
|
+
* `getText()` — guaranteed-valid TypeScript, where a `\` is always an escape and
|
|
48
|
+
* the delimiters always balance. `citedItTitles` reads **arbitrary prose** from
|
|
49
|
+
* a table cell, where neither holds. Sharing one permissive grammar across both
|
|
50
|
+
* cost a real regression, caught in review before this shipped: given a cell
|
|
51
|
+
* holding a malformed citation followed by a good one,
|
|
52
|
+
*
|
|
53
|
+
* `it('first` and `it('second')`
|
|
54
|
+
*
|
|
55
|
+
* the permissive body ran past the second citation's opening quote and closed
|
|
56
|
+
* there — yielding one bogus key and **dropping a real citation from the check
|
|
57
|
+
* entirely**. A citation nobody checks is the same false green this module was
|
|
58
|
+
* written to remove. So the prose variant is tempered: it cannot cross a line,
|
|
59
|
+
* and it cannot swallow the start of another citation.
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* The title in an enriched call name like `it('does a thing')`, or `undefined`
|
|
63
|
+
* if the argument is not a literal string. Accepts modifier forms (`it.skip`,
|
|
64
|
+
* `it.only`) — whether a *definition* in one of those forms counts is the
|
|
65
|
+
* caller's call, made by the callee filter above this.
|
|
66
|
+
*/
|
|
67
|
+
export declare function itTitleOf(enriched: string): string | undefined;
|
|
68
|
+
/** As {@link itTitleOf}, also accepting the `test` alias — gherkin↔ts's convention. */
|
|
69
|
+
export declare function itOrTestTitleOf(enriched: string): string | undefined;
|
|
70
|
+
/** Every `it('…')` title cited inside a fragment of prose, in order. */
|
|
71
|
+
export declare function citedItTitles(text: string): string[];
|
|
72
|
+
//# sourceMappingURL=it-title.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"it-title.d.ts","sourceRoot":"","sources":["../src/it-title.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAwCH;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE9D;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEpE;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOpD"}
|
package/dist/it-title.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `it('…')` title grammar for this package — one definition, because both of
|
|
3
|
+
* its presets read it. Not the family's only copy: `eess-md`'s `adr.ts` carries a
|
|
4
|
+
* third, on the same ADR-citation contract, and cannot import this one (the
|
|
5
|
+
* dependency runs crossvalidate → md, and `eess/md-isolated` forbids the
|
|
6
|
+
* reverse). See bug 0111 for that, and 0114 for why the grammar arguably belongs
|
|
7
|
+
* behind eess-ts's engine boundary rather than here at all.
|
|
8
|
+
*
|
|
9
|
+
* `md-ts` (ADR citations) and `gherkin-ts` (scenario citations) both have to
|
|
10
|
+
* recover the title a test declares from source text, and both got it wrong the
|
|
11
|
+
* same way: `['"`]([^'"`]+)['"`]` ends the capture at **any** quote character,
|
|
12
|
+
* not at the one that opened the string. A character class cannot express "up to
|
|
13
|
+
* the *matching* delimiter" — the opening quote is matched but never captured,
|
|
14
|
+
* so the closing side has no way to require the same character.
|
|
15
|
+
*
|
|
16
|
+
* The cost is bug 0104, a false green: a single-quoted title containing a
|
|
17
|
+
* backtick — the ordinary way to name a symbol in prose, and this repo's own
|
|
18
|
+
* house style — truncated at that backtick, so
|
|
19
|
+
*
|
|
20
|
+
* it('catches `HACK` inside a body') ─┐
|
|
21
|
+
* it('catches `any` in a return position') ┴─→ both key on `catches `
|
|
22
|
+
*
|
|
23
|
+
* collapsed onto one key. Because the markdown side truncated identically, a
|
|
24
|
+
* citation to a test that had been renamed away still resolved — against a
|
|
25
|
+
* *different* test that happened to share the truncated prefix. The gate passed
|
|
26
|
+
* over exactly the drift it exists to catch.
|
|
27
|
+
*
|
|
28
|
+
* Capturing the delimiter as `q` and back-referencing it fixes both halves at
|
|
29
|
+
* once; the `\\.` alternative keeps an escaped delimiter inside the title.
|
|
30
|
+
*
|
|
31
|
+
* Titles are compared as **raw source text**, so `it('it\'s fine')` keys on
|
|
32
|
+
* `it\'s fine`, backslash included. That is a deliberate consequence of reading
|
|
33
|
+
* one grammar on both sides: an ADR cites what is written in the test file, not
|
|
34
|
+
* what the string evaluates to, and both sides read it identically.
|
|
35
|
+
*
|
|
36
|
+
* That has a cost worth knowing before you write a citation: a title's raw text
|
|
37
|
+
* is **prettier's to change**. `it("say 'hi' to \"them\"")` is reformatted to
|
|
38
|
+
* `it('say \'hi\' to "them"')` — same string, different source, different key —
|
|
39
|
+
* while the ADR's citation sits in a markdown code span prettier will not touch.
|
|
40
|
+
* A correct citation can go red after a routine `npm run format`. It fails loud
|
|
41
|
+
* rather than silent, which is the right direction, but prefer a title that
|
|
42
|
+
* needs no escaping at all.
|
|
43
|
+
*
|
|
44
|
+
* ## Two input classes, two grammars
|
|
45
|
+
*
|
|
46
|
+
* The anchored readers are handed an enriched call name built from ts-morph's
|
|
47
|
+
* `getText()` — guaranteed-valid TypeScript, where a `\` is always an escape and
|
|
48
|
+
* the delimiters always balance. `citedItTitles` reads **arbitrary prose** from
|
|
49
|
+
* a table cell, where neither holds. Sharing one permissive grammar across both
|
|
50
|
+
* cost a real regression, caught in review before this shipped: given a cell
|
|
51
|
+
* holding a malformed citation followed by a good one,
|
|
52
|
+
*
|
|
53
|
+
* `it('first` and `it('second')`
|
|
54
|
+
*
|
|
55
|
+
* the permissive body ran past the second citation's opening quote and closed
|
|
56
|
+
* there — yielding one bogus key and **dropping a real citation from the check
|
|
57
|
+
* entirely**. A citation nobody checks is the same false green this module was
|
|
58
|
+
* written to remove. So the prose variant is tempered: it cannot cross a line,
|
|
59
|
+
* and it cannot swallow the start of another citation.
|
|
60
|
+
*/
|
|
61
|
+
/** The opening of a citation: `it(`, `it.skip(`, … up to and including its quote. */
|
|
62
|
+
const IT_OPEN = `it(?:\\.\\w+)?\\(\\s*['"\`]`;
|
|
63
|
+
/**
|
|
64
|
+
* A quoted string whose closing delimiter must match its opening one — group `q`
|
|
65
|
+
* is the delimiter, group `title` the payload. `body` is what may sit between
|
|
66
|
+
* them; the two alternatives have disjoint first characters (`\\.` starts with a
|
|
67
|
+
* backslash, the other excludes it), so the alternation is deterministic and the
|
|
68
|
+
* `+` cannot fork — no backtracking blowup on a long unterminated title.
|
|
69
|
+
*/
|
|
70
|
+
const quoted = (body) => `(?<q>['"\`])(?<title>(?:\\\\.|${body})+)\\k<q>`;
|
|
71
|
+
/** Valid TypeScript source: anything but the delimiter that opened the string. */
|
|
72
|
+
const SOURCE_BODY = `(?!\\k<q>)[^\\\\]`;
|
|
73
|
+
/**
|
|
74
|
+
* Arbitrary prose: additionally refuses to cross a newline (a Mechanism cell is
|
|
75
|
+
* one line) or to consume the start of another citation, so one malformed
|
|
76
|
+
* citation cannot eat its neighbour. Tempering on the full opening — callee,
|
|
77
|
+
* paren *and* quote — keeps a title that merely mentions `it(x)` intact.
|
|
78
|
+
*/
|
|
79
|
+
const PROSE_BODY = `(?!\\k<q>)(?!${IT_OPEN})[^\\\\\\n]`;
|
|
80
|
+
/**
|
|
81
|
+
* `<callee>(` with an optional modifier — `it(`, `it.skip(`, `test.concurrent(`.
|
|
82
|
+
* The lookbehind is what keeps `submit('save')`, `emit('drift')` and `audit('x')`
|
|
83
|
+
* from reading as citations; it is redundant under `^` and load-bearing in prose.
|
|
84
|
+
*/
|
|
85
|
+
const CALL = (callee) => `(?<![\\w$.])(?:${callee})(?:\\.\\w+)?\\(\\s*`;
|
|
86
|
+
// Anchored: the input is an enriched call name produced by eess-ts's
|
|
87
|
+
// `getName({ withArgument: 0 })`, e.g. `it('does a thing')`.
|
|
88
|
+
const IT_TITLE = new RegExp(`^${CALL('it')}${quoted(SOURCE_BODY)}`);
|
|
89
|
+
const IT_OR_TEST_TITLE = new RegExp(`^${CALL('it|test')}${quoted(SOURCE_BODY)}`);
|
|
90
|
+
// Unanchored + global: citations are embedded in prose (an ADR's Mechanism cell).
|
|
91
|
+
const IT_CITATION = new RegExp(`${CALL('it')}${quoted(PROSE_BODY)}`, 'g');
|
|
92
|
+
/**
|
|
93
|
+
* The title in an enriched call name like `it('does a thing')`, or `undefined`
|
|
94
|
+
* if the argument is not a literal string. Accepts modifier forms (`it.skip`,
|
|
95
|
+
* `it.only`) — whether a *definition* in one of those forms counts is the
|
|
96
|
+
* caller's call, made by the callee filter above this.
|
|
97
|
+
*/
|
|
98
|
+
export function itTitleOf(enriched) {
|
|
99
|
+
return IT_TITLE.exec(enriched)?.groups?.title;
|
|
100
|
+
}
|
|
101
|
+
/** As {@link itTitleOf}, also accepting the `test` alias — gherkin↔ts's convention. */
|
|
102
|
+
export function itOrTestTitleOf(enriched) {
|
|
103
|
+
return IT_OR_TEST_TITLE.exec(enriched)?.groups?.title;
|
|
104
|
+
}
|
|
105
|
+
/** Every `it('…')` title cited inside a fragment of prose, in order. */
|
|
106
|
+
export function citedItTitles(text) {
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const m of text.matchAll(IT_CITATION)) {
|
|
109
|
+
const title = m.groups?.title;
|
|
110
|
+
if (title !== undefined)
|
|
111
|
+
out.push(title);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=it-title.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"it-title.js","sourceRoot":"","sources":["../src/it-title.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,qFAAqF;AACrF,MAAM,OAAO,GAAG,6BAA6B,CAAA;AAE7C;;;;;;GAMG;AACH,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,iCAAiC,IAAI,WAAW,CAAA;AAEzF,kFAAkF;AAClF,MAAM,WAAW,GAAG,mBAAmB,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,GAAG,gBAAgB,OAAO,aAAa,CAAA;AAEvD;;;;GAIG;AACH,MAAM,IAAI,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,kBAAkB,MAAM,sBAAsB,CAAA;AAEvF,qEAAqE;AACrE,6DAA6D;AAC7D,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AACnE,MAAM,gBAAgB,GAAG,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAA;AAEhF,kFAAkF;AAClF,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;AAEzE;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAA;AAC/C,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,OAAO,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,KAAK,CAAA;AACvD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,CAAA;QAC7B,IAAI,KAAK,KAAK,SAAS;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/dist/md-ts.d.ts
CHANGED
|
@@ -8,6 +8,16 @@ export interface AdrCitationsResolveOptions {
|
|
|
8
8
|
/** Header pattern locating the mechanism column. Default `/mechanism/i` — non-English corpora pass their own (e.g. `/mekanisme/i`). */
|
|
9
9
|
readonly mechanismColumn?: RegExp;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Count what this preset would actually scan — the caller's non-vacuity guard.
|
|
13
|
+
* `adrCitationsResolve` reports OK when it resolves zero citations, so a drifted
|
|
14
|
+
* `dir` or `roots` reads exactly like a clean pass; a gate that prints this
|
|
15
|
+
* number can tell the two apart. Mirrors `scenarioTestStats` in gherkin↔ts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function adrCitationStats(corpus: Corpus, options?: AdrCitationsResolveOptions): {
|
|
18
|
+
citations: number;
|
|
19
|
+
adrs: number;
|
|
20
|
+
};
|
|
11
21
|
/**
|
|
12
22
|
* Cross-validate that every `it('…')` cited in an ADR's enforcement table
|
|
13
23
|
* actually exists as a test in the project. The AST-grounded upgrade of the
|
package/dist/md-ts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"md-ts.d.ts","sourceRoot":"","sources":["../src/md-ts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"md-ts.d.ts","sourceRoot":"","sources":["../src/md-ts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAG7D,MAAM,WAAW,0BAA0B;IACzC,uDAAuD;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAClC,uIAAuI;IACvI,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AA6CD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,0BAA+B,GACvC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAYrC;AAqCD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,0BAA+B,GACvC,IAAI,CA8CN"}
|
package/dist/md-ts.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import picomatch from 'picomatch';
|
|
2
2
|
import { correspondence } from '@nielspeter/eess';
|
|
3
3
|
import { calls } from '@nielspeter/eess-ts';
|
|
4
|
-
|
|
5
|
-
// Parse a title out of an enriched call name like `it('does a thing')`.
|
|
6
|
-
const IT_NAME_RE = /^it(?:\.\w+)?\(\s*['"`]([^'"`]+)['"`]/;
|
|
4
|
+
import { citedItTitles, itTitleOf } from './it-title.js';
|
|
7
5
|
function matchName(value, name) {
|
|
8
6
|
return typeof name === 'string' ? value === name : name.test(value);
|
|
9
7
|
}
|
|
@@ -23,15 +21,32 @@ function extractCitations(opts) {
|
|
|
23
21
|
continue;
|
|
24
22
|
for (const row of table.rows) {
|
|
25
23
|
const mech = row[mechIdx] ?? '';
|
|
26
|
-
for (const
|
|
27
|
-
|
|
28
|
-
if (title !== undefined)
|
|
29
|
-
out.push({ title, adr: doc.relPath, line: table.line });
|
|
24
|
+
for (const title of citedItTitles(mech)) {
|
|
25
|
+
out.push({ title, adr: doc.relPath, line: table.line });
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
return out;
|
|
34
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Count what this preset would actually scan — the caller's non-vacuity guard.
|
|
33
|
+
* `adrCitationsResolve` reports OK when it resolves zero citations, so a drifted
|
|
34
|
+
* `dir` or `roots` reads exactly like a clean pass; a gate that prints this
|
|
35
|
+
* number can tell the two apart. Mirrors `scenarioTestStats` in gherkin↔ts.
|
|
36
|
+
*/
|
|
37
|
+
export function adrCitationStats(corpus, options = {}) {
|
|
38
|
+
const dir = options.dir ?? 'docs/adr/**';
|
|
39
|
+
const inDir = picomatch(dir);
|
|
40
|
+
return {
|
|
41
|
+
citations: extractCitations({
|
|
42
|
+
corpus,
|
|
43
|
+
dir,
|
|
44
|
+
section: options.section ?? /^enforcement$/i,
|
|
45
|
+
mechanismColumn: options.mechanismColumn ?? /mechanism/i,
|
|
46
|
+
}).length,
|
|
47
|
+
adrs: corpus.documents().filter((d) => inDir(d.relPath)).length,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
35
50
|
/** Collect actual `it('…')` definitions from the project via eess-ts's public call API. */
|
|
36
51
|
function extractTestDefs(project) {
|
|
37
52
|
const allCalls = calls(project).select({
|
|
@@ -40,12 +55,29 @@ function extractTestDefs(project) {
|
|
|
40
55
|
}).elements;
|
|
41
56
|
const out = [];
|
|
42
57
|
for (const call of allCalls) {
|
|
43
|
-
|
|
58
|
+
// The ROOT callee, not the full name. eess-ts names a modifier call by its
|
|
59
|
+
// whole member expression, so `it.skip(…)` is `'it.skip'` and comparing the
|
|
60
|
+
// full name dropped every skipped, focused or concurrent test before its
|
|
61
|
+
// title was read — while the citation side had always accepted those forms
|
|
62
|
+
// (bug 0105). The gate binds a citation; it does not run the test, so a
|
|
63
|
+
// skipped test's citation is still checked. Same reasoning, same shape as
|
|
64
|
+
// `gherkin-ts.ts` — which is where it was already written down.
|
|
65
|
+
//
|
|
66
|
+
// **This guard is not what decides.** `itTitleOf` is anchored on the literal
|
|
67
|
+
// callee (`^` + `(?:it)(?:\.\w+)?\(`), so the grammar alone already rejects
|
|
68
|
+
// `describe(…)`, `test(…)`, `suite.it(…)`, `it.a.b(…)` and the outer
|
|
69
|
+
// `it.each([…])(…)`. Review measured it: delete these two lines and the whole
|
|
70
|
+
// suite stays green. What the guard buys is a cheap pre-filter — it skips
|
|
71
|
+
// `getArguments()` + `getText()` for every call expression in the project —
|
|
72
|
+
// and defence in depth: widening what counts as a test takes BOTH this line
|
|
73
|
+
// and the grammar, so neither can smuggle one in alone. Keep it for those
|
|
74
|
+
// reasons, not for a rejection it does not perform.
|
|
75
|
+
const root = call.getObjectName() ?? call.getMethodName();
|
|
76
|
+
if (root !== 'it')
|
|
44
77
|
continue;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
out.push({ title: m[1], file: call.getSourceFile().getFilePath() });
|
|
78
|
+
const title = itTitleOf(call.getName({ withArgument: 0 }) ?? '');
|
|
79
|
+
if (title !== undefined) {
|
|
80
|
+
out.push({ title, file: call.getSourceFile().getFilePath() });
|
|
49
81
|
}
|
|
50
82
|
}
|
|
51
83
|
return out;
|
|
@@ -78,6 +110,20 @@ export function adrCitationsResolve(corpus, project, options = {}) {
|
|
|
78
110
|
left,
|
|
79
111
|
right,
|
|
80
112
|
keyBy: (e) => e.title,
|
|
113
|
+
// `suggest.left`, not `.rule({ suggestion })`, and now for the right reason.
|
|
114
|
+
// The original comment here said rule-level suggestions never render on a
|
|
115
|
+
// correspondence (true until bug 0122 fixed it) and that appending to the
|
|
116
|
+
// message "does" render — which was never true: the terminal formatter
|
|
117
|
+
// dropped `message` entirely, so this remedy was invisible in the one format
|
|
118
|
+
// an agent reads. Both halves are fixed. `suggest` stays because the remedy
|
|
119
|
+
// is per-branch: a rule-level `suggestion` is stamped onto every branch of
|
|
120
|
+
// the correspondence, and "match the citation character for character" is
|
|
121
|
+
// advice for a *dangling* citation, not for an ambiguous or an uncited one.
|
|
122
|
+
suggest: {
|
|
123
|
+
left: () => 'Fix: the cited title is compared as raw source text — if the test was renamed, ' +
|
|
124
|
+
'match the citation to it character for character (escapes included); ' +
|
|
125
|
+
'if it was deleted, restore it or retire the clause it enforced.',
|
|
126
|
+
},
|
|
81
127
|
})
|
|
82
128
|
.should()
|
|
83
129
|
.beComplete({ direction: 'left-to-right' })
|
package/dist/md-ts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"md-ts.js","sourceRoot":"","sources":["../src/md-ts.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAA;AAEjE,OAAO,EAAE,KAAK,EAAoB,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"md-ts.js","sourceRoot":"","sources":["../src/md-ts.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nielspeter/eess-crossvalidate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Cross-validation presets for the eess family — bind two dialects' artifacts and fail when they drift.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
"types": "./dist/md-gherkin.d.ts",
|
|
25
25
|
"import": "./dist/md-gherkin.js"
|
|
26
26
|
},
|
|
27
|
+
"./gherkin-ts": {
|
|
28
|
+
"types": "./dist/gherkin-ts.d.ts",
|
|
29
|
+
"import": "./dist/gherkin-ts.js"
|
|
30
|
+
},
|
|
27
31
|
"./md-mermaid-er": {
|
|
28
32
|
"types": "./dist/md-mermaid-er.d.ts",
|
|
29
33
|
"import": "./dist/md-mermaid-er.js"
|
|
@@ -58,7 +62,7 @@
|
|
|
58
62
|
"markdown"
|
|
59
63
|
],
|
|
60
64
|
"dependencies": {
|
|
61
|
-
"@nielspeter/eess": "^0.2.
|
|
65
|
+
"@nielspeter/eess": "^0.2.2",
|
|
62
66
|
"picomatch": "^4.0.4"
|
|
63
67
|
},
|
|
64
68
|
"peerDependencies": {
|