@organon-methodology/testing 0.3.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 +80 -0
- package/dist/adapters/vitest.d.ts +49 -0
- package/dist/adapters/vitest.d.ts.map +1 -0
- package/dist/adapters/vitest.js +60 -0
- package/dist/adapters/vitest.js.map +1 -0
- package/dist/core/assert-custom.d.ts +43 -0
- package/dist/core/assert-custom.d.ts.map +1 -0
- package/dist/core/assert-custom.js +48 -0
- package/dist/core/assert-custom.js.map +1 -0
- package/dist/core/assert-exports-present.d.ts +52 -0
- package/dist/core/assert-exports-present.d.ts.map +1 -0
- package/dist/core/assert-exports-present.js +125 -0
- package/dist/core/assert-exports-present.js.map +1 -0
- package/dist/core/assert-file-exists.d.ts +41 -0
- package/dist/core/assert-file-exists.d.ts.map +1 -0
- package/dist/core/assert-file-exists.js +52 -0
- package/dist/core/assert-file-exists.js.map +1 -0
- package/dist/core/assert-max-value.d.ts +79 -0
- package/dist/core/assert-max-value.d.ts.map +1 -0
- package/dist/core/assert-max-value.js +90 -0
- package/dist/core/assert-max-value.js.map +1 -0
- package/dist/core/assert-naming-convention.d.ts +61 -0
- package/dist/core/assert-naming-convention.d.ts.map +1 -0
- package/dist/core/assert-naming-convention.js +92 -0
- package/dist/core/assert-naming-convention.js.map +1 -0
- package/dist/core/assert-no-side-effects.d.ts +55 -0
- package/dist/core/assert-no-side-effects.d.ts.map +1 -0
- package/dist/core/assert-no-side-effects.js +72 -0
- package/dist/core/assert-no-side-effects.js.map +1 -0
- package/dist/core/assertions/exports-present.d.ts +38 -0
- package/dist/core/assertions/exports-present.d.ts.map +1 -0
- package/dist/core/assertions/exports-present.js +47 -0
- package/dist/core/assertions/exports-present.js.map +1 -0
- package/dist/core/assertions/file-exists.d.ts +45 -0
- package/dist/core/assertions/file-exists.d.ts.map +1 -0
- package/dist/core/assertions/file-exists.js +46 -0
- package/dist/core/assertions/file-exists.js.map +1 -0
- package/dist/core/assertions/max-value.d.ts +77 -0
- package/dist/core/assertions/max-value.d.ts.map +1 -0
- package/dist/core/assertions/max-value.js +60 -0
- package/dist/core/assertions/max-value.js.map +1 -0
- package/dist/core/assertions/naming-convention.d.ts +51 -0
- package/dist/core/assertions/naming-convention.d.ts.map +1 -0
- package/dist/core/assertions/naming-convention.js +73 -0
- package/dist/core/assertions/naming-convention.js.map +1 -0
- package/dist/core/assertions/no-side-effects.d.ts +47 -0
- package/dist/core/assertions/no-side-effects.d.ts.map +1 -0
- package/dist/core/assertions/no-side-effects.js +70 -0
- package/dist/core/assertions/no-side-effects.js.map +1 -0
- package/dist/core/invariant-test.d.ts +128 -0
- package/dist/core/invariant-test.d.ts.map +1 -0
- package/dist/core/invariant-test.js +174 -0
- package/dist/core/invariant-test.js.map +1 -0
- package/dist/core/resolvers/file-resolver.d.ts +92 -0
- package/dist/core/resolvers/file-resolver.d.ts.map +1 -0
- package/dist/core/resolvers/file-resolver.js +103 -0
- package/dist/core/resolvers/file-resolver.js.map +1 -0
- package/dist/core/resolvers/import-resolver.d.ts +55 -0
- package/dist/core/resolvers/import-resolver.d.ts.map +1 -0
- package/dist/core/resolvers/import-resolver.js +99 -0
- package/dist/core/resolvers/import-resolver.js.map +1 -0
- package/dist/core/resolvers/node-fs.d.ts +13 -0
- package/dist/core/resolvers/node-fs.d.ts.map +1 -0
- package/dist/core/resolvers/node-fs.js +32 -0
- package/dist/core/resolvers/node-fs.js.map +1 -0
- package/dist/core/resolvers/parallel-reader.d.ts +35 -0
- package/dist/core/resolvers/parallel-reader.d.ts.map +1 -0
- package/dist/core/resolvers/parallel-reader.js +56 -0
- package/dist/core/resolvers/parallel-reader.js.map +1 -0
- package/dist/core/resolvers/string-resolver.d.ts +66 -0
- package/dist/core/resolvers/string-resolver.d.ts.map +1 -0
- package/dist/core/resolvers/string-resolver.js +127 -0
- package/dist/core/resolvers/string-resolver.js.map +1 -0
- package/dist/core/resolvers/types.d.ts +16 -0
- package/dist/core/resolvers/types.d.ts.map +1 -0
- package/dist/core/resolvers/types.js +10 -0
- package/dist/core/resolvers/types.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* testInvariant — Wrapper that links tests to invariant IDs.
|
|
3
|
+
*
|
|
4
|
+
* Invariants enforced:
|
|
5
|
+
* - INV-TEST-3 (invariant-id-required): Every test must link to an invariant ID.
|
|
6
|
+
* - INV-TEST-4 (framework-agnostic-core): Core logic has zero test-framework deps.
|
|
7
|
+
* - INV-TEST-6 (always-async): Test function is always async.
|
|
8
|
+
* - INV-TEST-7 (composable): No module-level mutable state that affects test outcomes.
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - The metadata registry is a read-only record of test registrations.
|
|
12
|
+
* It stores what tests were registered but does not affect test execution.
|
|
13
|
+
* Tests execute identically regardless of registry state (INV-TEST-7).
|
|
14
|
+
* - The wrapper delegates to a configurable test runner function.
|
|
15
|
+
* By default, it uses a pass-through runner that just calls the test function.
|
|
16
|
+
* Adapters (e.g., vitest.ts) provide framework-specific runners.
|
|
17
|
+
*/
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Validation
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Error thrown when testInvariant is called with invalid arguments.
|
|
23
|
+
*/
|
|
24
|
+
export class InvariantTestError extends Error {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'InvariantTestError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate an invariant ID format.
|
|
32
|
+
*
|
|
33
|
+
* Valid format: PREFIX-WORD-NUMBER (e.g., "INV-CACHE-1", "INV-TEST-42")
|
|
34
|
+
* Must be non-empty and follow the ID convention.
|
|
35
|
+
*/
|
|
36
|
+
export function validateInvariantId(id) {
|
|
37
|
+
if (!id || typeof id !== 'string') {
|
|
38
|
+
throw new InvariantTestError('Invariant ID is required. Provide a non-empty string (e.g., "INV-CACHE-1").');
|
|
39
|
+
}
|
|
40
|
+
const trimmed = id.trim();
|
|
41
|
+
if (trimmed.length === 0) {
|
|
42
|
+
throw new InvariantTestError('Invariant ID must not be empty or whitespace-only.');
|
|
43
|
+
}
|
|
44
|
+
if (trimmed !== id) {
|
|
45
|
+
throw new InvariantTestError(`Invariant ID "${id}" contains leading or trailing whitespace. Use "${trimmed}" instead.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validate a test description.
|
|
50
|
+
*/
|
|
51
|
+
export function validateDescription(description) {
|
|
52
|
+
if (!description || typeof description !== 'string') {
|
|
53
|
+
throw new InvariantTestError('Test description is required. Provide a non-empty string describing what the test verifies.');
|
|
54
|
+
}
|
|
55
|
+
const trimmed = description.trim();
|
|
56
|
+
if (trimmed.length === 0) {
|
|
57
|
+
throw new InvariantTestError('Test description must not be empty or whitespace-only.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate a test function.
|
|
62
|
+
*/
|
|
63
|
+
export function validateTestFn(testFn) {
|
|
64
|
+
if (typeof testFn !== 'function') {
|
|
65
|
+
throw new InvariantTestError(`Test function is required. Got ${typeof testFn} instead of a function.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a new empty registry instance.
|
|
70
|
+
*/
|
|
71
|
+
export function createRegistry() {
|
|
72
|
+
const entries = [];
|
|
73
|
+
return {
|
|
74
|
+
register(metadata) {
|
|
75
|
+
entries.push({ ...metadata });
|
|
76
|
+
},
|
|
77
|
+
getAll() {
|
|
78
|
+
return entries.map((e) => ({ ...e }));
|
|
79
|
+
},
|
|
80
|
+
has(invariantId) {
|
|
81
|
+
return entries.some((e) => e.invariantId === invariantId);
|
|
82
|
+
},
|
|
83
|
+
size() {
|
|
84
|
+
return entries.length;
|
|
85
|
+
},
|
|
86
|
+
coveredCount() {
|
|
87
|
+
return new Set(entries.map((e) => e.invariantId)).size;
|
|
88
|
+
},
|
|
89
|
+
clear() {
|
|
90
|
+
entries.length = 0;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Default registry (module-scoped singleton for convenience)
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* The default global registry. Used by testInvariant() when no custom
|
|
99
|
+
* registry is provided. Can be accessed via getDefaultRegistry() for
|
|
100
|
+
* coverage reporting.
|
|
101
|
+
*/
|
|
102
|
+
const defaultRegistry = createRegistry();
|
|
103
|
+
/**
|
|
104
|
+
* Get the default global registry for coverage reporting.
|
|
105
|
+
*/
|
|
106
|
+
export function getDefaultRegistry() {
|
|
107
|
+
return defaultRegistry;
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Core: testInvariant()
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/**
|
|
113
|
+
* Default test runner: executes the async test function directly.
|
|
114
|
+
* Used when no framework-specific adapter is configured.
|
|
115
|
+
* Framework adapters (vitest, jest) replace this with their own `it()`/`test()` wrapper.
|
|
116
|
+
*
|
|
117
|
+
* Since TestRunner is synchronous (for framework compatibility) but testFn is async,
|
|
118
|
+
* failures surface as unhandled promise rejections. In Node.js 15+, unhandled
|
|
119
|
+
* rejections crash the process with a clear error. A .catch() handler wraps the
|
|
120
|
+
* error with the test name for better diagnostics.
|
|
121
|
+
*/
|
|
122
|
+
const defaultRunner = (testName, testFn, _metadata) => {
|
|
123
|
+
testFn().catch((err) => {
|
|
124
|
+
const wrapped = err instanceof Error
|
|
125
|
+
? err
|
|
126
|
+
: new Error(String(err));
|
|
127
|
+
wrapped.message = `[${testName}] ${wrapped.message}`;
|
|
128
|
+
// Re-throw as unhandled rejection so Node.js exits non-zero
|
|
129
|
+
queueMicrotask(() => { throw wrapped; });
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* Register and declare an invariant test.
|
|
134
|
+
*
|
|
135
|
+
* This is the primary API for linking tests to invariant IDs (INV-TEST-3).
|
|
136
|
+
*
|
|
137
|
+
* Usage:
|
|
138
|
+
* ```typescript
|
|
139
|
+
* testInvariant('INV-CACHE-1', 'cache TTL max 24h', async () => {
|
|
140
|
+
* await assertMaxValue({ ... });
|
|
141
|
+
* });
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* What it does:
|
|
145
|
+
* 1. Validates inputs (invariant ID, description, test function)
|
|
146
|
+
* 2. Registers the test in the metadata registry (for coverage tracking)
|
|
147
|
+
* 3. Delegates to the configured test runner (framework adapter)
|
|
148
|
+
*
|
|
149
|
+
* @param invariantId - The invariant ID this test verifies (e.g., "INV-CACHE-1")
|
|
150
|
+
* @param description - Human-readable description of what the test checks
|
|
151
|
+
* @param testFn - Async function that performs assertions
|
|
152
|
+
* @param options - Optional configuration (custom runner, custom registry)
|
|
153
|
+
*
|
|
154
|
+
* @throws {InvariantTestError} if inputs are invalid
|
|
155
|
+
*/
|
|
156
|
+
export function testInvariant(invariantId, description, testFn, options) {
|
|
157
|
+
// 1. Validate inputs (INV-TEST-2: fail-fast on bad input)
|
|
158
|
+
validateInvariantId(invariantId);
|
|
159
|
+
validateDescription(description);
|
|
160
|
+
validateTestFn(testFn);
|
|
161
|
+
// 2. Build metadata
|
|
162
|
+
const metadata = {
|
|
163
|
+
invariantId,
|
|
164
|
+
description,
|
|
165
|
+
};
|
|
166
|
+
// 3. Register in the registry (for coverage tracking)
|
|
167
|
+
const registry = options?.registry ?? defaultRegistry;
|
|
168
|
+
registry.register(metadata);
|
|
169
|
+
// 4. Delegate to test runner
|
|
170
|
+
const runner = options?.runner ?? defaultRunner;
|
|
171
|
+
const testName = `[${invariantId}] ${description}`;
|
|
172
|
+
runner(testName, testFn, metadata);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=invariant-test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariant-test.js","sourceRoot":"","sources":["../../src/core/invariant-test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File resolver — I/O layer that reads files and feeds data to pure assertions.
|
|
3
|
+
*
|
|
4
|
+
* This module handles all file system interactions for assertions.
|
|
5
|
+
* It expands globs, reads file contents, and extracts pattern matches
|
|
6
|
+
* that are then validated by pure assertion functions in core/assertions/.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Depends on FileSystem interface (mockable for tests)
|
|
10
|
+
* - Returns structured data, never throws on I/O errors (wraps them)
|
|
11
|
+
* - Assertions receive pre-resolved data, never file paths
|
|
12
|
+
*/
|
|
13
|
+
import type { FileSystem } from './types.js';
|
|
14
|
+
/**
|
|
15
|
+
* A single match found in a file by the resolver.
|
|
16
|
+
*/
|
|
17
|
+
export interface FileMatch {
|
|
18
|
+
/** Path of the file where the match was found */
|
|
19
|
+
file: string;
|
|
20
|
+
/** The line number (1-based) where the match occurred */
|
|
21
|
+
line: number;
|
|
22
|
+
/** The full line of text containing the match */
|
|
23
|
+
lineText: string;
|
|
24
|
+
/** The raw matched string from the capturing group */
|
|
25
|
+
rawMatch: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A numeric value extracted from a file by pattern matching.
|
|
29
|
+
*/
|
|
30
|
+
export interface ExtractedValue {
|
|
31
|
+
/** Path of the file where the value was found */
|
|
32
|
+
file: string;
|
|
33
|
+
/** The line number (1-based) where the value was found */
|
|
34
|
+
line: number;
|
|
35
|
+
/** The full line of text containing the value */
|
|
36
|
+
lineText: string;
|
|
37
|
+
/** The extracted numeric value */
|
|
38
|
+
value: number;
|
|
39
|
+
/** The raw string that was parsed as a number */
|
|
40
|
+
rawValue: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Result of resolving files and extracting numeric values by pattern.
|
|
44
|
+
*/
|
|
45
|
+
export interface ResolvedValues {
|
|
46
|
+
/** Successfully extracted numeric values */
|
|
47
|
+
values: ExtractedValue[];
|
|
48
|
+
/** Files that were resolved and read */
|
|
49
|
+
filesRead: string[];
|
|
50
|
+
/** Errors encountered during resolution (e.g., file not found) */
|
|
51
|
+
errors: ResolverError[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* An error encountered during file resolution.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResolverError {
|
|
57
|
+
file: string;
|
|
58
|
+
message: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Options for resolving numeric values from files.
|
|
62
|
+
*/
|
|
63
|
+
export interface ResolveValuesOptions {
|
|
64
|
+
/** File paths or glob patterns to scan */
|
|
65
|
+
files: string[];
|
|
66
|
+
/** Regex pattern with at least one capturing group for the numeric value */
|
|
67
|
+
pattern: RegExp;
|
|
68
|
+
/** Working directory for glob resolution */
|
|
69
|
+
cwd?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Expand glob patterns to concrete file paths.
|
|
73
|
+
*
|
|
74
|
+
* Non-glob paths (no * or ? characters) are returned as-is.
|
|
75
|
+
*/
|
|
76
|
+
export declare function expandGlobs(files: string[], fs: FileSystem, cwd?: string): Promise<{
|
|
77
|
+
paths: string[];
|
|
78
|
+
errors: ResolverError[];
|
|
79
|
+
}>;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve files and extract numeric values matching a pattern.
|
|
82
|
+
*
|
|
83
|
+
* This function:
|
|
84
|
+
* 1. Expands glob patterns to concrete file paths
|
|
85
|
+
* 2. Reads each file
|
|
86
|
+
* 3. Scans each line for the regex pattern
|
|
87
|
+
* 4. Extracts numeric values from the first capturing group
|
|
88
|
+
*
|
|
89
|
+
* Non-numeric captures are reported as errors rather than silently skipped.
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveValues(options: ResolveValuesOptions, fs: FileSystem): Promise<ResolvedValues>;
|
|
92
|
+
//# sourceMappingURL=file-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-resolver.d.ts","sourceRoot":"","sources":["../../../src/core/resolvers/file-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kEAAkE;IAClE,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EAAE,EACf,EAAE,EAAE,UAAU,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAqBvD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,EAC7B,EAAE,EAAE,UAAU,GACb,OAAO,CAAC,cAAc,CAAC,CA0DzB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File resolver — I/O layer that reads files and feeds data to pure assertions.
|
|
3
|
+
*
|
|
4
|
+
* This module handles all file system interactions for assertions.
|
|
5
|
+
* It expands globs, reads file contents, and extracts pattern matches
|
|
6
|
+
* that are then validated by pure assertion functions in core/assertions/.
|
|
7
|
+
*
|
|
8
|
+
* Design:
|
|
9
|
+
* - Depends on FileSystem interface (mockable for tests)
|
|
10
|
+
* - Returns structured data, never throws on I/O errors (wraps them)
|
|
11
|
+
* - Assertions receive pre-resolved data, never file paths
|
|
12
|
+
*/
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { readFilesParallel } from './parallel-reader.js';
|
|
15
|
+
/**
|
|
16
|
+
* Expand glob patterns to concrete file paths.
|
|
17
|
+
*
|
|
18
|
+
* Non-glob paths (no * or ? characters) are returned as-is.
|
|
19
|
+
*/
|
|
20
|
+
export async function expandGlobs(files, fs, cwd) {
|
|
21
|
+
const paths = [];
|
|
22
|
+
const errors = [];
|
|
23
|
+
for (const file of files) {
|
|
24
|
+
if (file.includes('*') || file.includes('?')) {
|
|
25
|
+
try {
|
|
26
|
+
const expanded = await fs.glob(file, cwd ? { cwd } : undefined);
|
|
27
|
+
paths.push(...expanded);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
errors.push({
|
|
31
|
+
file,
|
|
32
|
+
message: `Failed to expand glob: ${err instanceof Error ? err.message : String(err)}`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
paths.push(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { paths, errors };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve files and extract numeric values matching a pattern.
|
|
44
|
+
*
|
|
45
|
+
* This function:
|
|
46
|
+
* 1. Expands glob patterns to concrete file paths
|
|
47
|
+
* 2. Reads each file
|
|
48
|
+
* 3. Scans each line for the regex pattern
|
|
49
|
+
* 4. Extracts numeric values from the first capturing group
|
|
50
|
+
*
|
|
51
|
+
* Non-numeric captures are reported as errors rather than silently skipped.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveValues(options, fs) {
|
|
54
|
+
const { files, pattern, cwd } = options;
|
|
55
|
+
const { paths, errors } = await expandGlobs(files, fs, cwd);
|
|
56
|
+
const values = [];
|
|
57
|
+
const filesRead = [];
|
|
58
|
+
// Build read paths (prepend cwd when set)
|
|
59
|
+
const readPaths = paths.map((p) => cwd ? join(cwd, p) : p);
|
|
60
|
+
// Read all files in parallel with concurrency control
|
|
61
|
+
const readResults = await readFilesParallel(readPaths, fs);
|
|
62
|
+
for (let idx = 0; idx < readResults.length; idx++) {
|
|
63
|
+
const result = readResults[idx];
|
|
64
|
+
const filePath = paths[idx];
|
|
65
|
+
if (result.error) {
|
|
66
|
+
errors.push({
|
|
67
|
+
file: filePath,
|
|
68
|
+
message: `Failed to read file: ${result.error}`,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
filesRead.push(filePath);
|
|
73
|
+
const content = result.content;
|
|
74
|
+
const lines = content.split('\n');
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const lineText = lines[i];
|
|
77
|
+
// Reset regex state for global patterns
|
|
78
|
+
const localPattern = new RegExp(pattern.source, pattern.flags.replace('g', ''));
|
|
79
|
+
const match = localPattern.exec(lineText);
|
|
80
|
+
if (match && match[1] !== undefined) {
|
|
81
|
+
const rawValue = match[1];
|
|
82
|
+
const parsed = Number(rawValue);
|
|
83
|
+
if (Number.isNaN(parsed)) {
|
|
84
|
+
errors.push({
|
|
85
|
+
file: filePath,
|
|
86
|
+
message: `Line ${i + 1}: Captured "${rawValue}" is not a valid number`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
values.push({
|
|
91
|
+
file: filePath,
|
|
92
|
+
line: i + 1,
|
|
93
|
+
lineText,
|
|
94
|
+
value: parsed,
|
|
95
|
+
rawValue,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { values, filesRead, errors };
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=file-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-resolver.js","sourceRoot":"","sources":["../../../src/core/resolvers/file-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import resolver — scans files for import/require statements.
|
|
3
|
+
*
|
|
4
|
+
* Extracts structured import data from TypeScript/JavaScript files.
|
|
5
|
+
* Used by assertNoSideEffects to check for forbidden module imports.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Depends on FileSystem interface (mockable for tests)
|
|
9
|
+
* - Returns structured data for pure assertion validation
|
|
10
|
+
*/
|
|
11
|
+
import type { FileSystem } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* A single import/require match found in a file.
|
|
14
|
+
*/
|
|
15
|
+
export interface ImportMatch {
|
|
16
|
+
/** Path of the file containing the import */
|
|
17
|
+
file: string;
|
|
18
|
+
/** The line number (1-based) where the import was found */
|
|
19
|
+
line: number;
|
|
20
|
+
/** The full line of text containing the import */
|
|
21
|
+
lineText: string;
|
|
22
|
+
/** The module specifier (e.g., 'fs', 'node:http', './utils') */
|
|
23
|
+
module: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Extract all import/require module specifiers from a file's content.
|
|
27
|
+
*
|
|
28
|
+
* This is a pure function — no I/O. Takes content string, returns matches.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractImports(file: string, content: string): ImportMatch[];
|
|
31
|
+
/**
|
|
32
|
+
* Result of resolving imports across multiple files.
|
|
33
|
+
*/
|
|
34
|
+
export interface ResolvedImports {
|
|
35
|
+
/** All import matches found across files */
|
|
36
|
+
imports: ImportMatch[];
|
|
37
|
+
/** Files that were successfully read */
|
|
38
|
+
filesRead: string[];
|
|
39
|
+
/** Errors encountered during resolution */
|
|
40
|
+
errors: Array<{
|
|
41
|
+
file: string;
|
|
42
|
+
message: string;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve imports from files matching glob patterns.
|
|
47
|
+
*
|
|
48
|
+
* Expands globs, reads files, and extracts all imports.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveImports(options: {
|
|
51
|
+
files: string[];
|
|
52
|
+
cwd?: string;
|
|
53
|
+
fs: FileSystem;
|
|
54
|
+
}): Promise<ResolvedImports>;
|
|
55
|
+
//# sourceMappingURL=import-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-resolver.d.ts","sourceRoot":"","sources":["../../../src/core/resolvers/import-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CAsBf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,wCAAwC;IACxC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,2CAA2C;IAC3C,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,UAAU,CAAC;CAChB,GAAG,OAAO,CAAC,eAAe,CAAC,CA0C3B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import resolver — scans files for import/require statements.
|
|
3
|
+
*
|
|
4
|
+
* Extracts structured import data from TypeScript/JavaScript files.
|
|
5
|
+
* Used by assertNoSideEffects to check for forbidden module imports.
|
|
6
|
+
*
|
|
7
|
+
* Design:
|
|
8
|
+
* - Depends on FileSystem interface (mockable for tests)
|
|
9
|
+
* - Returns structured data for pure assertion validation
|
|
10
|
+
*/
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
/**
|
|
13
|
+
* Patterns to match import/require statements.
|
|
14
|
+
* Captures the module specifier from:
|
|
15
|
+
* - import ... from 'module'
|
|
16
|
+
* - import 'module'
|
|
17
|
+
* - require('module')
|
|
18
|
+
* - import('module') (dynamic import)
|
|
19
|
+
*/
|
|
20
|
+
const IMPORT_PATTERNS = [
|
|
21
|
+
/import\s+.*?\s+from\s+['"]([^'"]+)['"]/,
|
|
22
|
+
/import\s+['"]([^'"]+)['"]/,
|
|
23
|
+
/require\(\s*['"]([^'"]+)['"]\s*\)/,
|
|
24
|
+
/import\(\s*['"]([^'"]+)['"]\s*\)/,
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Extract all import/require module specifiers from a file's content.
|
|
28
|
+
*
|
|
29
|
+
* This is a pure function — no I/O. Takes content string, returns matches.
|
|
30
|
+
*/
|
|
31
|
+
export function extractImports(file, content) {
|
|
32
|
+
const matches = [];
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
for (let i = 0; i < lines.length; i++) {
|
|
35
|
+
const lineText = lines[i];
|
|
36
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
37
|
+
const match = pattern.exec(lineText);
|
|
38
|
+
if (match && match[1]) {
|
|
39
|
+
matches.push({
|
|
40
|
+
file,
|
|
41
|
+
line: i + 1,
|
|
42
|
+
lineText,
|
|
43
|
+
module: match[1],
|
|
44
|
+
});
|
|
45
|
+
break; // One match per line is sufficient
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return matches;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve imports from files matching glob patterns.
|
|
53
|
+
*
|
|
54
|
+
* Expands globs, reads files, and extracts all imports.
|
|
55
|
+
*/
|
|
56
|
+
export async function resolveImports(options) {
|
|
57
|
+
const { files, cwd, fs } = options;
|
|
58
|
+
const imports = [];
|
|
59
|
+
const filesRead = [];
|
|
60
|
+
const errors = [];
|
|
61
|
+
// Expand globs
|
|
62
|
+
const expandedPaths = [];
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
if (file.includes('*') || file.includes('?')) {
|
|
65
|
+
try {
|
|
66
|
+
const expanded = await fs.glob(file, cwd ? { cwd } : undefined);
|
|
67
|
+
expandedPaths.push(...expanded);
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
errors.push({
|
|
71
|
+
file,
|
|
72
|
+
message: `Failed to expand glob: ${err instanceof Error ? err.message : String(err)}`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
expandedPaths.push(file);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Read files and extract imports
|
|
81
|
+
for (const filePath of expandedPaths) {
|
|
82
|
+
const readPath = cwd ? join(cwd, filePath) : filePath;
|
|
83
|
+
let content;
|
|
84
|
+
try {
|
|
85
|
+
content = await fs.readFile(readPath);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
errors.push({
|
|
89
|
+
file: filePath,
|
|
90
|
+
message: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`,
|
|
91
|
+
});
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
filesRead.push(filePath);
|
|
95
|
+
imports.push(...extractImports(filePath, content));
|
|
96
|
+
}
|
|
97
|
+
return { imports, filesRead, errors };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=import-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"import-resolver.js","sourceRoot":"","sources":["../../../src/core/resolvers/import-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Node.js FileSystem implementation.
|
|
3
|
+
*
|
|
4
|
+
* Uses node:fs/promises for file reading and fast-glob for glob expansion.
|
|
5
|
+
* This is the default filesystem used by assertMaxValue when no custom
|
|
6
|
+
* FileSystem is provided.
|
|
7
|
+
*/
|
|
8
|
+
import type { FileSystem } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a FileSystem backed by Node.js built-in modules + fast-glob.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createNodeFileSystem(): FileSystem;
|
|
13
|
+
//# sourceMappingURL=node-fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-fs.d.ts","sourceRoot":"","sources":["../../../src/core/resolvers/node-fs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,CAmBjD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Node.js FileSystem implementation.
|
|
3
|
+
*
|
|
4
|
+
* Uses node:fs/promises for file reading and fast-glob for glob expansion.
|
|
5
|
+
* This is the default filesystem used by assertMaxValue when no custom
|
|
6
|
+
* FileSystem is provided.
|
|
7
|
+
*/
|
|
8
|
+
import { readFile, access } from 'node:fs/promises';
|
|
9
|
+
import fg from 'fast-glob';
|
|
10
|
+
/**
|
|
11
|
+
* Create a FileSystem backed by Node.js built-in modules + fast-glob.
|
|
12
|
+
*/
|
|
13
|
+
export function createNodeFileSystem() {
|
|
14
|
+
return {
|
|
15
|
+
async readFile(path) {
|
|
16
|
+
return readFile(path, 'utf-8');
|
|
17
|
+
},
|
|
18
|
+
async glob(pattern, options) {
|
|
19
|
+
return fg(pattern, { cwd: options?.cwd });
|
|
20
|
+
},
|
|
21
|
+
async exists(path) {
|
|
22
|
+
try {
|
|
23
|
+
await access(path);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=node-fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-fs.js","sourceRoot":"","sources":["../../../src/core/resolvers/node-fs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,WAAW,CAAC;AAG3B;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,IAAY;YACzB,OAAO,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,OAAe,EAAE,OAA0B;YACpD,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5C,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,IAAY;YACvB,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;gBACnB,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel reader — batched file reading with concurrency control.
|
|
3
|
+
*
|
|
4
|
+
* Provides a shared utility for reading multiple files in parallel
|
|
5
|
+
* using Promise.allSettled with a configurable concurrency limit
|
|
6
|
+
* to avoid file descriptor exhaustion.
|
|
7
|
+
*
|
|
8
|
+
* Used by file-resolver.ts and other resolvers for efficient I/O.
|
|
9
|
+
*/
|
|
10
|
+
import type { FileSystem } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Result of reading a single file.
|
|
13
|
+
*/
|
|
14
|
+
export interface FileReadResult {
|
|
15
|
+
/** The file path that was read */
|
|
16
|
+
path: string;
|
|
17
|
+
/** The file content (undefined if reading failed) */
|
|
18
|
+
content?: string;
|
|
19
|
+
/** Error message if reading failed */
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Read multiple files in parallel with concurrency control.
|
|
24
|
+
*
|
|
25
|
+
* Uses Promise.allSettled to ensure all reads complete (or fail)
|
|
26
|
+
* without aborting on the first error. Files are processed in
|
|
27
|
+
* batches to avoid exhausting file descriptors.
|
|
28
|
+
*
|
|
29
|
+
* @param paths - File paths to read
|
|
30
|
+
* @param fs - FileSystem implementation
|
|
31
|
+
* @param concurrency - Maximum number of concurrent reads (default: 50)
|
|
32
|
+
* @returns Results for each file (success or error)
|
|
33
|
+
*/
|
|
34
|
+
export declare function readFilesParallel(paths: string[], fs: FileSystem, concurrency?: number): Promise<FileReadResult[]>;
|
|
35
|
+
//# sourceMappingURL=parallel-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parallel-reader.d.ts","sourceRoot":"","sources":["../../../src/core/resolvers/parallel-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAO7C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EAAE,EACf,EAAE,EAAE,UAAU,EACd,WAAW,GAAE,MAA4B,GACxC,OAAO,CAAC,cAAc,EAAE,CAAC,CAkC3B"}
|