@gobing-ai/ts-rule-engine 0.2.6 → 0.2.7
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/dist/config/extensions.d.ts +46 -0
- package/dist/config/extensions.d.ts.map +1 -0
- package/dist/config/extensions.js +63 -0
- package/dist/config/loader.js +13 -3
- package/dist/engine.d.ts +26 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +79 -0
- package/dist/evaluators/exit-code-evaluator.d.ts +1 -1
- package/dist/evaluators/exit-code-evaluator.d.ts.map +1 -1
- package/dist/evaluators/exit-code-evaluator.js +22 -9
- package/dist/evaluators/forbidden-import-evaluator.d.ts +16 -3
- package/dist/evaluators/forbidden-import-evaluator.d.ts.map +1 -1
- package/dist/evaluators/forbidden-import-evaluator.js +71 -6
- package/dist/evaluators/import-boundary-evaluator.d.ts +19 -0
- package/dist/evaluators/import-boundary-evaluator.d.ts.map +1 -0
- package/dist/evaluators/import-boundary-evaluator.js +85 -0
- package/dist/evaluators/path-evaluator.d.ts +15 -2
- package/dist/evaluators/path-evaluator.d.ts.map +1 -1
- package/dist/evaluators/path-evaluator.js +49 -3
- package/dist/evaluators/regex-evaluator.d.ts.map +1 -1
- package/dist/evaluators/regex-evaluator.js +43 -8
- package/dist/evaluators/schema-artifact-evaluator.d.ts +21 -0
- package/dist/evaluators/schema-artifact-evaluator.d.ts.map +1 -0
- package/dist/evaluators/schema-artifact-evaluator.js +102 -0
- package/dist/evaluators/secrets-scanner-evaluator.d.ts +13 -2
- package/dist/evaluators/secrets-scanner-evaluator.d.ts.map +1 -1
- package/dist/evaluators/secrets-scanner-evaluator.js +72 -9
- package/dist/evaluators/sg-evaluator.d.ts +19 -0
- package/dist/evaluators/sg-evaluator.d.ts.map +1 -0
- package/dist/evaluators/sg-evaluator.js +112 -0
- package/dist/evaluators/test-location-evaluator.d.ts +14 -1
- package/dist/evaluators/test-location-evaluator.d.ts.map +1 -1
- package/dist/evaluators/test-location-evaluator.js +42 -22
- package/dist/evaluators/tsdoc-export-evaluator.js +19 -5
- package/dist/fixers/fixers.d.ts +86 -0
- package/dist/fixers/fixers.d.ts.map +1 -0
- package/dist/fixers/fixers.js +230 -0
- package/dist/fixers/test-stub-fixer.d.ts +49 -0
- package/dist/fixers/test-stub-fixer.d.ts.map +1 -0
- package/dist/fixers/test-stub-fixer.js +91 -0
- package/dist/host/builtins.d.ts.map +1 -1
- package/dist/host/builtins.js +12 -1
- package/dist/host/rule-engine-host.d.ts +3 -0
- package/dist/host/rule-engine-host.d.ts.map +1 -1
- package/dist/host/rule-engine-host.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/resolvers/test-path-resolver.d.ts +72 -0
- package/dist/resolvers/test-path-resolver.d.ts.map +1 -0
- package/dist/resolvers/test-path-resolver.js +112 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/package.json +3 -3
- package/src/config/extensions.ts +108 -0
- package/src/config/loader.ts +13 -3
- package/src/engine.ts +99 -2
- package/src/evaluators/exit-code-evaluator.ts +27 -9
- package/src/evaluators/forbidden-import-evaluator.ts +101 -7
- package/src/evaluators/import-boundary-evaluator.ts +135 -0
- package/src/evaluators/path-evaluator.ts +66 -3
- package/src/evaluators/regex-evaluator.ts +53 -12
- package/src/evaluators/schema-artifact-evaluator.ts +134 -0
- package/src/evaluators/secrets-scanner-evaluator.ts +89 -11
- package/src/evaluators/sg-evaluator.ts +133 -0
- package/src/evaluators/test-location-evaluator.ts +47 -35
- package/src/evaluators/tsdoc-export-evaluator.ts +19 -5
- package/src/fixers/fixers.ts +294 -0
- package/src/fixers/test-stub-fixer.ts +118 -0
- package/src/host/builtins.ts +17 -1
- package/src/host/rule-engine-host.ts +4 -0
- package/src/index.ts +4 -0
- package/src/resolvers/test-path-resolver.ts +133 -0
- package/src/types.ts +34 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TestPathResolver } from '../resolvers/test-path-resolver';
|
|
1
2
|
import type { ResultFormatter, RuleEvaluator } from '../types';
|
|
2
3
|
import { CapabilityRegistry } from './capability-registry';
|
|
3
4
|
/** Host container for rule-engine capabilities. */
|
|
@@ -6,6 +7,8 @@ export declare class RuleEngineHost {
|
|
|
6
7
|
readonly evaluators: CapabilityRegistry<RuleEvaluator>;
|
|
7
8
|
/** Formatter registry keyed by formatter name. */
|
|
8
9
|
readonly formatters: CapabilityRegistry<ResultFormatter>;
|
|
10
|
+
/** Test-path resolver registry keyed by language name. */
|
|
11
|
+
readonly resolvers: CapabilityRegistry<TestPathResolver>;
|
|
9
12
|
constructor();
|
|
10
13
|
}
|
|
11
14
|
//# sourceMappingURL=rule-engine-host.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;;
|
|
1
|
+
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzD,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;;CAO5D"}
|
|
@@ -5,8 +5,11 @@ export class RuleEngineHost {
|
|
|
5
5
|
evaluators;
|
|
6
6
|
/** Formatter registry keyed by formatter name. */
|
|
7
7
|
formatters;
|
|
8
|
+
/** Test-path resolver registry keyed by language name. */
|
|
9
|
+
resolvers;
|
|
8
10
|
constructor() {
|
|
9
11
|
this.evaluators = new CapabilityRegistry('evaluator');
|
|
10
12
|
this.formatters = new CapabilityRegistry('formatter');
|
|
13
|
+
this.resolvers = new CapabilityRegistry('resolver');
|
|
11
14
|
}
|
|
12
15
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export * from './config/extensions';
|
|
1
2
|
export * from './config/loader';
|
|
2
3
|
export * from './engine';
|
|
4
|
+
export * from './fixers/fixers';
|
|
5
|
+
export * from './fixers/test-stub-fixer';
|
|
3
6
|
export * from './formatters/json';
|
|
4
7
|
export * from './formatters/text';
|
|
5
8
|
export * from './host/capability-registry';
|
|
6
9
|
export * from './host/rule-engine-host';
|
|
10
|
+
export * from './resolvers/test-path-resolver';
|
|
7
11
|
export * from './types';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
export * from './config/extensions.js';
|
|
1
2
|
export * from './config/loader.js';
|
|
2
3
|
export * from './engine.js';
|
|
4
|
+
export * from './fixers/fixers.js';
|
|
5
|
+
export * from './fixers/test-stub-fixer.js';
|
|
3
6
|
export * from './formatters/json.js';
|
|
4
7
|
export * from './formatters/text.js';
|
|
5
8
|
export * from './host/capability-registry.js';
|
|
6
9
|
export * from './host/rule-engine-host.js';
|
|
10
|
+
export * from './resolvers/test-path-resolver.js';
|
|
7
11
|
export * from './types.js';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable test-path resolution for the test-location evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Each implementation maps a source file path to its conventional test file path
|
|
5
|
+
* for one language, so the same evaluator works across TypeScript, Python, Go, and
|
|
6
|
+
* Rust projects. Resolvers are registered on the host and selected per rule via
|
|
7
|
+
* `evaluator.config.resolver`.
|
|
8
|
+
*/
|
|
9
|
+
/** Metadata about an exported symbol, used when generating a test skeleton. */
|
|
10
|
+
export interface ExportInfo {
|
|
11
|
+
/** Symbol name. */
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/** Declaration kind. */
|
|
14
|
+
readonly kind: 'function' | 'class' | 'const' | 'type' | 'interface' | 'unknown';
|
|
15
|
+
/** One-based source line, when known. */
|
|
16
|
+
readonly line?: number;
|
|
17
|
+
}
|
|
18
|
+
/** Maps source files to expected test files for a project type. */
|
|
19
|
+
export interface TestPathResolver {
|
|
20
|
+
/** Language name this resolver handles (registry key). */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** Compute the expected test file path for a source file. */
|
|
23
|
+
resolveTestPath(srcRelPath: string): string;
|
|
24
|
+
/** Optionally generate a test skeleton for a source file. */
|
|
25
|
+
generateSkeleton?(srcRelPath: string, exports: ExportInfo[]): string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* TypeScript conventions:
|
|
29
|
+
* src/foo/bar.ts → tests/foo/bar.test.ts
|
|
30
|
+
* packages/core/src/foo/bar.ts → packages/core/tests/foo/bar.test.ts
|
|
31
|
+
*/
|
|
32
|
+
export declare class TypeScriptTestPathResolver implements TestPathResolver {
|
|
33
|
+
/** Registry key. */
|
|
34
|
+
readonly name = "typescript";
|
|
35
|
+
constructor();
|
|
36
|
+
/** Map a TS/JS source path to its `tests/…test.ts` counterpart. */
|
|
37
|
+
resolveTestPath(srcRelPath: string): string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Python conventions (pytest):
|
|
41
|
+
* src/foo/bar.py → tests/foo/test_bar.py
|
|
42
|
+
*/
|
|
43
|
+
export declare class PythonTestPathResolver implements TestPathResolver {
|
|
44
|
+
/** Registry key. */
|
|
45
|
+
readonly name = "python";
|
|
46
|
+
constructor();
|
|
47
|
+
/** Map a Python source path to its `tests/…/test_*.py` counterpart. */
|
|
48
|
+
resolveTestPath(srcRelPath: string): string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Go conventions:
|
|
52
|
+
* foo/bar.go → foo/bar_test.go (sibling)
|
|
53
|
+
*/
|
|
54
|
+
export declare class GoTestPathResolver implements TestPathResolver {
|
|
55
|
+
/** Registry key. */
|
|
56
|
+
readonly name = "go";
|
|
57
|
+
constructor();
|
|
58
|
+
/** Map a Go source path to its sibling `_test.go` file. */
|
|
59
|
+
resolveTestPath(srcRelPath: string): string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Rust conventions (Cargo integration tests):
|
|
63
|
+
* crate/src/foo.rs → crate/tests/foo.rs
|
|
64
|
+
*/
|
|
65
|
+
export declare class RustTestPathResolver implements TestPathResolver {
|
|
66
|
+
/** Registry key. */
|
|
67
|
+
readonly name = "rust";
|
|
68
|
+
constructor();
|
|
69
|
+
/** Map a Rust source path to its `tests/` integration-test counterpart. */
|
|
70
|
+
resolveTestPath(srcRelPath: string): string;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=test-path-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-path-resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/test-path-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,mBAAmB;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;IACjF,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC7B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5C,6DAA6D;IAC7D,gBAAgB,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;CACxE;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,oBAAoB;IACpB,QAAQ,CAAC,IAAI,gBAAgB;;IAI7B,mEAAmE;IACnE,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAW9C;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,gBAAgB;IAC3D,oBAAoB;IACpB,QAAQ,CAAC,IAAI,YAAY;;IAIzB,uEAAuE;IACvE,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAe9C;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,gBAAgB;IACvD,oBAAoB;IACpB,QAAQ,CAAC,IAAI,QAAQ;;IAIrB,2DAA2D;IAC3D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAM9C;AAED;;;GAGG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IACzD,oBAAoB;IACpB,QAAQ,CAAC,IAAI,UAAU;;IAIvB,2EAA2E;IAC3E,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;CAY9C"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable test-path resolution for the test-location evaluator.
|
|
3
|
+
*
|
|
4
|
+
* Each implementation maps a source file path to its conventional test file path
|
|
5
|
+
* for one language, so the same evaluator works across TypeScript, Python, Go, and
|
|
6
|
+
* Rust projects. Resolvers are registered on the host and selected per rule via
|
|
7
|
+
* `evaluator.config.resolver`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* TypeScript conventions:
|
|
11
|
+
* src/foo/bar.ts → tests/foo/bar.test.ts
|
|
12
|
+
* packages/core/src/foo/bar.ts → packages/core/tests/foo/bar.test.ts
|
|
13
|
+
*/
|
|
14
|
+
export class TypeScriptTestPathResolver {
|
|
15
|
+
/** Registry key. */
|
|
16
|
+
name = 'typescript';
|
|
17
|
+
constructor() { }
|
|
18
|
+
/** Map a TS/JS source path to its `tests/…test.ts` counterpart. */
|
|
19
|
+
resolveTestPath(srcRelPath) {
|
|
20
|
+
if (srcRelPath.includes('.test.') || srcRelPath.includes('.spec.'))
|
|
21
|
+
return srcRelPath;
|
|
22
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
23
|
+
if (srcIdx !== -1) {
|
|
24
|
+
const pkg = srcRelPath.slice(0, srcIdx);
|
|
25
|
+
const rel = srcRelPath.slice(srcIdx + '/src/'.length).replace(/\.(ts|tsx|js|jsx)$/, '.test.ts');
|
|
26
|
+
return `${pkg}/tests/${rel}`;
|
|
27
|
+
}
|
|
28
|
+
const withoutExt = srcRelPath.replace(/\.(ts|tsx|js|jsx)$/, '');
|
|
29
|
+
return `tests/${withoutExt.replace(/^src\//, '')}.test.ts`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Python conventions (pytest):
|
|
34
|
+
* src/foo/bar.py → tests/foo/test_bar.py
|
|
35
|
+
*/
|
|
36
|
+
export class PythonTestPathResolver {
|
|
37
|
+
/** Registry key. */
|
|
38
|
+
name = 'python';
|
|
39
|
+
constructor() { }
|
|
40
|
+
/** Map a Python source path to its `tests/…/test_*.py` counterpart. */
|
|
41
|
+
resolveTestPath(srcRelPath) {
|
|
42
|
+
if (!srcRelPath)
|
|
43
|
+
throw new Error('empty source path');
|
|
44
|
+
if (srcRelPath.endsWith('_test.py') || srcRelPath.includes('/test_') || srcRelPath.startsWith('test_')) {
|
|
45
|
+
return srcRelPath;
|
|
46
|
+
}
|
|
47
|
+
if (srcRelPath.startsWith('tests/'))
|
|
48
|
+
return srcRelPath;
|
|
49
|
+
if (!srcRelPath.endsWith('.py'))
|
|
50
|
+
throw new Error(`unsupported extension for python resolver: ${srcRelPath}`);
|
|
51
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
52
|
+
if (srcIdx !== -1) {
|
|
53
|
+
const pkg = srcRelPath.slice(0, srcIdx);
|
|
54
|
+
return `${pkg}/tests/${testify(srcRelPath.slice(srcIdx + '/src/'.length))}`;
|
|
55
|
+
}
|
|
56
|
+
if (srcRelPath.startsWith('src/'))
|
|
57
|
+
return `tests/${testify(srcRelPath.slice(4))}`;
|
|
58
|
+
return `tests/${testify(srcRelPath)}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Go conventions:
|
|
63
|
+
* foo/bar.go → foo/bar_test.go (sibling)
|
|
64
|
+
*/
|
|
65
|
+
export class GoTestPathResolver {
|
|
66
|
+
/** Registry key. */
|
|
67
|
+
name = 'go';
|
|
68
|
+
constructor() { }
|
|
69
|
+
/** Map a Go source path to its sibling `_test.go` file. */
|
|
70
|
+
resolveTestPath(srcRelPath) {
|
|
71
|
+
if (!srcRelPath)
|
|
72
|
+
throw new Error('empty source path');
|
|
73
|
+
if (srcRelPath.endsWith('_test.go'))
|
|
74
|
+
return srcRelPath;
|
|
75
|
+
if (!srcRelPath.endsWith('.go'))
|
|
76
|
+
throw new Error(`unsupported extension for go resolver: ${srcRelPath}`);
|
|
77
|
+
return srcRelPath.replace(/\.go$/, '_test.go');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rust conventions (Cargo integration tests):
|
|
82
|
+
* crate/src/foo.rs → crate/tests/foo.rs
|
|
83
|
+
*/
|
|
84
|
+
export class RustTestPathResolver {
|
|
85
|
+
/** Registry key. */
|
|
86
|
+
name = 'rust';
|
|
87
|
+
constructor() { }
|
|
88
|
+
/** Map a Rust source path to its `tests/` integration-test counterpart. */
|
|
89
|
+
resolveTestPath(srcRelPath) {
|
|
90
|
+
if (!srcRelPath)
|
|
91
|
+
throw new Error('empty source path');
|
|
92
|
+
if (srcRelPath.startsWith('tests/') || srcRelPath.includes('/tests/'))
|
|
93
|
+
return srcRelPath;
|
|
94
|
+
if (!srcRelPath.endsWith('.rs'))
|
|
95
|
+
throw new Error(`unsupported extension for rust resolver: ${srcRelPath}`);
|
|
96
|
+
const srcIdx = srcRelPath.indexOf('/src/');
|
|
97
|
+
if (srcIdx !== -1) {
|
|
98
|
+
const crate = srcRelPath.slice(0, srcIdx);
|
|
99
|
+
return `${crate}/tests/${srcRelPath.slice(srcIdx + '/src/'.length)}`;
|
|
100
|
+
}
|
|
101
|
+
if (srcRelPath.startsWith('src/'))
|
|
102
|
+
return `tests/${srcRelPath.slice(4)}`;
|
|
103
|
+
return `tests/${srcRelPath}`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/** Prefix the basename of a Python source path with `test_`. */
|
|
107
|
+
function testify(rel) {
|
|
108
|
+
const lastSlash = rel.lastIndexOf('/');
|
|
109
|
+
const dir = lastSlash >= 0 ? rel.slice(0, lastSlash + 1) : '';
|
|
110
|
+
const base = lastSlash >= 0 ? rel.slice(lastSlash + 1) : rel;
|
|
111
|
+
return `${dir}test_${base}`;
|
|
112
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -49,6 +49,17 @@ export interface ConstraintRuleFile {
|
|
|
49
49
|
/** Rule definitions. */
|
|
50
50
|
rules: ConstraintRule[];
|
|
51
51
|
}
|
|
52
|
+
/** Relative module paths a preset contributes per capability kind. */
|
|
53
|
+
export interface PresetExtensions {
|
|
54
|
+
/** Test-path resolver module paths. */
|
|
55
|
+
resolvers?: string[];
|
|
56
|
+
/** Evaluator module paths. */
|
|
57
|
+
evaluators?: string[];
|
|
58
|
+
/** Fixer module paths. */
|
|
59
|
+
fixers?: string[];
|
|
60
|
+
/** Formatter module paths. */
|
|
61
|
+
formatters?: string[];
|
|
62
|
+
}
|
|
52
63
|
/** Preset definition that composes category folders or other presets. */
|
|
53
64
|
export interface PresetDefinition {
|
|
54
65
|
/** Preset name. */
|
|
@@ -63,6 +74,8 @@ export interface PresetDefinition {
|
|
|
63
74
|
mode: FixMode;
|
|
64
75
|
};
|
|
65
76
|
}>;
|
|
77
|
+
/** Custom capability modules contributed by this preset (opt-in to load). */
|
|
78
|
+
extensions?: PresetExtensions;
|
|
66
79
|
}
|
|
67
80
|
/** Candidate fix emitted by an evaluator or fixer. */
|
|
68
81
|
export interface Fix {
|
|
@@ -79,6 +92,15 @@ export interface Fix {
|
|
|
79
92
|
/** Whether this fix may be applied automatically. */
|
|
80
93
|
mode: Exclude<FixMode, 'none'>;
|
|
81
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* What a finding represents.
|
|
97
|
+
*
|
|
98
|
+
* - `violation`: the rule ran and the project breached its policy (the default).
|
|
99
|
+
* - `error`: the rule could not run — a misconfiguration or runtime fault in the
|
|
100
|
+
* evaluator itself. These are not policy breaches and callers may surface them
|
|
101
|
+
* separately (e.g. "rule misconfigured") rather than as project violations.
|
|
102
|
+
*/
|
|
103
|
+
export type FindingKind = 'violation' | 'error';
|
|
82
104
|
/** Finding emitted by a constraint rule. */
|
|
83
105
|
export interface ConstraintFinding {
|
|
84
106
|
/** Rule identifier. */
|
|
@@ -95,6 +117,8 @@ export interface ConstraintFinding {
|
|
|
95
117
|
column?: number;
|
|
96
118
|
/** Machine-readable evaluator/source code. */
|
|
97
119
|
code?: string;
|
|
120
|
+
/** Whether this is a policy violation or an evaluator error. Absent means `violation`. */
|
|
121
|
+
kind?: FindingKind;
|
|
98
122
|
}
|
|
99
123
|
/** Aggregate result returned by a rule evaluator. */
|
|
100
124
|
export interface RuleEvaluationResult {
|
|
@@ -214,5 +238,11 @@ export declare const PresetDefinitionSchema: z.ZodObject<{
|
|
|
214
238
|
}>;
|
|
215
239
|
}, z.core.$strip>>;
|
|
216
240
|
}, z.core.$strip>>>;
|
|
241
|
+
extensions: z.ZodOptional<z.ZodObject<{
|
|
242
|
+
resolvers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
243
|
+
evaluators: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
244
|
+
fixers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
245
|
+
formatters: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
246
|
+
}, z.core.$strip>>;
|
|
217
247
|
}, z.core.$strip>;
|
|
218
248
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,mDAAmD;AACnD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAExD,+CAA+C;AAC/C,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AAElD,qDAAqD;AACrD,MAAM,WAAW,mBAAmB;IAChC,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC3B,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE,YAAY,CAAC;IACvB,+BAA+B;IAC/B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC1B,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;IACd,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IAC/B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,wBAAwB;IACxB,KAAK,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,yEAAyE;AACzE,MAAM,WAAW,gBAAgB;IAC7B,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,GAAG,CAAC,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -46,4 +46,12 @@ export const PresetDefinitionSchema = z.object({
|
|
|
46
46
|
overrides: z
|
|
47
47
|
.record(z.string(), z.object({ fix: z.object({ mode: z.enum(['none', 'suggest', 'auto']) }).optional() }))
|
|
48
48
|
.optional(),
|
|
49
|
+
extensions: z
|
|
50
|
+
.object({
|
|
51
|
+
resolvers: z.array(z.string()).optional(),
|
|
52
|
+
evaluators: z.array(z.string()).optional(),
|
|
53
|
+
fixers: z.array(z.string()).optional(),
|
|
54
|
+
formatters: z.array(z.string()).optional(),
|
|
55
|
+
})
|
|
56
|
+
.optional(),
|
|
49
57
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-rule-engine",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-rule-engine-v<version> && git push --tags' && exit 1"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@gobing-ai/ts-ai-runner": "^0.2.
|
|
51
|
-
"@gobing-ai/ts-runtime": "^0.2.
|
|
50
|
+
"@gobing-ai/ts-ai-runner": "^0.2.7",
|
|
51
|
+
"@gobing-ai/ts-runtime": "^0.2.7",
|
|
52
52
|
"yaml": "^2.7.0",
|
|
53
53
|
"zod": "^4.1.0"
|
|
54
54
|
},
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import type { RuleEngineHost } from '../host/rule-engine-host';
|
|
3
|
+
|
|
4
|
+
/** A capability kind a preset extension can contribute. */
|
|
5
|
+
export type ExtensionKind = 'resolvers' | 'evaluators' | 'fixers' | 'formatters';
|
|
6
|
+
|
|
7
|
+
/** A single extension module reference, resolved to an absolute path. */
|
|
8
|
+
export interface ExtensionRef {
|
|
9
|
+
/** Capability registry the module registers into. */
|
|
10
|
+
readonly kind: ExtensionKind;
|
|
11
|
+
/** Absolute path to the module to import. */
|
|
12
|
+
readonly absPath: string;
|
|
13
|
+
/** Name of the preset that declared this extension (for diagnostics). */
|
|
14
|
+
readonly presetName: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Options controlling preset-extension loading. */
|
|
18
|
+
export interface LoadExtensionsOptions {
|
|
19
|
+
/**
|
|
20
|
+
* Whether to actually import extension modules. Defaults to `false`: loading
|
|
21
|
+
* arbitrary code referenced by a preset is a trust decision the caller must
|
|
22
|
+
* make explicitly. When refs exist and this is false, loading throws.
|
|
23
|
+
*/
|
|
24
|
+
allowExtensions?: boolean;
|
|
25
|
+
/** Optional sink for non-fatal warnings (e.g. built-in overrides). */
|
|
26
|
+
logger?: { warn: (message: string) => void };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Host registries that can receive extension capabilities (fixers live on the engine, not the host). */
|
|
30
|
+
const HOST_REGISTRY_BY_KIND: Partial<Record<ExtensionKind, 'resolvers' | 'evaluators' | 'formatters'>> = {
|
|
31
|
+
resolvers: 'resolvers',
|
|
32
|
+
evaluators: 'evaluators',
|
|
33
|
+
formatters: 'formatters',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Collect extension refs declared by a preset's `extensions` block.
|
|
38
|
+
*
|
|
39
|
+
* Paths are resolved relative to the preset file's directory. Use the returned
|
|
40
|
+
* refs with {@link loadExtensionsIntoHost}.
|
|
41
|
+
*/
|
|
42
|
+
export function collectPresetExtensions(
|
|
43
|
+
presetName: string,
|
|
44
|
+
presetDir: string,
|
|
45
|
+
extensions: Partial<Record<ExtensionKind, string[] | undefined>> | undefined,
|
|
46
|
+
): ExtensionRef[] {
|
|
47
|
+
if (extensions === undefined) return [];
|
|
48
|
+
const refs: ExtensionRef[] = [];
|
|
49
|
+
for (const kind of ['resolvers', 'evaluators', 'fixers', 'formatters'] as ExtensionKind[]) {
|
|
50
|
+
for (const path of extensions[kind] ?? []) {
|
|
51
|
+
refs.push({ kind, presetName, absPath: resolve(presetDir, path) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return refs;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Import each extension module and register its export on the matching host
|
|
59
|
+
* registry.
|
|
60
|
+
*
|
|
61
|
+
* A module must default-export (or named-export `extension`) an object with a
|
|
62
|
+
* `name: string` and the capability implementation. Loading is gated by
|
|
63
|
+
* {@link LoadExtensionsOptions.allowExtensions}; when refs are present but
|
|
64
|
+
* loading is not allowed, this throws so the requirement is never silently dropped.
|
|
65
|
+
*
|
|
66
|
+
* @throws When extensions are present but `allowExtensions` is not true, or when
|
|
67
|
+
* a module cannot be imported or lacks a valid `name`.
|
|
68
|
+
*/
|
|
69
|
+
export async function loadExtensionsIntoHost(
|
|
70
|
+
host: RuleEngineHost,
|
|
71
|
+
refs: readonly ExtensionRef[],
|
|
72
|
+
options: LoadExtensionsOptions = {},
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
if (refs.length === 0) return;
|
|
75
|
+
if (options.allowExtensions !== true) {
|
|
76
|
+
const first = refs[0] as ExtensionRef;
|
|
77
|
+
throw new Error(
|
|
78
|
+
`preset "${first.presetName}" declares ${first.kind} extension "${first.absPath}", but extensions are disabled — pass allowExtensions: true to load preset extension modules`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const ref of refs) {
|
|
83
|
+
const moduleExports = (await import(ref.absPath)) as Record<string, unknown>;
|
|
84
|
+
const candidate = moduleExports.default ?? moduleExports.extension;
|
|
85
|
+
if (
|
|
86
|
+
candidate === null ||
|
|
87
|
+
typeof candidate !== 'object' ||
|
|
88
|
+
typeof (candidate as { name?: unknown }).name !== 'string'
|
|
89
|
+
) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`preset "${ref.presetName}" extension "${ref.absPath}" must export an object with a string "name"`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const name = (candidate as { name: string }).name;
|
|
95
|
+
const registryKey = HOST_REGISTRY_BY_KIND[ref.kind];
|
|
96
|
+
if (registryKey === undefined) {
|
|
97
|
+
throw new Error(`preset "${ref.presetName}" ${ref.kind} extensions are not supported`);
|
|
98
|
+
}
|
|
99
|
+
const registry = host[registryKey] as unknown as {
|
|
100
|
+
register: (name: string, impl: unknown, origin: 'builtin' | 'extension') => void;
|
|
101
|
+
has?: (name: string) => boolean;
|
|
102
|
+
};
|
|
103
|
+
if (options.logger && registry.has?.(name)) {
|
|
104
|
+
options.logger.warn(`preset "${ref.presetName}" ${ref.kind} extension overrides existing "${name}"`);
|
|
105
|
+
}
|
|
106
|
+
registry.register(name, candidate, 'extension');
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/config/loader.ts
CHANGED
|
@@ -63,9 +63,12 @@ export async function loadRuleFile(filePath: string): Promise<ConstraintRule[]>
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
async function loadPresetEntry(merged: MergedRoots, entry: string, seen: Set<string>): Promise<ConstraintRule[]> {
|
|
66
|
-
// Sub-preset reference — recurse
|
|
66
|
+
// Sub-preset reference — recurse, erroring on a genuine cycle.
|
|
67
67
|
const presetPath = findMergedPreset(merged, entry);
|
|
68
|
-
if (presetPath !== null
|
|
68
|
+
if (presetPath !== null) {
|
|
69
|
+
if (seen.has(entry)) {
|
|
70
|
+
throw new Error(`Circular preset dependency detected: ${[...seen, entry].join(' → ')}`);
|
|
71
|
+
}
|
|
69
72
|
seen.add(entry);
|
|
70
73
|
const preset = PresetDefinitionSchema.safeParse(await readStructuredFile(presetPath));
|
|
71
74
|
if (preset.success) {
|
|
@@ -200,7 +203,8 @@ function normalizeFileRules(file: ConstraintRuleFile, sourceDir: string): Constr
|
|
|
200
203
|
...rule,
|
|
201
204
|
severity: rule.severity ?? file.severity ?? 'error',
|
|
202
205
|
include: rule.include ?? file.include,
|
|
203
|
-
|
|
206
|
+
// File-level excludes always apply; a rule's own excludes add to (not replace) them.
|
|
207
|
+
exclude: mergeExcludes(file.exclude, rule.exclude),
|
|
204
208
|
},
|
|
205
209
|
{},
|
|
206
210
|
sourceDir,
|
|
@@ -208,6 +212,12 @@ function normalizeFileRules(file: ConstraintRuleFile, sourceDir: string): Constr
|
|
|
208
212
|
);
|
|
209
213
|
}
|
|
210
214
|
|
|
215
|
+
/** Union of file-level and rule-level excludes, de-duplicated. Returns undefined when both empty. */
|
|
216
|
+
function mergeExcludes(fileExclude?: string[], ruleExclude?: string[]): string[] | undefined {
|
|
217
|
+
if (fileExclude === undefined && ruleExclude === undefined) return undefined;
|
|
218
|
+
return [...new Set([...(fileExclude ?? []), ...(ruleExclude ?? [])])];
|
|
219
|
+
}
|
|
220
|
+
|
|
211
221
|
function normalizeRule(rule: ConstraintRule, _defaults: Partial<ConstraintRule>, _sourceDir: string): ConstraintRule {
|
|
212
222
|
return {
|
|
213
223
|
...rule,
|
package/src/engine.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import {
|
|
3
|
+
applyFixes as applyFixesImpl,
|
|
4
|
+
builtInFixers,
|
|
5
|
+
type EffectiveFix,
|
|
6
|
+
FIX_MODE_RANK,
|
|
7
|
+
type FixApplicationResult,
|
|
8
|
+
type RuleFixerProvider,
|
|
9
|
+
} from './fixers/fixers';
|
|
2
10
|
import { registerBuiltins } from './host/builtins';
|
|
3
11
|
import { RuleEngineHost } from './host/rule-engine-host';
|
|
4
|
-
import type { ConstraintFinding, ConstraintRule, RuleEngineResult, RuleEvaluator } from './types';
|
|
12
|
+
import type { ConstraintFinding, ConstraintRule, Fix, FixMode, RuleEngineResult, RuleEvaluator } from './types';
|
|
5
13
|
import { createFinding } from './types';
|
|
6
14
|
|
|
7
15
|
/** Options for constructing a RuleEngine. */
|
|
@@ -17,9 +25,13 @@ export class RuleEngine {
|
|
|
17
25
|
/** Capability host used by this engine. */
|
|
18
26
|
readonly host: RuleEngineHost;
|
|
19
27
|
|
|
28
|
+
/** Fixer providers keyed by evaluator type. */
|
|
29
|
+
private readonly fixers: Map<string, RuleFixerProvider>;
|
|
30
|
+
|
|
20
31
|
constructor(options: RuleEngineOptions = {}) {
|
|
21
32
|
this.host = options.host ?? new RuleEngineHost();
|
|
22
33
|
registerBuiltins(this.host, options.processExecutor);
|
|
34
|
+
this.fixers = builtInFixers(this.host, options.processExecutor);
|
|
23
35
|
}
|
|
24
36
|
|
|
25
37
|
/** Register or replace an evaluator. */
|
|
@@ -30,7 +42,7 @@ export class RuleEngine {
|
|
|
30
42
|
/** Evaluate all enabled rules against a working directory. */
|
|
31
43
|
async evaluate(rules: ConstraintRule[], workdir: string): Promise<RuleEngineResult> {
|
|
32
44
|
const findings: ConstraintFinding[] = [];
|
|
33
|
-
const fixes = [];
|
|
45
|
+
const fixes: Fix[] = [];
|
|
34
46
|
for (const rule of rules) {
|
|
35
47
|
if (rule.enabled === false) continue;
|
|
36
48
|
try {
|
|
@@ -41,10 +53,95 @@ export class RuleEngine {
|
|
|
41
53
|
findings.push(
|
|
42
54
|
createFinding(rule, error instanceof Error ? error.message : String(error), null, {
|
|
43
55
|
code: `evaluator:${rule.evaluator.type}`,
|
|
56
|
+
kind: 'error',
|
|
44
57
|
}),
|
|
45
58
|
);
|
|
46
59
|
}
|
|
47
60
|
}
|
|
48
61
|
return { findings, fixes };
|
|
49
62
|
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Evaluate all enabled rules and collect candidate fixes.
|
|
66
|
+
*
|
|
67
|
+
* For each rule with findings and a non-none fix mode, looks up the fixer
|
|
68
|
+
* provider by evaluator type and calls `createFixes`. The effective fix mode
|
|
69
|
+
* is the minimum of the rule's configured mode and `maxFixMode`.
|
|
70
|
+
*
|
|
71
|
+
* @param rules - Normalized rule definitions to evaluate.
|
|
72
|
+
* @param workdir - Working directory to scan.
|
|
73
|
+
* @param maxFixMode - Highest fix authority requested by the caller.
|
|
74
|
+
* @returns Findings plus fixes allowed by the requested authority.
|
|
75
|
+
*/
|
|
76
|
+
async evaluateWithFixes(
|
|
77
|
+
rules: ConstraintRule[],
|
|
78
|
+
workdir: string,
|
|
79
|
+
maxFixMode: FixMode = 'auto',
|
|
80
|
+
): Promise<RuleEngineResult> {
|
|
81
|
+
const findings: ConstraintFinding[] = [];
|
|
82
|
+
const fixes: Fix[] = [];
|
|
83
|
+
|
|
84
|
+
for (const rule of rules) {
|
|
85
|
+
if (rule.enabled === false) continue;
|
|
86
|
+
|
|
87
|
+
let ruleFindings: ConstraintFinding[] = [];
|
|
88
|
+
let ruleEvalFixes: Fix[] = [];
|
|
89
|
+
try {
|
|
90
|
+
const result = await this.host.evaluators.get(rule.evaluator.type).evaluate(rule, { rule, workdir });
|
|
91
|
+
ruleFindings = result.findings;
|
|
92
|
+
ruleEvalFixes = result.fixes;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
ruleFindings = [
|
|
95
|
+
createFinding(rule, error instanceof Error ? error.message : String(error), null, {
|
|
96
|
+
code: `evaluator:${rule.evaluator.type}`,
|
|
97
|
+
kind: 'error',
|
|
98
|
+
}),
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
findings.push(...ruleFindings);
|
|
103
|
+
fixes.push(...ruleEvalFixes);
|
|
104
|
+
|
|
105
|
+
const ruleMode = rule.fix?.mode ?? 'none';
|
|
106
|
+
const effectiveMode = effectiveFixMode(ruleMode, maxFixMode);
|
|
107
|
+
|
|
108
|
+
if (effectiveMode !== 'none' && ruleFindings.length > 0) {
|
|
109
|
+
const provider = this.fixers.get(rule.evaluator.type);
|
|
110
|
+
if (provider) {
|
|
111
|
+
const effectiveFix: EffectiveFix = {
|
|
112
|
+
mode: effectiveMode,
|
|
113
|
+
...(rule.fix?.replacement !== undefined ? { replacement: rule.fix.replacement } : {}),
|
|
114
|
+
...(rule.fix?.params !== undefined ? { params: rule.fix.params } : {}),
|
|
115
|
+
};
|
|
116
|
+
const providerFixes = await provider.createFixes({
|
|
117
|
+
rule,
|
|
118
|
+
context: { rule, workdir },
|
|
119
|
+
findings: ruleFindings,
|
|
120
|
+
fix: effectiveFix,
|
|
121
|
+
});
|
|
122
|
+
fixes.push(...providerFixes);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { findings, fixes };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Apply or preview candidate byte-range fixes.
|
|
132
|
+
*
|
|
133
|
+
* @param workdir - Working directory that fix file paths are relative to.
|
|
134
|
+
* @param fixes - Fixes to apply.
|
|
135
|
+
* @param dryRun - When true, return a diff without writing files.
|
|
136
|
+
* @returns Application details and optional diff.
|
|
137
|
+
*/
|
|
138
|
+
async applyFixes(workdir: string, fixes: readonly Fix[], dryRun = false): Promise<FixApplicationResult> {
|
|
139
|
+
return applyFixesImpl(workdir, fixes, dryRun);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Return the lower-authority mode between what the rule requests and what the caller allows. */
|
|
144
|
+
function effectiveFixMode(ruleMode: FixMode, requestedMode: FixMode): FixMode {
|
|
145
|
+
if (requestedMode === 'none' || ruleMode === 'none') return 'none';
|
|
146
|
+
return FIX_MODE_RANK[ruleMode] <= FIX_MODE_RANK[requestedMode] ? ruleMode : requestedMode;
|
|
50
147
|
}
|