@kernlang/review 3.3.9 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/call-graph.d.ts +10 -0
- package/dist/call-graph.js +138 -9
- package/dist/call-graph.js.map +1 -1
- package/dist/concept-rules/auth-drift.js +2 -0
- package/dist/concept-rules/auth-drift.js.map +1 -1
- package/dist/concept-rules/auth-propagation-drift.js +2 -0
- package/dist/concept-rules/auth-propagation-drift.js.map +1 -1
- package/dist/concept-rules/body-shape-drift.d.ts +1 -1
- package/dist/concept-rules/body-shape-drift.js +5 -3
- package/dist/concept-rules/body-shape-drift.js.map +1 -1
- package/dist/concept-rules/contract-drift.js +3 -1
- package/dist/concept-rules/contract-drift.js.map +1 -1
- package/dist/concept-rules/contract-method-drift.js +2 -0
- package/dist/concept-rules/contract-method-drift.js.map +1 -1
- package/dist/concept-rules/index.js +2 -33
- package/dist/concept-rules/index.js.map +1 -1
- package/dist/concept-rules/request-validation-drift.js +3 -0
- package/dist/concept-rules/request-validation-drift.js.map +1 -1
- package/dist/concept-rules/root-cause.d.ts +4 -0
- package/dist/concept-rules/root-cause.js +31 -0
- package/dist/concept-rules/root-cause.js.map +1 -0
- package/dist/concept-rules/unbounded-collection-query.js +2 -0
- package/dist/concept-rules/unbounded-collection-query.js.map +1 -1
- package/dist/concept-rules/unhandled-api-error-shape.js +2 -0
- package/dist/concept-rules/unhandled-api-error-shape.js.map +1 -1
- package/dist/default-export.d.ts +41 -0
- package/dist/default-export.js +76 -0
- package/dist/default-export.js.map +1 -0
- package/dist/eval.d.ts +67 -0
- package/dist/eval.js +177 -0
- package/dist/eval.js.map +1 -0
- package/dist/file-context.js +32 -13
- package/dist/file-context.js.map +1 -1
- package/dist/file-role.d.ts +6 -0
- package/dist/file-role.js +27 -0
- package/dist/file-role.js.map +1 -1
- package/dist/framework-seeds.d.ts +46 -0
- package/dist/framework-seeds.js +245 -0
- package/dist/framework-seeds.js.map +1 -0
- package/dist/git-env.d.ts +1 -0
- package/dist/git-env.js +25 -0
- package/dist/git-env.js.map +1 -0
- package/dist/graph.js +246 -21
- package/dist/graph.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +200 -56
- package/dist/index.js.map +1 -1
- package/dist/mappers/ts-concepts.js +87 -20
- package/dist/mappers/ts-concepts.js.map +1 -1
- package/dist/path-canonical.d.ts +34 -0
- package/dist/path-canonical.js +85 -0
- package/dist/path-canonical.js.map +1 -0
- package/dist/policy.d.ts +22 -0
- package/dist/policy.js +47 -0
- package/dist/policy.js.map +1 -0
- package/dist/project-context.d.ts +135 -0
- package/dist/project-context.js +563 -0
- package/dist/project-context.js.map +1 -0
- package/dist/public-api.d.ts +21 -0
- package/dist/public-api.js +17 -2
- package/dist/public-api.js.map +1 -1
- package/dist/reporter.js +22 -0
- package/dist/reporter.js.map +1 -1
- package/dist/rule-quality.d.ts +58 -0
- package/dist/rule-quality.js +357 -0
- package/dist/rule-quality.js.map +1 -0
- package/dist/rules/dead-code.d.ts +2 -2
- package/dist/rules/dead-code.js +88 -4
- package/dist/rules/dead-code.js.map +1 -1
- package/dist/rules/index.d.ts +22 -0
- package/dist/rules/index.js +32 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/kern-source.d.ts +4 -0
- package/dist/rules/kern-source.js +183 -0
- package/dist/rules/kern-source.js.map +1 -1
- package/dist/rules/react.js +52 -3
- package/dist/rules/react.js.map +1 -1
- package/dist/rules/suggest-kern-primitive.js +0 -1
- package/dist/rules/suggest-kern-primitive.js.map +1 -1
- package/dist/semantic-diff.js +2 -0
- package/dist/semantic-diff.js.map +1 -1
- package/dist/suppression/apply-suppression.js +2 -0
- package/dist/suppression/apply-suppression.js.map +1 -1
- package/dist/suppression/parse-directives.d.ts +13 -5
- package/dist/suppression/parse-directives.js +62 -8
- package/dist/suppression/parse-directives.js.map +1 -1
- package/dist/suppression/types.d.ts +9 -0
- package/dist/suppression/types.js +6 -1
- package/dist/suppression/types.js.map +1 -1
- package/dist/taint-crossfile.js +15 -8
- package/dist/taint-crossfile.js.map +1 -1
- package/dist/telemetry.d.ts +126 -0
- package/dist/telemetry.js +303 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +165 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createFingerprint } from '../types.js';
|
|
10
10
|
import { API_PATH_RE, CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findHighConfidenceRouteForMethod, findMatchingRouteForMethod, normalizeClientUrl, } from './cross-stack-utils.js';
|
|
11
|
+
import { apiCallRootCause, routeRootCause } from './root-cause.js';
|
|
11
12
|
const GUARD_BODY_METHODS = new Set(['POST']);
|
|
12
13
|
const AUDIT_BODY_METHODS = new Set(['POST', 'PUT', 'PATCH']);
|
|
13
14
|
export function requestValidationDrift(ctx) {
|
|
@@ -44,6 +45,7 @@ function backendUnvalidatedBodyFindings(ctx) {
|
|
|
44
45
|
primarySpan: node.primarySpan,
|
|
45
46
|
fingerprint: createFingerprint('request-validation-drift', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
46
47
|
confidence: node.confidence * 0.8,
|
|
48
|
+
rootCause: routeRootCause(node, method),
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
51
|
return findings;
|
|
@@ -89,6 +91,7 @@ function clientExtraFieldFindings(ctx) {
|
|
|
89
91
|
relatedSpans: [route.node.primarySpan],
|
|
90
92
|
fingerprint: createFingerprint('request-validation-drift', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
91
93
|
confidence: node.confidence * CROSS_STACK_HEURISTIC_CONFIDENCE,
|
|
94
|
+
rootCause: apiCallRootCause(node, normalized, node.payload.method, route.node),
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
97
|
return findings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-validation-drift.js","sourceRoot":"","sources":["../../src/concept-rules/request-validation-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,gCAAgC,EAChC,wBAAwB,EACxB,gCAAgC,EAChC,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"request-validation-drift.js","sourceRoot":"","sources":["../../src/concept-rules/request-validation-drift.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,WAAW,EACX,gCAAgC,EAChC,wBAAwB,EACxB,gCAAgC,EAChC,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AAC7C,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAE7D,MAAM,UAAU,sBAAsB,CAAC,GAAuB;IAC5D,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,QAAQ,CAAC,IAAI,CAAC,GAAG,8BAA8B,CAAC,GAAG,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,IAAI,CAAC,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,8BAA8B,CAAC,GAAuB;IAC7D,MAAM,QAAQ,GAAoB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,OAAO;YAAE,SAAS;QACnH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,EAAE,CAAC;QACtD,MAAM,WAAW,GAAG,GAAG,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,kBAAkB,CAAC;QAC7F,IAAI,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,SAAS;QAClD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QACnD,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,IAAI;YAAE,SAAS;QAC/C,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjH,SAAS;QACX,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,KAAK,IAAI;YAAE,SAAS;QAEtD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjF,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,0BAA0B;YAClC,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,WAAW,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,gCAAgC,MAAM,sEAAsE;YAC3J,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,WAAW,EAAE,iBAAiB,CAAC,0BAA0B,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YACjH,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,GAAG;YACjC,SAAS,EAAE,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC;SACxC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,wBAAwB,CAAC,GAAuB;IACvD,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9D,MAAM,YAAY,GAAG,wBAAwB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC/D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEzC,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,aAAa,GAAG,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC;IAExE,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,SAAS;QAC7G,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU;YAAE,SAAS;QACnF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QACnC,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,SAAS;QACzC,MAAM,UAAU,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC9C,IAAI,CAAC,UAAU;YAAE,SAAS;QAE1B,MAAM,KAAK,GACT,GAAG,CAAC,cAAc,KAAK,OAAO;YAC5B,CAAC,CAAC,0BAA0B,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC;YAC3E,CAAC,CAAC,gCAAgC,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QACtF,IAAI,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,YAAY;YAAE,SAAS;QACzD,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB;YAAE,SAAS;QAE5G,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAClE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QAC/E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEjC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,0BAA0B;YAClC,QAAQ,EAAE,SAAS;YACnB,QAAQ,EAAE,KAAK;YACf,OAAO,EAAE,gBAAgB,SAAS,SAAS,MAAM,kEAAkE,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,6EAA6E;YAClP,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,YAAY,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC;YACtC,WAAW,EAAE,iBAAiB,CAAC,0BAA0B,EAAE,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC;YACjH,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,gCAAgC;YAC9D,SAAS,EAAE,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC;SAC/E,CAAC,CAAC;IACL,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ConceptNode } from '@kernlang/core';
|
|
2
|
+
import type { RootCause } from '../types.js';
|
|
3
|
+
export declare function apiCallRootCause(clientNode: ConceptNode, normalizedPath: string, method?: string, routeNode?: ConceptNode): RootCause;
|
|
4
|
+
export declare function routeRootCause(routeNode: ConceptNode, method?: string): RootCause;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function methodKey(method, fallback = 'GET') {
|
|
2
|
+
return (method ?? fallback).toUpperCase();
|
|
3
|
+
}
|
|
4
|
+
export function apiCallRootCause(clientNode, normalizedPath, method, routeNode) {
|
|
5
|
+
const httpMethod = methodKey(method);
|
|
6
|
+
const routePart = routeNode ? ` route=${routeNode.id}` : '';
|
|
7
|
+
return {
|
|
8
|
+
kind: 'api-call',
|
|
9
|
+
key: `api-call client=${clientNode.id} method=${httpMethod} path=${normalizedPath}${routePart}`,
|
|
10
|
+
facets: {
|
|
11
|
+
clientNodeId: clientNode.id,
|
|
12
|
+
method: httpMethod,
|
|
13
|
+
path: normalizedPath,
|
|
14
|
+
...(routeNode ? { routeNodeId: routeNode.id } : {}),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function routeRootCause(routeNode, method) {
|
|
19
|
+
const name = routeNode.payload.kind === 'entrypoint' ? routeNode.payload.name : routeNode.id;
|
|
20
|
+
const httpMethod = methodKey(method, 'ANY');
|
|
21
|
+
return {
|
|
22
|
+
kind: 'route',
|
|
23
|
+
key: `route node=${routeNode.id} method=${httpMethod} path=${name}`,
|
|
24
|
+
facets: {
|
|
25
|
+
routeNodeId: routeNode.id,
|
|
26
|
+
method: httpMethod,
|
|
27
|
+
path: name,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=root-cause.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-cause.js","sourceRoot":"","sources":["../../src/concept-rules/root-cause.ts"],"names":[],"mappings":"AAGA,SAAS,SAAS,CAAC,MAA0B,EAAE,QAAQ,GAAG,KAAK;IAC7D,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,UAAuB,EACvB,cAAsB,EACtB,MAAe,EACf,SAAuB;IAEvB,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,UAAU,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,mBAAmB,UAAU,CAAC,EAAE,WAAW,UAAU,SAAS,cAAc,GAAG,SAAS,EAAE;QAC/F,MAAM,EAAE;YACN,YAAY,EAAE,UAAU,CAAC,EAAE;YAC3B,MAAM,EAAE,UAAU;YAClB,IAAI,EAAE,cAAc;YACpB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpD;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAsB,EAAE,MAAe;IACpE,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;IAC7F,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC5C,OAAO;QACL,IAAI,EAAE,OAAO;QACb,GAAG,EAAE,cAAc,SAAS,CAAC,EAAE,WAAW,UAAU,SAAS,IAAI,EAAE;QACnE,MAAM,EAAE;YACN,WAAW,EAAE,SAAS,CAAC,EAAE;YACzB,MAAM,EAAE,UAAU;YAClB,IAAI,EAAE,IAAI;SACX;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createFingerprint } from '../types.js';
|
|
9
9
|
import { CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findHighConfidenceRouteForMethod, findMatchingRouteForMethod, normalizeClientUrl, } from './cross-stack-utils.js';
|
|
10
|
+
import { apiCallRootCause } from './root-cause.js';
|
|
10
11
|
const PAGINATION_QUERY_PARAMS = new Set(['limit', 'take', 'page', 'pageSize', 'perPage', 'cursor', 'offset', 'skip']);
|
|
11
12
|
export function unboundedCollectionQuery(ctx) {
|
|
12
13
|
if (!ctx.allConcepts || ctx.allConcepts.size === 0)
|
|
@@ -46,6 +47,7 @@ export function unboundedCollectionQuery(ctx) {
|
|
|
46
47
|
relatedSpans: [route.node.primarySpan],
|
|
47
48
|
fingerprint: createFingerprint('unbounded-collection-query', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
48
49
|
confidence: node.confidence * CROSS_STACK_HEURISTIC_CONFIDENCE,
|
|
50
|
+
rootCause: apiCallRootCause(node, normalized, node.payload.method, route.node),
|
|
49
51
|
});
|
|
50
52
|
}
|
|
51
53
|
return findings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unbounded-collection-query.js","sourceRoot":"","sources":["../../src/concept-rules/unbounded-collection-query.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,gCAAgC,EAChC,wBAAwB,EACxB,gCAAgC,EAChC,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"unbounded-collection-query.js","sourceRoot":"","sources":["../../src/concept-rules/unbounded-collection-query.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createFingerprint } from '../types.js';
|
|
9
9
|
import { CROSS_STACK_EXACT_CONFIDENCE, collectRoutesAcrossGraph, findHighConfidenceRouteForMethod, findMatchingRouteForMethod, normalizeClientUrl, } from './cross-stack-utils.js';
|
|
10
|
+
import { apiCallRootCause } from './root-cause.js';
|
|
10
11
|
const ERROR_STATUS_CODES = new Set([401, 403, 404, 422, 500]);
|
|
11
12
|
export function unhandledApiErrorShape(ctx) {
|
|
12
13
|
if (!ctx.allConcepts || ctx.allConcepts.size === 0)
|
|
@@ -47,6 +48,7 @@ export function unhandledApiErrorShape(ctx) {
|
|
|
47
48
|
relatedSpans: route?.node ? [route.node.primarySpan] : undefined,
|
|
48
49
|
fingerprint: createFingerprint('unhandled-api-error-shape', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
49
50
|
confidence: node.confidence * CROSS_STACK_EXACT_CONFIDENCE,
|
|
51
|
+
rootCause: apiCallRootCause(node, normalized, node.payload.method, route?.node),
|
|
50
52
|
});
|
|
51
53
|
}
|
|
52
54
|
return findings;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unhandled-api-error-shape.js","sourceRoot":"","sources":["../../src/concept-rules/unhandled-api-error-shape.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EACL,4BAA4B,EAC5B,wBAAwB,EACxB,gCAAgC,EAChC,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"unhandled-api-error-shape.js","sourceRoot":"","sources":["../../src/concept-rules/unhandled-api-error-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export resolver — bridges the gap between how seeds express
|
|
3
|
+
* "this file's default export is public" (`{filePath}#default`) and how
|
|
4
|
+
* the call graph indexes the same symbol internally.
|
|
5
|
+
*
|
|
6
|
+
* For `export default function Page() {}`, the call graph stores the
|
|
7
|
+
* function under its declaration name (`Page`). The framework seed
|
|
8
|
+
* (Next.js page convention, etc.) uses `default`. Without a bridge, the
|
|
9
|
+
* (filePath, 'Page') dead-export check looks up (filePath, 'default') in
|
|
10
|
+
* the seed map and misses — flagging Page as dead. This module returns
|
|
11
|
+
* the call-graph-internal name so the caller can also accept that.
|
|
12
|
+
*
|
|
13
|
+
* Step 9b wires this into the dead-export rule. Splitting it lets us
|
|
14
|
+
* test the resolver against every TypeScript default-export shape in
|
|
15
|
+
* isolation — there are five distinct cases and getting any one wrong
|
|
16
|
+
* silently re-introduces FPs.
|
|
17
|
+
*/
|
|
18
|
+
import { type SourceFile } from 'ts-morph';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the call-graph-internal name of a source file's default export.
|
|
21
|
+
*
|
|
22
|
+
* Returns the identifier the call graph would track the symbol under —
|
|
23
|
+
* `'Page'` for `export default function Page() {}`, `'x'` for
|
|
24
|
+
* `export default x` or `export { x as default }`, and `undefined` for
|
|
25
|
+
* anonymous defaults (`export default function () {}` / `export default 42`)
|
|
26
|
+
* or files with no default export at all.
|
|
27
|
+
*
|
|
28
|
+
* The five cases red-team #11 enumerated:
|
|
29
|
+
* 1. `export default function Page() {}` → `'Page'`
|
|
30
|
+
* 2. `export default class Page {}` → `'Page'`
|
|
31
|
+
* 3. `export default x` (where x is a local identifier) → `'x'`
|
|
32
|
+
* 4. `export { x as default }` → `'x'`
|
|
33
|
+
* 5. anonymous (`export default function () {}`, `export default 42`,
|
|
34
|
+
* `export default { ... }`) → undefined
|
|
35
|
+
*
|
|
36
|
+
* `undefined` callers should fall back to the literal `'default'` —
|
|
37
|
+
* that's how the call graph keys anonymous defaults internally
|
|
38
|
+
* (resolveDefaultExportBinding in call-graph.ts uses 'default' as the
|
|
39
|
+
* fallback name when the symbol carries no identifier).
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveDefaultExportName(sourceFile: SourceFile): string | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export resolver — bridges the gap between how seeds express
|
|
3
|
+
* "this file's default export is public" (`{filePath}#default`) and how
|
|
4
|
+
* the call graph indexes the same symbol internally.
|
|
5
|
+
*
|
|
6
|
+
* For `export default function Page() {}`, the call graph stores the
|
|
7
|
+
* function under its declaration name (`Page`). The framework seed
|
|
8
|
+
* (Next.js page convention, etc.) uses `default`. Without a bridge, the
|
|
9
|
+
* (filePath, 'Page') dead-export check looks up (filePath, 'default') in
|
|
10
|
+
* the seed map and misses — flagging Page as dead. This module returns
|
|
11
|
+
* the call-graph-internal name so the caller can also accept that.
|
|
12
|
+
*
|
|
13
|
+
* Step 9b wires this into the dead-export rule. Splitting it lets us
|
|
14
|
+
* test the resolver against every TypeScript default-export shape in
|
|
15
|
+
* isolation — there are five distinct cases and getting any one wrong
|
|
16
|
+
* silently re-introduces FPs.
|
|
17
|
+
*/
|
|
18
|
+
import { SyntaxKind } from 'ts-morph';
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the call-graph-internal name of a source file's default export.
|
|
21
|
+
*
|
|
22
|
+
* Returns the identifier the call graph would track the symbol under —
|
|
23
|
+
* `'Page'` for `export default function Page() {}`, `'x'` for
|
|
24
|
+
* `export default x` or `export { x as default }`, and `undefined` for
|
|
25
|
+
* anonymous defaults (`export default function () {}` / `export default 42`)
|
|
26
|
+
* or files with no default export at all.
|
|
27
|
+
*
|
|
28
|
+
* The five cases red-team #11 enumerated:
|
|
29
|
+
* 1. `export default function Page() {}` → `'Page'`
|
|
30
|
+
* 2. `export default class Page {}` → `'Page'`
|
|
31
|
+
* 3. `export default x` (where x is a local identifier) → `'x'`
|
|
32
|
+
* 4. `export { x as default }` → `'x'`
|
|
33
|
+
* 5. anonymous (`export default function () {}`, `export default 42`,
|
|
34
|
+
* `export default { ... }`) → undefined
|
|
35
|
+
*
|
|
36
|
+
* `undefined` callers should fall back to the literal `'default'` —
|
|
37
|
+
* that's how the call graph keys anonymous defaults internally
|
|
38
|
+
* (resolveDefaultExportBinding in call-graph.ts uses 'default' as the
|
|
39
|
+
* fallback name when the symbol carries no identifier).
|
|
40
|
+
*/
|
|
41
|
+
export function resolveDefaultExportName(sourceFile) {
|
|
42
|
+
const symbol = sourceFile.getDefaultExportSymbol();
|
|
43
|
+
if (!symbol)
|
|
44
|
+
return undefined;
|
|
45
|
+
for (const decl of symbol.getDeclarations()) {
|
|
46
|
+
const kind = decl.getKind();
|
|
47
|
+
// Case 3 + 4: `export default x` and `export { x as default }`.
|
|
48
|
+
// Both surface as ExportAssignment in ts-morph; getExpression() is
|
|
49
|
+
// the identifier when a name is in play.
|
|
50
|
+
if (kind === SyntaxKind.ExportAssignment) {
|
|
51
|
+
const expr = decl.getExpression();
|
|
52
|
+
if (expr.getKind() === SyntaxKind.Identifier) {
|
|
53
|
+
const text = expr.getText();
|
|
54
|
+
if (text)
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
// Anything else under an ExportAssignment is anonymous (literal,
|
|
58
|
+
// object expression, function expression without a name, …).
|
|
59
|
+
// Fall through to the next declaration just in case (rare for
|
|
60
|
+
// ExportSpecifier-style aliasing the symbol resolved to).
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// Cases 1 + 2 + the named-function-expression form of 5: anything
|
|
64
|
+
// with a `getName()` method (FunctionDeclaration, ClassDeclaration,
|
|
65
|
+
// FunctionExpression, ClassExpression). Anonymous functions/classes
|
|
66
|
+
// declared inline via `export default function () {}` have no name.
|
|
67
|
+
const named = decl;
|
|
68
|
+
if (typeof named.getName === 'function') {
|
|
69
|
+
const name = named.getName();
|
|
70
|
+
if (name)
|
|
71
|
+
return name;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=default-export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-export.js","sourceRoot":"","sources":["../src/default-export.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAqD,UAAU,EAAE,MAAM,UAAU,CAAC;AAEzF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,wBAAwB,CAAC,UAAsB;IAC7D,MAAM,MAAM,GAAG,UAAU,CAAC,sBAAsB,EAAE,CAAC;IACnD,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAE9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,eAAe,EAAE,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAE5B,gEAAgE;QAChE,mEAAmE;QACnE,yCAAyC;QACzC,IAAI,IAAI,KAAK,UAAU,CAAC,gBAAgB,EAAE,CAAC;YACzC,MAAM,IAAI,GAAI,IAAyB,CAAC,aAAa,EAAE,CAAC;YACxD,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,UAAU,CAAC,UAAU,EAAE,CAAC;gBAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC5B,IAAI,IAAI;oBAAE,OAAO,IAAI,CAAC;YACxB,CAAC;YACD,iEAAiE;YACjE,6DAA6D;YAC7D,8DAA8D;YAC9D,0DAA0D;YAC1D,SAAS;QACX,CAAC;QAED,kEAAkE;QAClE,oEAAoE;QACpE,oEAAoE;QACpE,oEAAoE;QACpE,MAAM,KAAK,GAAG,IAAqD,CAAC;QACpE,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;YACxC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YAC7B,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/dist/eval.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReviewConfig, ReviewFinding, ReviewReport } from './types.js';
|
|
2
|
+
export interface ReviewEvalFindingExpectation {
|
|
3
|
+
ruleId: string;
|
|
4
|
+
file?: string;
|
|
5
|
+
severity?: ReviewFinding['severity'];
|
|
6
|
+
messageIncludes?: string;
|
|
7
|
+
rootCauseKind?: NonNullable<ReviewFinding['rootCause']>['kind'];
|
|
8
|
+
minCount?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ReviewEvalExpectations {
|
|
11
|
+
present?: ReviewEvalFindingExpectation[];
|
|
12
|
+
absent?: ReviewEvalFindingExpectation[];
|
|
13
|
+
maxFindings?: number;
|
|
14
|
+
maxErrors?: number;
|
|
15
|
+
maxWarnings?: number;
|
|
16
|
+
maxDurationMs?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface ReviewEvalCaseConfig {
|
|
19
|
+
target?: string;
|
|
20
|
+
policy?: ReviewConfig['policy'];
|
|
21
|
+
crossStackMode?: ReviewConfig['crossStackMode'];
|
|
22
|
+
minConfidence?: number;
|
|
23
|
+
maxErrors?: number;
|
|
24
|
+
maxWarnings?: number;
|
|
25
|
+
disabledRules?: string[];
|
|
26
|
+
strict?: ReviewConfig['strict'];
|
|
27
|
+
strictParse?: boolean;
|
|
28
|
+
requireConfidenceAnnotations?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface ReviewEvalCase {
|
|
31
|
+
name: string;
|
|
32
|
+
files: string[];
|
|
33
|
+
graph?: boolean;
|
|
34
|
+
maxDepth?: number;
|
|
35
|
+
config?: ReviewEvalCaseConfig;
|
|
36
|
+
expect: ReviewEvalExpectations;
|
|
37
|
+
}
|
|
38
|
+
export interface ReviewEvalManifest {
|
|
39
|
+
schemaVersion?: 1;
|
|
40
|
+
cases: ReviewEvalCase[];
|
|
41
|
+
}
|
|
42
|
+
export interface ReviewEvalCaseResult {
|
|
43
|
+
name: string;
|
|
44
|
+
passed: boolean;
|
|
45
|
+
files: string[];
|
|
46
|
+
findings: number;
|
|
47
|
+
errors: number;
|
|
48
|
+
warnings: number;
|
|
49
|
+
notes: number;
|
|
50
|
+
durationMs?: number;
|
|
51
|
+
failures: string[];
|
|
52
|
+
}
|
|
53
|
+
export interface ReviewEvalSummary {
|
|
54
|
+
passed: boolean;
|
|
55
|
+
cases: number;
|
|
56
|
+
passedCases: number;
|
|
57
|
+
failedCases: number;
|
|
58
|
+
failures: number;
|
|
59
|
+
results: ReviewEvalCaseResult[];
|
|
60
|
+
}
|
|
61
|
+
export interface ReviewEvalRunMetadata {
|
|
62
|
+
durationMs?: number;
|
|
63
|
+
}
|
|
64
|
+
export declare function normalizeReviewEvalManifest(value: unknown): ReviewEvalManifest;
|
|
65
|
+
export declare function evaluateReviewReports(testCase: ReviewEvalCase, reports: readonly ReviewReport[], metadata?: ReviewEvalRunMetadata): ReviewEvalCaseResult;
|
|
66
|
+
export declare function summarizeReviewEvalResults(results: readonly ReviewEvalCaseResult[]): ReviewEvalSummary;
|
|
67
|
+
export declare function formatReviewEvalSummary(summary: ReviewEvalSummary): string;
|
package/dist/eval.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
export function normalizeReviewEvalManifest(value) {
|
|
2
|
+
if (!isRecord(value))
|
|
3
|
+
throw new Error('eval manifest must be an object');
|
|
4
|
+
const rawCases = value.cases;
|
|
5
|
+
if (!Array.isArray(rawCases) || rawCases.length === 0) {
|
|
6
|
+
throw new Error('eval manifest must contain a non-empty cases array');
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
schemaVersion: value.schemaVersion === undefined ? undefined : 1,
|
|
10
|
+
cases: rawCases.map((entry, index) => normalizeReviewEvalCase(entry, index)),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function evaluateReviewReports(testCase, reports, metadata = {}) {
|
|
14
|
+
const findings = reports.flatMap((report) => report.findings);
|
|
15
|
+
const failures = [];
|
|
16
|
+
const expected = testCase.expect ?? {};
|
|
17
|
+
for (const expectation of expected.present ?? []) {
|
|
18
|
+
const count = countMatches(findings, expectation);
|
|
19
|
+
const minCount = expectation.minCount ?? 1;
|
|
20
|
+
if (count < minCount) {
|
|
21
|
+
failures.push(`expected ${describeExpectation(expectation)} at least ${minCount} time(s), found ${count}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
for (const expectation of expected.absent ?? []) {
|
|
25
|
+
const count = countMatches(findings, expectation);
|
|
26
|
+
if (count > 0) {
|
|
27
|
+
failures.push(`expected no ${describeExpectation(expectation)}, found ${count}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (expected.maxFindings !== undefined && findings.length > expected.maxFindings) {
|
|
31
|
+
failures.push(`expected at most ${expected.maxFindings} finding(s), found ${findings.length}`);
|
|
32
|
+
}
|
|
33
|
+
const errors = findings.filter((finding) => finding.severity === 'error').length;
|
|
34
|
+
const warnings = findings.filter((finding) => finding.severity === 'warning').length;
|
|
35
|
+
const notes = findings.filter((finding) => finding.severity === 'info').length;
|
|
36
|
+
if (expected.maxErrors !== undefined && errors > expected.maxErrors) {
|
|
37
|
+
failures.push(`expected at most ${expected.maxErrors} error(s), found ${errors}`);
|
|
38
|
+
}
|
|
39
|
+
if (expected.maxWarnings !== undefined && warnings > expected.maxWarnings) {
|
|
40
|
+
failures.push(`expected at most ${expected.maxWarnings} warning(s), found ${warnings}`);
|
|
41
|
+
}
|
|
42
|
+
if (expected.maxDurationMs !== undefined &&
|
|
43
|
+
metadata.durationMs !== undefined &&
|
|
44
|
+
metadata.durationMs > expected.maxDurationMs) {
|
|
45
|
+
failures.push(`expected duration <= ${expected.maxDurationMs}ms, observed ${metadata.durationMs}ms`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
name: testCase.name,
|
|
49
|
+
passed: failures.length === 0,
|
|
50
|
+
files: testCase.files,
|
|
51
|
+
findings: findings.length,
|
|
52
|
+
errors,
|
|
53
|
+
warnings,
|
|
54
|
+
notes,
|
|
55
|
+
...(metadata.durationMs !== undefined ? { durationMs: metadata.durationMs } : {}),
|
|
56
|
+
failures,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function summarizeReviewEvalResults(results) {
|
|
60
|
+
const failedCases = results.filter((result) => !result.passed).length;
|
|
61
|
+
return {
|
|
62
|
+
passed: failedCases === 0,
|
|
63
|
+
cases: results.length,
|
|
64
|
+
passedCases: results.length - failedCases,
|
|
65
|
+
failedCases,
|
|
66
|
+
failures: results.reduce((sum, result) => sum + result.failures.length, 0),
|
|
67
|
+
results: [...results],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function formatReviewEvalSummary(summary) {
|
|
71
|
+
const lines = [
|
|
72
|
+
'KERN Review Eval',
|
|
73
|
+
'',
|
|
74
|
+
`Cases: ${summary.passedCases}/${summary.cases} passed`,
|
|
75
|
+
`Failures: ${summary.failures}`,
|
|
76
|
+
];
|
|
77
|
+
for (const result of summary.results) {
|
|
78
|
+
const status = result.passed ? 'PASS' : 'FAIL';
|
|
79
|
+
const duration = result.durationMs !== undefined ? `, ${result.durationMs}ms` : '';
|
|
80
|
+
lines.push('', `${status} ${result.name} (${result.findings} findings${duration})`);
|
|
81
|
+
for (const failure of result.failures) {
|
|
82
|
+
lines.push(` - ${failure}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
function normalizeReviewEvalCase(value, index) {
|
|
88
|
+
if (!isRecord(value))
|
|
89
|
+
throw new Error(`eval case ${index + 1} must be an object`);
|
|
90
|
+
if (typeof value.name !== 'string' || value.name.length === 0) {
|
|
91
|
+
throw new Error(`eval case ${index + 1} must declare a non-empty name`);
|
|
92
|
+
}
|
|
93
|
+
if (!Array.isArray(value.files) || value.files.some((file) => typeof file !== 'string')) {
|
|
94
|
+
throw new Error(`eval case '${value.name}' must declare a files string array`);
|
|
95
|
+
}
|
|
96
|
+
if (!isRecord(value.expect)) {
|
|
97
|
+
throw new Error(`eval case '${value.name}' must declare expect`);
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
name: value.name,
|
|
101
|
+
files: value.files,
|
|
102
|
+
...(value.graph !== undefined ? { graph: Boolean(value.graph) } : {}),
|
|
103
|
+
...(typeof value.maxDepth === 'number' ? { maxDepth: value.maxDepth } : {}),
|
|
104
|
+
...(isRecord(value.config) ? { config: value.config } : {}),
|
|
105
|
+
expect: normalizeExpectations(value.expect),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function normalizeExpectations(value) {
|
|
109
|
+
return {
|
|
110
|
+
...(Array.isArray(value.present)
|
|
111
|
+
? { present: value.present.map((entry) => normalizeFindingExpectation(entry)) }
|
|
112
|
+
: {}),
|
|
113
|
+
...(Array.isArray(value.absent) ? { absent: value.absent.map((entry) => normalizeFindingExpectation(entry)) } : {}),
|
|
114
|
+
...(typeof value.maxFindings === 'number' ? { maxFindings: value.maxFindings } : {}),
|
|
115
|
+
...(typeof value.maxErrors === 'number' ? { maxErrors: value.maxErrors } : {}),
|
|
116
|
+
...(typeof value.maxWarnings === 'number' ? { maxWarnings: value.maxWarnings } : {}),
|
|
117
|
+
...(typeof value.maxDurationMs === 'number' ? { maxDurationMs: value.maxDurationMs } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function normalizeFindingExpectation(value) {
|
|
121
|
+
if (typeof value === 'string')
|
|
122
|
+
return { ruleId: value };
|
|
123
|
+
if (!isRecord(value) || typeof value.ruleId !== 'string') {
|
|
124
|
+
throw new Error('finding expectation must be a rule id string or object with ruleId');
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
ruleId: value.ruleId,
|
|
128
|
+
...(typeof value.file === 'string' ? { file: value.file } : {}),
|
|
129
|
+
...(isSeverity(value.severity) ? { severity: value.severity } : {}),
|
|
130
|
+
...(typeof value.messageIncludes === 'string' ? { messageIncludes: value.messageIncludes } : {}),
|
|
131
|
+
...(typeof value.rootCauseKind === 'string'
|
|
132
|
+
? { rootCauseKind: value.rootCauseKind }
|
|
133
|
+
: {}),
|
|
134
|
+
...(typeof value.minCount === 'number' ? { minCount: value.minCount } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function countMatches(findings, expectation) {
|
|
138
|
+
return findings.filter((finding) => findingMatches(finding, expectation)).length;
|
|
139
|
+
}
|
|
140
|
+
function findingMatches(finding, expectation) {
|
|
141
|
+
if (finding.ruleId !== expectation.ruleId)
|
|
142
|
+
return false;
|
|
143
|
+
if (expectation.severity && finding.severity !== expectation.severity)
|
|
144
|
+
return false;
|
|
145
|
+
if (expectation.messageIncludes && !finding.message.includes(expectation.messageIncludes))
|
|
146
|
+
return false;
|
|
147
|
+
if (expectation.rootCauseKind && finding.rootCause?.kind !== expectation.rootCauseKind)
|
|
148
|
+
return false;
|
|
149
|
+
if (expectation.file && !pathMatches(finding.primarySpan.file, expectation.file))
|
|
150
|
+
return false;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
function pathMatches(actual, expected) {
|
|
154
|
+
const normalizedActual = normalizePath(actual);
|
|
155
|
+
const normalizedExpected = normalizePath(expected);
|
|
156
|
+
return normalizedActual === normalizedExpected || normalizedActual.endsWith(`/${normalizedExpected}`);
|
|
157
|
+
}
|
|
158
|
+
function normalizePath(path) {
|
|
159
|
+
return path.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
160
|
+
}
|
|
161
|
+
function describeExpectation(expectation) {
|
|
162
|
+
const parts = [expectation.ruleId];
|
|
163
|
+
if (expectation.file)
|
|
164
|
+
parts.push(`in ${expectation.file}`);
|
|
165
|
+
if (expectation.severity)
|
|
166
|
+
parts.push(`severity=${expectation.severity}`);
|
|
167
|
+
if (expectation.rootCauseKind)
|
|
168
|
+
parts.push(`rootCause=${expectation.rootCauseKind}`);
|
|
169
|
+
return parts.join(' ');
|
|
170
|
+
}
|
|
171
|
+
function isSeverity(value) {
|
|
172
|
+
return value === 'error' || value === 'warning' || value === 'info';
|
|
173
|
+
}
|
|
174
|
+
function isRecord(value) {
|
|
175
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=eval.js.map
|
package/dist/eval.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../src/eval.ts"],"names":[],"mappings":"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"}
|
package/dist/file-context.js
CHANGED
|
@@ -155,37 +155,49 @@ function traceImportChain(targetPath, fileMap, entrySet) {
|
|
|
155
155
|
export function buildFileContextMap(graph) {
|
|
156
156
|
const contextMap = new Map();
|
|
157
157
|
const fileMap = new Map();
|
|
158
|
-
|
|
158
|
+
// Internal maps key by canonicalPath (red-team #9 / Option B). Cross-file
|
|
159
|
+
// edges (gf.imports / gf.importedBy / graph.entryFiles include canonical
|
|
160
|
+
// forms after the canonicalisation pass in graph.ts), and ts-morph paths
|
|
161
|
+
// round-trip through canonical, so canonical is the only stable key.
|
|
162
|
+
// Display paths are reported via gf.path on the value side, not the key.
|
|
163
|
+
const entrySet = new Set(graph.entryFiles.map((p) => graph.files.find((g) => g.path === p)?.canonicalPath ?? p));
|
|
159
164
|
for (const gf of graph.files) {
|
|
160
|
-
fileMap.set(gf.
|
|
165
|
+
fileMap.set(gf.canonicalPath, gf);
|
|
161
166
|
}
|
|
162
|
-
// Classify entry points
|
|
167
|
+
// Classify entry points (key by canonicalPath so subsequent lookups match)
|
|
163
168
|
const entryBoundaries = new Map();
|
|
164
169
|
for (const entry of graph.entryFiles) {
|
|
165
|
-
|
|
170
|
+
const entryGf = graph.files.find((g) => g.path === entry);
|
|
171
|
+
const key = entryGf?.canonicalPath ?? entry;
|
|
172
|
+
// classifyEntryPoint reads suffix patterns (page.tsx, route.ts, etc.) —
|
|
173
|
+
// those are the same regardless of /var vs /private/var, so passing
|
|
174
|
+
// either form works. Use display so reads from disk hit the right file.
|
|
175
|
+
entryBoundaries.set(key, classifyEntryPoint(entry));
|
|
166
176
|
}
|
|
167
177
|
// Client boundary propagation cache
|
|
168
178
|
const clientBoundaryCache = new Map();
|
|
169
179
|
for (const gf of graph.files) {
|
|
170
|
-
const isClient = isWithinClientBoundary(gf.
|
|
180
|
+
const isClient = isWithinClientBoundary(gf.canonicalPath, fileMap, entrySet, clientBoundaryCache, new Set());
|
|
181
|
+
// hasUseClientDirective reads from disk — pass display so symlinked
|
|
182
|
+
// paths work even on systems without realpath idempotence guarantees.
|
|
171
183
|
const hasDirective = hasUseClientDirective(gf.path);
|
|
172
|
-
const importChain = traceImportChain(gf.
|
|
184
|
+
const importChain = traceImportChain(gf.canonicalPath, fileMap, entrySet);
|
|
173
185
|
// Determine boundary from import chain
|
|
174
186
|
let boundary = 'unknown';
|
|
175
187
|
if (isClient || hasDirective) {
|
|
176
188
|
boundary = 'client';
|
|
177
189
|
}
|
|
178
|
-
else if (entrySet.has(gf.
|
|
179
|
-
boundary = entryBoundaries.get(gf.
|
|
190
|
+
else if (entrySet.has(gf.canonicalPath)) {
|
|
191
|
+
boundary = entryBoundaries.get(gf.canonicalPath) || 'unknown';
|
|
180
192
|
}
|
|
181
193
|
else {
|
|
182
194
|
// Inherit boundary from entry points that import this file
|
|
183
195
|
const entryBounds = new Set();
|
|
184
196
|
for (const entry of graph.entryFiles) {
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
if (entryGf && canReach(
|
|
188
|
-
entryBounds.add(entryBoundaries.get(
|
|
197
|
+
const entryGf = graph.files.find((g) => g.path === entry);
|
|
198
|
+
const entryKey = entryGf?.canonicalPath ?? entry;
|
|
199
|
+
if (entryGf && canReach(entryKey, gf.canonicalPath, fileMap, new Set())) {
|
|
200
|
+
entryBounds.add(entryBoundaries.get(entryKey) || 'unknown');
|
|
189
201
|
}
|
|
190
202
|
}
|
|
191
203
|
if (entryBounds.size === 1) {
|
|
@@ -198,10 +210,17 @@ export function buildFileContextMap(graph) {
|
|
|
198
210
|
// Find which entry points reach this file
|
|
199
211
|
const reachableEntries = [];
|
|
200
212
|
for (const entry of graph.entryFiles) {
|
|
201
|
-
|
|
213
|
+
const entryGf = graph.files.find((g) => g.path === entry);
|
|
214
|
+
const entryKey = entryGf?.canonicalPath ?? entry;
|
|
215
|
+
if (entryKey === gf.canonicalPath || canReach(entryKey, gf.canonicalPath, fileMap, new Set())) {
|
|
216
|
+
// Push the DISPLAY entry path so consumers see what the user
|
|
217
|
+
// passed in, not the realpath-resolved form.
|
|
202
218
|
reachableEntries.push(entry);
|
|
203
219
|
}
|
|
204
220
|
}
|
|
221
|
+
// contextMap keyed by gf.path (display) so callers querying by the
|
|
222
|
+
// path they passed in find the entry directly. Internal lookups
|
|
223
|
+
// (fileMap, traceImportChain) use canonicalPath — see above.
|
|
205
224
|
contextMap.set(gf.path, {
|
|
206
225
|
boundary,
|
|
207
226
|
entryPoints: reachableEntries,
|