@nielspeter/eess-crossvalidate 0.1.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/LICENSE +21 -0
- package/README.md +44 -0
- package/dist/files.d.ts +44 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +68 -0
- package/dist/files.js.map +1 -0
- package/dist/md-gherkin.d.ts +41 -0
- package/dist/md-gherkin.d.ts.map +1 -0
- package/dist/md-gherkin.js +0 -0
- package/dist/md-gherkin.js.map +1 -0
- package/dist/md-mermaid-er.d.ts +42 -0
- package/dist/md-mermaid-er.d.ts.map +1 -0
- package/dist/md-mermaid-er.js +122 -0
- package/dist/md-mermaid-er.js.map +1 -0
- package/dist/md-mermaid.d.ts +21 -0
- package/dist/md-mermaid.d.ts.map +1 -0
- package/dist/md-mermaid.js +44 -0
- package/dist/md-mermaid.js.map +1 -0
- package/dist/md-ts.d.ts +20 -0
- package/dist/md-ts.d.ts.map +1 -0
- package/dist/md-ts.js +90 -0
- package/dist/md-ts.js.map +1 -0
- package/dist/mermaid-ts.d.ts +25 -0
- package/dist/mermaid-ts.d.ts.map +1 -0
- package/dist/mermaid-ts.js +40 -0
- package/dist/mermaid-ts.js.map +1 -0
- package/package.json +84 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Niels Peter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @nielspeter/eess-crossvalidate
|
|
2
|
+
|
|
3
|
+
Cross-validation presets for the [eess](../../README.md) family — bind two dialects' artifacts and fail the build when they drift.
|
|
4
|
+
|
|
5
|
+
The generic engine lives in the kernel: [`correspondence()`](../core) binds two element `Selection`s (from any dialects) and checks completeness both ways plus relation preservation. This package ships the **dialect-coupled presets** — the pairs that must import two concrete dialects.
|
|
6
|
+
|
|
7
|
+
## Mermaid ↔ TypeScript
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { diagramMatchesCode } from '@nielspeter/eess-crossvalidate/mermaid-ts'
|
|
11
|
+
import { project } from '@nielspeter/eess-ts'
|
|
12
|
+
import { diagram } from '@nielspeter/eess-mermaid'
|
|
13
|
+
|
|
14
|
+
// every class in the diagram exists in code, and vice versa
|
|
15
|
+
diagramMatchesCode(diagram('docs/architecture.mmd'), project('tsconfig.json')).check?.()
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If the code gains a `ModuloOperation` class the diagram doesn't have (or vice
|
|
19
|
+
versa), the build fails with a two-sided, actionable message. This supersedes the
|
|
20
|
+
deprecated `fromDiagram()` bridge — class-level correspondence, both directions.
|
|
21
|
+
|
|
22
|
+
## Markdown ↔ TypeScript
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { adrCitationsResolve } from '@nielspeter/eess-crossvalidate/md-ts'
|
|
26
|
+
import { corpus } from '@nielspeter/eess-md'
|
|
27
|
+
import { project } from '@nielspeter/eess-ts'
|
|
28
|
+
|
|
29
|
+
// every it('…') cited in an ADR enforcement table exists as a real test
|
|
30
|
+
adrCitationsResolve(corpus({ roots: ['docs/**'] }), project('tsconfig.json'))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
AST-grounded: cited titles are resolved against actual `it()` call expressions
|
|
34
|
+
(via eess-ts's public API — no ts-morph here, per ADR-007), so it also catches
|
|
35
|
+
no-substitution template titles the text-level check misses.
|
|
36
|
+
|
|
37
|
+
## Peers
|
|
38
|
+
|
|
39
|
+
The dialects are optional peer dependencies — install the ones your presets use
|
|
40
|
+
(`@nielspeter/eess-ts`, `@nielspeter/eess-mermaid`, `@nielspeter/eess-md`).
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
MIT
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Selection, ElementInfo } from '@nielspeter/eess';
|
|
2
|
+
/**
|
|
3
|
+
* A file matched by [[files]]. Its content is **not** read — this is a
|
|
4
|
+
* path-selection brick. Callers that need the file's content (e.g. parse a
|
|
5
|
+
* `package.json`) read `absPath` inside their own `identify`.
|
|
6
|
+
*/
|
|
7
|
+
export interface FileEntry {
|
|
8
|
+
/** Repo-relative path, POSIX separators, e.g. `adr/001-toolchain.md`. */
|
|
9
|
+
readonly path: string;
|
|
10
|
+
/** Absolute path — for callers that read content in `identify`. */
|
|
11
|
+
readonly absPath: string;
|
|
12
|
+
}
|
|
13
|
+
/** Options for [[files]]. */
|
|
14
|
+
export interface FilesOptions {
|
|
15
|
+
/** Glob(s) selecting files, matched against repo-relative POSIX paths. */
|
|
16
|
+
readonly glob: string | readonly string[];
|
|
17
|
+
/** Root the globs resolve against. Defaults to `process.cwd()`. */
|
|
18
|
+
readonly cwd?: string;
|
|
19
|
+
/** Globs excluded from the result (in addition to the built-in dir skips). */
|
|
20
|
+
readonly ignore?: readonly string[];
|
|
21
|
+
/** Display label for this side of a `correspondence()`. */
|
|
22
|
+
readonly label: string;
|
|
23
|
+
/** Map a file to its correspondence identity (name/file/line). */
|
|
24
|
+
readonly identify: (file: FileEntry) => ElementInfo;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Turn a set of files into a `Selection` — the recurring "the code side is a
|
|
28
|
+
* set of files" case (an ADR index binds to `adr/*.md`, a manifest binds to
|
|
29
|
+
* `packages/*`). Feed the result to `correspondence()` as one side.
|
|
30
|
+
*
|
|
31
|
+
* Path selection only: the file's content is not read. When a file's identity
|
|
32
|
+
* comes from its content (a `package.json`'s `name`), read `absPath` inside
|
|
33
|
+
* `identify`. Results are sorted by path for deterministic output.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* const adrFiles = files({
|
|
37
|
+
* glob: 'adr/*.md',
|
|
38
|
+
* label: 'ADR file',
|
|
39
|
+
* identify: (f) => ({ name: numberOf(f.path), file: f.path, line: 1 }),
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function files(opts: FilesOptions): Selection<FileEntry>;
|
|
44
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9D;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,6BAA6B;AAC7B,MAAM,WAAW,YAAY;IAC3B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAA;IACzC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,WAAW,CAAA;CACpD;AAoCD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,CAgB9D"}
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { join, relative, sep } from 'node:path';
|
|
3
|
+
import picomatch from 'picomatch';
|
|
4
|
+
// Directories never walked — deps, build output, VCS. Mirrors the corpus walk.
|
|
5
|
+
const BUILTIN_IGNORE_DIRS = new Set([
|
|
6
|
+
'node_modules',
|
|
7
|
+
'.git',
|
|
8
|
+
'dist',
|
|
9
|
+
'coverage',
|
|
10
|
+
'.output',
|
|
11
|
+
'.nuxt',
|
|
12
|
+
'.vercel',
|
|
13
|
+
]);
|
|
14
|
+
function toPosix(p) {
|
|
15
|
+
return sep === '/' ? p : p.split(sep).join('/');
|
|
16
|
+
}
|
|
17
|
+
function walk(dir, root, acc) {
|
|
18
|
+
let entries;
|
|
19
|
+
try {
|
|
20
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
void err; // unreadable dir (permissions, dangling symlink) — deliberately skipped
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
for (const e of entries) {
|
|
27
|
+
const abs = join(dir, e.name);
|
|
28
|
+
if (e.isDirectory()) {
|
|
29
|
+
if (BUILTIN_IGNORE_DIRS.has(e.name))
|
|
30
|
+
continue;
|
|
31
|
+
walk(abs, root, acc);
|
|
32
|
+
}
|
|
33
|
+
else if (e.isFile()) {
|
|
34
|
+
acc.push(toPosix(relative(root, abs)));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Turn a set of files into a `Selection` — the recurring "the code side is a
|
|
40
|
+
* set of files" case (an ADR index binds to `adr/*.md`, a manifest binds to
|
|
41
|
+
* `packages/*`). Feed the result to `correspondence()` as one side.
|
|
42
|
+
*
|
|
43
|
+
* Path selection only: the file's content is not read. When a file's identity
|
|
44
|
+
* comes from its content (a `package.json`'s `name`), read `absPath` inside
|
|
45
|
+
* `identify`. Results are sorted by path for deterministic output.
|
|
46
|
+
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const adrFiles = files({
|
|
49
|
+
* glob: 'adr/*.md',
|
|
50
|
+
* label: 'ADR file',
|
|
51
|
+
* identify: (f) => ({ name: numberOf(f.path), file: f.path, line: 1 }),
|
|
52
|
+
* })
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export function files(opts) {
|
|
56
|
+
const root = opts.cwd ?? process.cwd();
|
|
57
|
+
const all = [];
|
|
58
|
+
walk(root, root, all);
|
|
59
|
+
const globs = typeof opts.glob === 'string' ? [opts.glob] : [...opts.glob];
|
|
60
|
+
const matchesGlob = picomatch(globs);
|
|
61
|
+
const matchesIgnore = opts.ignore && opts.ignore.length > 0 ? picomatch([...opts.ignore]) : () => false;
|
|
62
|
+
const elements = all
|
|
63
|
+
.filter((rel) => matchesGlob(rel) && !matchesIgnore(rel))
|
|
64
|
+
.sort()
|
|
65
|
+
.map((rel) => ({ path: rel, absPath: join(root, rel) }));
|
|
66
|
+
return { elements, label: opts.label, identify: opts.identify };
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type ArchViolation, type PresetReportOptions } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '@nielspeter/eess-md';
|
|
3
|
+
import type { FeatureSet } from '@nielspeter/eess-gherkin';
|
|
4
|
+
export interface ScenarioCitationsResolveOptions extends PresetReportOptions {
|
|
5
|
+
/** Glob selecting the markdown documents to scan. Default `**` (whole corpus). */
|
|
6
|
+
readonly dir?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Custom citation extractor for transition periods — receives one
|
|
9
|
+
* fence-stripped line and returns the citations on it. Default: the eess
|
|
10
|
+
* citation convention, a backticked feature path plus an optional quoted
|
|
11
|
+
* scenario title on the same line — `` `path/to/x.feature` `` · `'Title'`.
|
|
12
|
+
*/
|
|
13
|
+
readonly extract?: (line: string) => readonly ExtractedCitation[];
|
|
14
|
+
}
|
|
15
|
+
export interface ExtractedCitation {
|
|
16
|
+
/** The cited feature path, as written (may be a unique suffix). */
|
|
17
|
+
readonly path: string;
|
|
18
|
+
/** The cited scenario title; absent for a file-level citation. */
|
|
19
|
+
readonly title?: string;
|
|
20
|
+
}
|
|
21
|
+
declare function defaultExtract(line: string): ExtractedCitation[];
|
|
22
|
+
/**
|
|
23
|
+
* Cross-validate that every scenario citation in the markdown corpus resolves
|
|
24
|
+
* against the loaded feature set — the md↔gherkin pairing (plan 0069 Phase 2),
|
|
25
|
+
* the exact analogue of `adrCitationsResolve`'s `it('…')` title resolution.
|
|
26
|
+
*
|
|
27
|
+
* A citation is a backticked feature path with an optional quoted scenario
|
|
28
|
+
* title on the same line. Three failure modes are gated: the cited feature
|
|
29
|
+
* file doesn't exist in the set, the path is ambiguous (matches several
|
|
30
|
+
* files), or the cited scenario title doesn't exist in that file. Citations
|
|
31
|
+
* inside fenced code blocks never count.
|
|
32
|
+
*/
|
|
33
|
+
export declare function scenarioCitationsResolve(corpus: Corpus, set: FeatureSet, options?: ScenarioCitationsResolveOptions): ArchViolation[];
|
|
34
|
+
/** Count citations/scenarios for a caller's non-vacuity summary line. */
|
|
35
|
+
export declare function scenarioCitationStats(corpus: Corpus, set: FeatureSet, options?: ScenarioCitationsResolveOptions): {
|
|
36
|
+
citations: number;
|
|
37
|
+
features: number;
|
|
38
|
+
scenarios: number;
|
|
39
|
+
};
|
|
40
|
+
export { defaultExtract };
|
|
41
|
+
//# sourceMappingURL=md-gherkin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-gherkin.d.ts","sourceRoot":"","sources":["../src/md-gherkin.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,MAAM,WAAW,+BAAgC,SAAQ,mBAAmB;IAC1E,kFAAkF;IAClF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,iBAAiB,EAAE,CAAA;CAClE;AAED,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAaD,iBAAS,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAUzD;AA6CD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,+BAAoC,GAC5C,aAAa,EAAE,CA2CjB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,UAAU,EACf,OAAO,GAAE,+BAAoC,GAC5C;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAQ5D;AAGD,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-gherkin.js","sourceRoot":"","sources":["../src/md-gherkin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ArchViolation, type PresetReportOptions } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus, MdDocument } from '@nielspeter/eess-md';
|
|
3
|
+
export interface TableErAgreeOptions extends PresetReportOptions {
|
|
4
|
+
/** Glob selecting the documents to check. Default `**`. */
|
|
5
|
+
readonly docs?: string;
|
|
6
|
+
/**
|
|
7
|
+
* The consumer's table shape (never baked in): which section holds the
|
|
8
|
+
* property table, and which headers carry the property name and —
|
|
9
|
+
* optionally — its type.
|
|
10
|
+
*/
|
|
11
|
+
readonly table: {
|
|
12
|
+
readonly section: RegExp;
|
|
13
|
+
readonly name: RegExp;
|
|
14
|
+
readonly type?: RegExp;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Which diagram entity a document describes — e.g. derive `JobSchedule`
|
|
18
|
+
* from an `# Entity: Job Schedule` heading. Return `undefined` to skip
|
|
19
|
+
* the document.
|
|
20
|
+
*/
|
|
21
|
+
readonly entity: (doc: MdDocument) => string | undefined;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Cross-validate that a document's property table and its own `erDiagram`
|
|
25
|
+
* describe the same entity (plan 0069 Phase 3): the entity the document is
|
|
26
|
+
* about must appear in the diagram, and every diagram attribute of that
|
|
27
|
+
* entity must be a row of the table (attributes ⊆ properties). Where the
|
|
28
|
+
* consumer maps a type column, attribute types must match it too
|
|
29
|
+
* (case-insensitive).
|
|
30
|
+
*
|
|
31
|
+
* Documents with no erDiagram block, or for which `entity()` returns
|
|
32
|
+
* `undefined`, are skipped — gate diagram *presence* separately with
|
|
33
|
+
* `docs()` conformance if the corpus requires one.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tableErAgree(corpus: Corpus, options: TableErAgreeOptions): ArchViolation[];
|
|
36
|
+
/** Count compared docs/entities/attributes for a caller's non-vacuity summary. */
|
|
37
|
+
export declare function tableErStats(corpus: Corpus, options: TableErAgreeOptions): {
|
|
38
|
+
docs: number;
|
|
39
|
+
entities: number;
|
|
40
|
+
attributes: number;
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=md-mermaid-er.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-mermaid-er.d.ts","sourceRoot":"","sources":["../src/md-mermaid-er.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC7F,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAG7D,MAAM,WAAW,mBAAoB,SAAQ,mBAAmB;IAC9D,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE;QACd,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,MAAM,GAAG,SAAS,CAAA;CACzD;AAoCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,aAAa,EAAE,CAoG1F;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,mBAAmB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAiBxD"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import picomatch from 'picomatch';
|
|
2
|
+
import { finishPreset } from '@nielspeter/eess';
|
|
3
|
+
import { parseErDiagram, collectEntities } from '@nielspeter/eess-mermaid';
|
|
4
|
+
const RULE = 'entity property tables and their erDiagram should agree';
|
|
5
|
+
const ER_HEADER = /^\s*erDiagram\b/;
|
|
6
|
+
const v = (doc, line, message, because) => ({
|
|
7
|
+
rule: RULE,
|
|
8
|
+
ruleId: 'crossval/table-er-agree',
|
|
9
|
+
element: doc.relPath,
|
|
10
|
+
file: doc.file,
|
|
11
|
+
line,
|
|
12
|
+
message,
|
|
13
|
+
because,
|
|
14
|
+
});
|
|
15
|
+
function erEntitiesOf(doc) {
|
|
16
|
+
return doc.codeBlocks
|
|
17
|
+
.filter((b) => (b.lang === 'mermaid' || b.lang === null) && ER_HEADER.test(b.value))
|
|
18
|
+
.map((b) => {
|
|
19
|
+
try {
|
|
20
|
+
return { entities: collectEntities(parseErDiagram(b.value)), line: b.line };
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const message = err instanceof Error ? (err.message.split('\n')[1] ?? err.message) : String(err);
|
|
24
|
+
return { entities: [], line: b.line, parseError: message };
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Cross-validate that a document's property table and its own `erDiagram`
|
|
30
|
+
* describe the same entity (plan 0069 Phase 3): the entity the document is
|
|
31
|
+
* about must appear in the diagram, and every diagram attribute of that
|
|
32
|
+
* entity must be a row of the table (attributes ⊆ properties). Where the
|
|
33
|
+
* consumer maps a type column, attribute types must match it too
|
|
34
|
+
* (case-insensitive).
|
|
35
|
+
*
|
|
36
|
+
* Documents with no erDiagram block, or for which `entity()` returns
|
|
37
|
+
* `undefined`, are skipped — gate diagram *presence* separately with
|
|
38
|
+
* `docs()` conformance if the corpus requires one.
|
|
39
|
+
*/
|
|
40
|
+
export function tableErAgree(corpus, options) {
|
|
41
|
+
const inDocs = picomatch(options.docs ?? '**');
|
|
42
|
+
const violations = [];
|
|
43
|
+
for (const doc of corpus.documents()) {
|
|
44
|
+
if (!inDocs(doc.relPath))
|
|
45
|
+
continue;
|
|
46
|
+
const entityName = options.entity(doc);
|
|
47
|
+
if (entityName === undefined)
|
|
48
|
+
continue;
|
|
49
|
+
const blocks = erEntitiesOf(doc);
|
|
50
|
+
if (blocks.length === 0)
|
|
51
|
+
continue;
|
|
52
|
+
// A block that is not standard Mermaid ER syntax is itself drift — flag
|
|
53
|
+
// it (never crash, never silently skip) and compare what did parse.
|
|
54
|
+
for (const b of blocks) {
|
|
55
|
+
if (b.parseError !== undefined) {
|
|
56
|
+
violations.push(v(doc, b.line, `erDiagram does not parse as standard Mermaid ER syntax: ${b.parseError}`, 'a diagram the standard grammar rejects cannot be validated — fix the syntax'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (blocks.every((b) => b.parseError !== undefined))
|
|
60
|
+
continue;
|
|
61
|
+
const entity = blocks.flatMap((b) => b.entities).find((e) => e.name === entityName);
|
|
62
|
+
const diagramLine = blocks[0]?.line ?? 1;
|
|
63
|
+
if (entity === undefined) {
|
|
64
|
+
violations.push(v(doc, diagramLine, `erDiagram does not declare entity '${entityName}'`, 'the diagram and the document must describe the same entity'));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const table = doc.tables.find((t) => t.sectionPath.some((h) => options.table.section.test(h)) &&
|
|
68
|
+
t.header.some((h) => options.table.name.test(h)));
|
|
69
|
+
if (table === undefined) {
|
|
70
|
+
violations.push(v(doc, diagramLine, `no property table matching section ${String(options.table.section)} to compare the erDiagram against`, 'attributes ⊆ properties is only checkable when the table exists'));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const nameIdx = table.header.findIndex((h) => options.table.name.test(h));
|
|
74
|
+
const typeIdx = options.table.type !== undefined
|
|
75
|
+
? table.header.findIndex((h) => {
|
|
76
|
+
const re = options.table.type;
|
|
77
|
+
return re !== undefined && re.test(h);
|
|
78
|
+
})
|
|
79
|
+
: -1;
|
|
80
|
+
const typeByName = new Map();
|
|
81
|
+
for (const row of table.rows) {
|
|
82
|
+
const name = row[nameIdx];
|
|
83
|
+
if (name === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
typeByName.set(name.trim(), typeIdx >= 0 ? (row[typeIdx] ?? '').trim() : '');
|
|
86
|
+
}
|
|
87
|
+
for (const attr of entity.attributes) {
|
|
88
|
+
const tableType = typeByName.get(attr.name);
|
|
89
|
+
if (tableType === undefined) {
|
|
90
|
+
violations.push(v(doc, diagramLine, `erDiagram attribute '${attr.name}' of '${entityName}' is not a row of the property table`, 'a diagram attribute the table does not carry is drift between two views of one entity'));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (typeIdx >= 0 && tableType.toLowerCase() !== attr.type.toLowerCase()) {
|
|
94
|
+
violations.push(v(doc, diagramLine, `type of '${attr.name}' disagrees: table says '${tableType}', erDiagram says '${attr.type}'`, 'the two representations must state the same type'));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return finishPreset(violations, options);
|
|
99
|
+
}
|
|
100
|
+
/** Count compared docs/entities/attributes for a caller's non-vacuity summary. */
|
|
101
|
+
export function tableErStats(corpus, options) {
|
|
102
|
+
const inDocs = picomatch(options.docs ?? '**');
|
|
103
|
+
let docs = 0;
|
|
104
|
+
let entities = 0;
|
|
105
|
+
let attributes = 0;
|
|
106
|
+
for (const doc of corpus.documents()) {
|
|
107
|
+
if (!inDocs(doc.relPath))
|
|
108
|
+
continue;
|
|
109
|
+
if (options.entity(doc) === undefined)
|
|
110
|
+
continue;
|
|
111
|
+
const blocks = erEntitiesOf(doc);
|
|
112
|
+
if (blocks.length === 0)
|
|
113
|
+
continue;
|
|
114
|
+
docs++;
|
|
115
|
+
for (const b of blocks) {
|
|
116
|
+
entities += b.entities.length;
|
|
117
|
+
attributes += b.entities.reduce((n, e) => n + e.attributes.length, 0);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return { docs, entities, attributes };
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=md-mermaid-er.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-mermaid-er.js","sourceRoot":"","sources":["../src/md-mermaid-er.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ArchViolation, type Direction, type PresetReportOptions } from '@nielspeter/eess';
|
|
2
|
+
import type { Corpus } from '@nielspeter/eess-md';
|
|
3
|
+
import { type ArchProject } from '@nielspeter/eess-ts';
|
|
4
|
+
export interface EmbeddedDiagramsMatchCodeOptions extends PresetReportOptions {
|
|
5
|
+
/** Glob (matched against a class's directory) restricting which TS classes participate. */
|
|
6
|
+
readonly scope?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Completeness direction. Default `left-to-right` — every class named in an
|
|
9
|
+
* embedded diagram must exist in code (a diagram fragment in an ADR need not
|
|
10
|
+
* list every code class).
|
|
11
|
+
*/
|
|
12
|
+
readonly completeness?: Direction;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Cross-validate Mermaid diagrams embedded as fenced ```mermaid blocks in the
|
|
16
|
+
* markdown corpus against TypeScript code — each embedded diagram is validated
|
|
17
|
+
* exactly like a standalone `.mmd` file, with violations pointing at the
|
|
18
|
+
* markdown file and the fence line. Composed from the kernel `correspondence()`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function embeddedDiagramsMatchCode(corpus: Corpus, project: ArchProject, options?: EmbeddedDiagramsMatchCodeOptions): ArchViolation[];
|
|
21
|
+
//# sourceMappingURL=md-mermaid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-mermaid.d.ts","sourceRoot":"","sources":["../src/md-mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,SAAS,EACd,KAAK,mBAAmB,EACzB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAEjD,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAE5E,MAAM,WAAW,gCAAiC,SAAQ,mBAAmB;IAC3E,2FAA2F;IAC3F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAA;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gCAAqC,GAC7C,aAAa,EAAE,CAoCjB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { correspondence, finishPreset, } from '@nielspeter/eess';
|
|
2
|
+
import { diagram, classes as mmdClasses } from '@nielspeter/eess-mermaid';
|
|
3
|
+
import { classes as tsClasses } from '@nielspeter/eess-ts';
|
|
4
|
+
/**
|
|
5
|
+
* Cross-validate Mermaid diagrams embedded as fenced ```mermaid blocks in the
|
|
6
|
+
* markdown corpus against TypeScript code — each embedded diagram is validated
|
|
7
|
+
* exactly like a standalone `.mmd` file, with violations pointing at the
|
|
8
|
+
* markdown file and the fence line. Composed from the kernel `correspondence()`.
|
|
9
|
+
*/
|
|
10
|
+
export function embeddedDiagramsMatchCode(corpus, project, options = {}) {
|
|
11
|
+
const scope = options.scope ?? '**/src/**';
|
|
12
|
+
const direction = options.completeness ?? 'left-to-right';
|
|
13
|
+
const right = tsClasses(project)
|
|
14
|
+
.that()
|
|
15
|
+
.resideInFolder(scope)
|
|
16
|
+
.select({
|
|
17
|
+
label: 'TS class',
|
|
18
|
+
identify: (c) => ({
|
|
19
|
+
name: c.getName() ?? '<anonymous>',
|
|
20
|
+
file: c.getSourceFile().getFilePath(),
|
|
21
|
+
line: c.getStartLineNumber(),
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
const violations = [];
|
|
25
|
+
for (const doc of corpus.documents()) {
|
|
26
|
+
for (const block of doc.codeBlocks) {
|
|
27
|
+
if (block.lang !== 'mermaid')
|
|
28
|
+
continue;
|
|
29
|
+
const d = diagram(block.value);
|
|
30
|
+
const left = mmdClasses(d).select({
|
|
31
|
+
label: `diagram class (in ${doc.relPath})`,
|
|
32
|
+
// point violations at the markdown file + fence line, not the parsed diagram
|
|
33
|
+
identify: (c) => ({ name: c.name, file: doc.file, line: block.line }),
|
|
34
|
+
});
|
|
35
|
+
violations.push(...correspondence({ left, right })
|
|
36
|
+
.should()
|
|
37
|
+
.beComplete({ direction })
|
|
38
|
+
.rule({ id: 'crossval/embedded-diagram' })
|
|
39
|
+
.violations());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return finishPreset(violations, options);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=md-mermaid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-mermaid.js","sourceRoot":"","sources":["../src/md-mermaid.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,YAAY,GAIb,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,OAAO,IAAI,SAAS,EAAoB,MAAM,qBAAqB,CAAA;AAa5E;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CACvC,MAAc,EACd,OAAoB,EACpB,UAA4C,EAAE;IAE9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAAA;IAC1C,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,IAAI,eAAe,CAAA;IAEzD,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC;SAC7B,IAAI,EAAE;SACN,cAAc,CAAC,KAAK,CAAC;SACrB,MAAM,CAAC;QACN,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,aAAa;YAClC,IAAI,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE;YACrC,IAAI,EAAE,CAAC,CAAC,kBAAkB,EAAE;SAC7B,CAAC;KACH,CAAC,CAAA;IAEJ,MAAM,UAAU,GAAoB,EAAE,CAAA;IACtC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;QACrC,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;gBAAE,SAAQ;YACtC,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;YAC9B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;gBAChC,KAAK,EAAE,qBAAqB,GAAG,CAAC,OAAO,GAAG;gBAC1C,6EAA6E;gBAC7E,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;aACtE,CAAC,CAAA;YACF,UAAU,CAAC,IAAI,CACb,GAAG,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;iBAC/B,MAAM,EAAE;iBACR,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;iBACzB,IAAI,CAAC,EAAE,EAAE,EAAE,2BAA2B,EAAE,CAAC;iBACzC,UAAU,EAAE,CAChB,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;AAC1C,CAAC"}
|
package/dist/md-ts.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Corpus } from '@nielspeter/eess-md';
|
|
2
|
+
import { type ArchProject } from '@nielspeter/eess-ts';
|
|
3
|
+
export interface AdrCitationsResolveOptions {
|
|
4
|
+
/** Glob selecting ADR files. Default `docs/adr/**`. */
|
|
5
|
+
readonly dir?: string;
|
|
6
|
+
/** Enforcement section heading. Default `/^enforcement$/i`. */
|
|
7
|
+
readonly section?: string | RegExp;
|
|
8
|
+
/** Header pattern locating the mechanism column. Default `/mechanism/i` — non-English corpora pass their own (e.g. `/mekanisme/i`). */
|
|
9
|
+
readonly mechanismColumn?: RegExp;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Cross-validate that every `it('…')` cited in an ADR's enforcement table
|
|
13
|
+
* actually exists as a test in the project. The AST-grounded upgrade of the
|
|
14
|
+
* text-level citation check in `eess-md`'s `adrEnforcement` — it resolves titles
|
|
15
|
+
* against real test call expressions (via eess-ts's public API; no ts-morph
|
|
16
|
+
* here, per ADR-007), so it also sees `it(\`no-substitution template\`)` titles
|
|
17
|
+
* the regex missed.
|
|
18
|
+
*/
|
|
19
|
+
export declare function adrCitationsResolve(corpus: Corpus, project: ArchProject, options?: AdrCitationsResolveOptions): void;
|
|
20
|
+
//# sourceMappingURL=md-ts.d.ts.map
|
|
@@ -0,0 +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;AAE7D,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;AAoED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,0BAA+B,GACvC,IAAI,CA+BN"}
|
package/dist/md-ts.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import picomatch from 'picomatch';
|
|
2
|
+
import { correspondence } from '@nielspeter/eess';
|
|
3
|
+
import { calls } from '@nielspeter/eess-ts';
|
|
4
|
+
const IT_CITE_RE = /it(?:\.\w+)?\(\s*['"`]([^'"`]+)['"`]/g;
|
|
5
|
+
// Parse a title out of an enriched call name like `it('does a thing')`.
|
|
6
|
+
const IT_NAME_RE = /^it(?:\.\w+)?\(\s*['"`]([^'"`]+)['"`]/;
|
|
7
|
+
function matchName(value, name) {
|
|
8
|
+
return typeof name === 'string' ? value === name : name.test(value);
|
|
9
|
+
}
|
|
10
|
+
/** Extract cited `it('…')` titles from the mechanism column of ADR enforcement tables. */
|
|
11
|
+
function extractCitations(opts) {
|
|
12
|
+
const inDir = picomatch(opts.dir);
|
|
13
|
+
const out = [];
|
|
14
|
+
for (const doc of opts.corpus.documents()) {
|
|
15
|
+
if (!inDir(doc.relPath))
|
|
16
|
+
continue;
|
|
17
|
+
const table = doc.tables.find((t) => t.sectionPath.some((h) => matchName(h, opts.section)) &&
|
|
18
|
+
t.header.some((h) => opts.mechanismColumn.test(h)));
|
|
19
|
+
if (!table)
|
|
20
|
+
continue;
|
|
21
|
+
const mechIdx = table.header.findIndex((h) => opts.mechanismColumn.test(h));
|
|
22
|
+
if (mechIdx < 0)
|
|
23
|
+
continue;
|
|
24
|
+
for (const row of table.rows) {
|
|
25
|
+
const mech = row[mechIdx] ?? '';
|
|
26
|
+
for (const m of mech.matchAll(IT_CITE_RE)) {
|
|
27
|
+
const title = m[1];
|
|
28
|
+
if (title !== undefined)
|
|
29
|
+
out.push({ title, adr: doc.relPath, line: table.line });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
/** Collect actual `it('…')` definitions from the project via eess-ts's public call API. */
|
|
36
|
+
function extractTestDefs(project) {
|
|
37
|
+
const allCalls = calls(project).select({
|
|
38
|
+
label: 'call',
|
|
39
|
+
identify: (c) => ({ name: c.getName() ?? '' }),
|
|
40
|
+
}).elements;
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const call of allCalls) {
|
|
43
|
+
if (call.getName() !== 'it')
|
|
44
|
+
continue;
|
|
45
|
+
const enriched = call.getName({ withArgument: 0 }) ?? '';
|
|
46
|
+
const m = IT_NAME_RE.exec(enriched);
|
|
47
|
+
if (m?.[1] !== undefined) {
|
|
48
|
+
out.push({ title: m[1], file: call.getSourceFile().getFilePath() });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Cross-validate that every `it('…')` cited in an ADR's enforcement table
|
|
55
|
+
* actually exists as a test in the project. The AST-grounded upgrade of the
|
|
56
|
+
* text-level citation check in `eess-md`'s `adrEnforcement` — it resolves titles
|
|
57
|
+
* against real test call expressions (via eess-ts's public API; no ts-morph
|
|
58
|
+
* here, per ADR-007), so it also sees `it(\`no-substitution template\`)` titles
|
|
59
|
+
* the regex missed.
|
|
60
|
+
*/
|
|
61
|
+
export function adrCitationsResolve(corpus, project, options = {}) {
|
|
62
|
+
const dir = options.dir ?? 'docs/adr/**';
|
|
63
|
+
const section = options.section ?? /^enforcement$/i;
|
|
64
|
+
const mechanismColumn = options.mechanismColumn ?? /mechanism/i;
|
|
65
|
+
const citations = extractCitations({ corpus, dir, section, mechanismColumn });
|
|
66
|
+
const testDefs = extractTestDefs(project);
|
|
67
|
+
const left = {
|
|
68
|
+
elements: citations,
|
|
69
|
+
label: 'cited it()',
|
|
70
|
+
identify: (c) => ({ name: `it('${c.title}')`, file: c.adr, line: c.line }),
|
|
71
|
+
};
|
|
72
|
+
const right = {
|
|
73
|
+
elements: testDefs,
|
|
74
|
+
label: 'test',
|
|
75
|
+
identify: (d) => ({ name: `it('${d.title}')`, file: d.file }),
|
|
76
|
+
};
|
|
77
|
+
correspondence({
|
|
78
|
+
left,
|
|
79
|
+
right,
|
|
80
|
+
keyBy: (e) => e.title,
|
|
81
|
+
})
|
|
82
|
+
.should()
|
|
83
|
+
.beComplete({ direction: 'left-to-right' })
|
|
84
|
+
.rule({
|
|
85
|
+
id: 'crossval/adr-citations-resolve',
|
|
86
|
+
because: 'an ADR that cites a test must cite one that exists',
|
|
87
|
+
})
|
|
88
|
+
.check();
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=md-ts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"md-ts.js","sourceRoot":"","sources":["../src/md-ts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Direction } from '@nielspeter/eess';
|
|
2
|
+
import { type ArchProject as MermaidDiagram } from '@nielspeter/eess-mermaid';
|
|
3
|
+
import { type ArchProject } from '@nielspeter/eess-ts';
|
|
4
|
+
export interface DiagramMatchesCodeOptions {
|
|
5
|
+
/** Glob (matched against a class's directory) restricting which TS classes
|
|
6
|
+
* participate. Defaults to a `src` folder anywhere in the tree. */
|
|
7
|
+
readonly scope?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Completeness direction. `both` (default) = every diagram class has a TS
|
|
10
|
+
* class and vice versa — the walkthrough stage-7 property.
|
|
11
|
+
*/
|
|
12
|
+
readonly completeness?: Direction;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Cross-validate a Mermaid class diagram against TypeScript code: every class in
|
|
16
|
+
* the diagram has a matching TS class and vice versa. Built on the kernel
|
|
17
|
+
* `correspondence()` primitive (class-level, both directions) — the correct
|
|
18
|
+
* generalization of the deprecated `fromDiagram()` bridge.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* diagramMatchesCode(diagram('docs/architecture.mmd'), project('tsconfig.json'))
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare function diagramMatchesCode(diagram: MermaidDiagram, project: ArchProject, options?: DiagramMatchesCodeOptions): void;
|
|
25
|
+
//# sourceMappingURL=mermaid-ts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mermaid-ts.d.ts","sourceRoot":"","sources":["../src/mermaid-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAyB,KAAK,WAAW,IAAI,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACpG,OAAO,EAAwB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAE5E,MAAM,WAAW,yBAAyB;IACxC;uEACmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,CAAA;CAClC;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,yBAA8B,GACtC,IAAI,CA4BN"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { correspondence } from '@nielspeter/eess';
|
|
2
|
+
import { classes as mmdClasses } from '@nielspeter/eess-mermaid';
|
|
3
|
+
import { classes as tsClasses } from '@nielspeter/eess-ts';
|
|
4
|
+
/**
|
|
5
|
+
* Cross-validate a Mermaid class diagram against TypeScript code: every class in
|
|
6
|
+
* the diagram has a matching TS class and vice versa. Built on the kernel
|
|
7
|
+
* `correspondence()` primitive (class-level, both directions) — the correct
|
|
8
|
+
* generalization of the deprecated `fromDiagram()` bridge.
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* diagramMatchesCode(diagram('docs/architecture.mmd'), project('tsconfig.json'))
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function diagramMatchesCode(diagram, project, options = {}) {
|
|
15
|
+
const scope = options.scope ?? '**/src/**';
|
|
16
|
+
const left = mmdClasses(diagram).select({
|
|
17
|
+
label: 'diagram class',
|
|
18
|
+
identify: (c) => ({ name: c.name }),
|
|
19
|
+
});
|
|
20
|
+
const right = tsClasses(project)
|
|
21
|
+
.that()
|
|
22
|
+
.resideInFolder(scope)
|
|
23
|
+
.select({
|
|
24
|
+
label: 'TS class',
|
|
25
|
+
identify: (c) => ({
|
|
26
|
+
name: c.getName() ?? '<anonymous>',
|
|
27
|
+
file: c.getSourceFile().getFilePath(),
|
|
28
|
+
line: c.getStartLineNumber(),
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
correspondence({ left, right })
|
|
32
|
+
.should()
|
|
33
|
+
.beComplete({ direction: options.completeness ?? 'both' })
|
|
34
|
+
.rule({
|
|
35
|
+
id: 'crossval/diagram-completeness',
|
|
36
|
+
because: 'the diagram and the code must agree on which classes exist',
|
|
37
|
+
})
|
|
38
|
+
.check();
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=mermaid-ts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mermaid-ts.js","sourceRoot":"","sources":["../src/mermaid-ts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAkB,MAAM,kBAAkB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAsC,MAAM,0BAA0B,CAAA;AACpG,OAAO,EAAE,OAAO,IAAI,SAAS,EAAoB,MAAM,qBAAqB,CAAA;AAa5E;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAuB,EACvB,OAAoB,EACpB,UAAqC,EAAE;IAEvC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,CAAA;IAE1C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QACtC,KAAK,EAAE,eAAe;QACtB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;KACpC,CAAC,CAAA;IAEF,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC;SAC7B,IAAI,EAAE;SACN,cAAc,CAAC,KAAK,CAAC;SACrB,MAAM,CAAC;QACN,KAAK,EAAE,UAAU;QACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,aAAa;YAClC,IAAI,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC,WAAW,EAAE;YACrC,IAAI,EAAE,CAAC,CAAC,kBAAkB,EAAE;SAC7B,CAAC;KACH,CAAC,CAAA;IAEJ,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;SAC5B,MAAM,EAAE;SACR,UAAU,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,YAAY,IAAI,MAAM,EAAE,CAAC;SACzD,IAAI,CAAC;QACJ,EAAE,EAAE,+BAA+B;QACnC,OAAO,EAAE,4DAA4D;KACtE,CAAC;SACD,KAAK,EAAE,CAAA;AACZ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nielspeter/eess-crossvalidate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cross-validation presets for the eess family — bind two dialects' artifacts and fail when they drift.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./mermaid-ts": {
|
|
8
|
+
"types": "./dist/mermaid-ts.d.ts",
|
|
9
|
+
"import": "./dist/mermaid-ts.js"
|
|
10
|
+
},
|
|
11
|
+
"./md-ts": {
|
|
12
|
+
"types": "./dist/md-ts.d.ts",
|
|
13
|
+
"import": "./dist/md-ts.js"
|
|
14
|
+
},
|
|
15
|
+
"./md-mermaid": {
|
|
16
|
+
"types": "./dist/md-mermaid.d.ts",
|
|
17
|
+
"import": "./dist/md-mermaid.js"
|
|
18
|
+
},
|
|
19
|
+
"./files": {
|
|
20
|
+
"types": "./dist/files.d.ts",
|
|
21
|
+
"import": "./dist/files.js"
|
|
22
|
+
},
|
|
23
|
+
"./md-gherkin": {
|
|
24
|
+
"types": "./dist/md-gherkin.d.ts",
|
|
25
|
+
"import": "./dist/md-gherkin.js"
|
|
26
|
+
},
|
|
27
|
+
"./md-mermaid-er": {
|
|
28
|
+
"types": "./dist/md-mermaid-er.d.ts",
|
|
29
|
+
"import": "./dist/md-mermaid-er.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=24.0.0"
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/nielspeter/eess.git",
|
|
43
|
+
"directory": "packages/crossvalidate"
|
|
44
|
+
},
|
|
45
|
+
"author": "Niels Peter",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"prepublishOnly": "npm run build",
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"eess",
|
|
55
|
+
"cross-validation",
|
|
56
|
+
"architecture",
|
|
57
|
+
"mermaid",
|
|
58
|
+
"markdown"
|
|
59
|
+
],
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@nielspeter/eess": "^0.1.0",
|
|
62
|
+
"picomatch": "^4.0.4"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@nielspeter/eess-ts": "^0.1.0",
|
|
66
|
+
"@nielspeter/eess-mermaid": "^0.1.0",
|
|
67
|
+
"@nielspeter/eess-md": "^0.1.0",
|
|
68
|
+
"@nielspeter/eess-gherkin": "^0.1.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"@nielspeter/eess-ts": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@nielspeter/eess-mermaid": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@nielspeter/eess-md": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@nielspeter/eess-gherkin": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|