@kernlang/review 3.3.8 → 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/cache.js +1 -1
- 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.d.ts +10 -0
- package/dist/concept-rules/auth-propagation-drift.js +85 -0
- package/dist/concept-rules/auth-propagation-drift.js.map +1 -0
- package/dist/concept-rules/body-shape-drift.d.ts +32 -0
- package/dist/concept-rules/body-shape-drift.js +98 -0
- package/dist/concept-rules/body-shape-drift.js.map +1 -0
- 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/cross-stack-utils.d.ts +24 -0
- package/dist/concept-rules/cross-stack-utils.js +123 -29
- package/dist/concept-rules/cross-stack-utils.js.map +1 -1
- package/dist/concept-rules/index.d.ts +4 -2
- package/dist/concept-rules/index.js +22 -3
- package/dist/concept-rules/index.js.map +1 -1
- package/dist/concept-rules/mutation-without-idempotency.d.ts +10 -0
- package/dist/concept-rules/mutation-without-idempotency.js +47 -0
- package/dist/concept-rules/mutation-without-idempotency.js.map +1 -0
- package/dist/concept-rules/request-validation-drift.d.ts +11 -0
- package/dist/concept-rules/request-validation-drift.js +99 -0
- package/dist/concept-rules/request-validation-drift.js.map +1 -0
- 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.d.ts +10 -0
- package/dist/concept-rules/unbounded-collection-query.js +58 -0
- package/dist/concept-rules/unbounded-collection-query.js.map +1 -0
- package/dist/concept-rules/unhandled-api-error-shape.d.ts +10 -0
- package/dist/concept-rules/unhandled-api-error-shape.js +59 -0
- package/dist/concept-rules/unhandled-api-error-shape.js.map +1 -0
- 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/external-tools.js +52 -3
- package/dist/external-tools.js.map +1 -1
- 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 +12 -3
- package/dist/index.js +314 -96
- package/dist/index.js.map +1 -1
- package/dist/mappers/ts-concepts.js +730 -1
- 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/python-fallback.d.ts +2 -0
- package/dist/python-fallback.js +506 -0
- package/dist/python-fallback.js.map +1 -0
- package/dist/reporter.js +106 -1
- 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/base.js +21 -3
- package/dist/rules/base.js.map +1 -1
- 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 +72 -0
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/kern-source.d.ts +4 -0
- package/dist/rules/kern-source.js +184 -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 +172 -2
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
|
@@ -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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: unbounded-collection-query
|
|
3
|
+
*
|
|
4
|
+
* Cross-stack rule — fires when a client calls a list endpoint without
|
|
5
|
+
* page/cursor/limit parameters and the matching server route appears to return
|
|
6
|
+
* a DB-backed collection without a bound.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReviewFinding } from '../types.js';
|
|
9
|
+
import type { ConceptRuleContext } from './index.js';
|
|
10
|
+
export declare function unboundedCollectionQuery(ctx: ConceptRuleContext): ReviewFinding[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: unbounded-collection-query
|
|
3
|
+
*
|
|
4
|
+
* Cross-stack rule — fires when a client calls a list endpoint without
|
|
5
|
+
* page/cursor/limit parameters and the matching server route appears to return
|
|
6
|
+
* a DB-backed collection without a bound.
|
|
7
|
+
*/
|
|
8
|
+
import { createFingerprint } from '../types.js';
|
|
9
|
+
import { CROSS_STACK_HEURISTIC_CONFIDENCE, collectRoutesAcrossGraph, findHighConfidenceRouteForMethod, findMatchingRouteForMethod, normalizeClientUrl, } from './cross-stack-utils.js';
|
|
10
|
+
import { apiCallRootCause } from './root-cause.js';
|
|
11
|
+
const PAGINATION_QUERY_PARAMS = new Set(['limit', 'take', 'page', 'pageSize', 'perPage', 'cursor', 'offset', 'skip']);
|
|
12
|
+
export function unboundedCollectionQuery(ctx) {
|
|
13
|
+
if (!ctx.allConcepts || ctx.allConcepts.size === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const serverRoutes = collectRoutesAcrossGraph(ctx.allConcepts);
|
|
16
|
+
if (serverRoutes.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const findings = [];
|
|
19
|
+
const localConcepts = ctx.allConcepts.get(ctx.filePath) ?? ctx.concepts;
|
|
20
|
+
for (const node of localConcepts.nodes) {
|
|
21
|
+
if (node.kind !== 'effect' || node.payload.kind !== 'effect' || node.payload.subtype !== 'network')
|
|
22
|
+
continue;
|
|
23
|
+
if (node.payload.queryParamsResolved !== true)
|
|
24
|
+
continue;
|
|
25
|
+
if (hasPaginationParam(node.payload.queryParams ?? []))
|
|
26
|
+
continue;
|
|
27
|
+
const target = node.payload.target;
|
|
28
|
+
if (typeof target !== 'string')
|
|
29
|
+
continue;
|
|
30
|
+
const normalized = normalizeClientUrl(target);
|
|
31
|
+
if (!normalized)
|
|
32
|
+
continue;
|
|
33
|
+
const route = ctx.crossStackMode === 'audit'
|
|
34
|
+
? findMatchingRouteForMethod(normalized, node.payload.method, serverRoutes)
|
|
35
|
+
: findHighConfidenceRouteForMethod(normalized, node.payload.method, serverRoutes);
|
|
36
|
+
if (route?.node?.payload.kind !== 'entrypoint')
|
|
37
|
+
continue;
|
|
38
|
+
if (route.node.payload.hasUnboundedCollectionQuery !== true)
|
|
39
|
+
continue;
|
|
40
|
+
findings.push({
|
|
41
|
+
source: 'kern',
|
|
42
|
+
ruleId: 'unbounded-collection-query',
|
|
43
|
+
severity: 'warning',
|
|
44
|
+
category: 'bug',
|
|
45
|
+
message: `Client calls list endpoint \`${target}\` without page/cursor/limit parameters, and the matching server route appears to return an unbounded DB collection. Add pagination on both sides before this endpoint grows.`,
|
|
46
|
+
primarySpan: node.primarySpan,
|
|
47
|
+
relatedSpans: [route.node.primarySpan],
|
|
48
|
+
fingerprint: createFingerprint('unbounded-collection-query', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
49
|
+
confidence: node.confidence * CROSS_STACK_HEURISTIC_CONFIDENCE,
|
|
50
|
+
rootCause: apiCallRootCause(node, normalized, node.payload.method, route.node),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
}
|
|
55
|
+
function hasPaginationParam(params) {
|
|
56
|
+
return params.some((param) => PAGINATION_QUERY_PARAMS.has(param));
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=unbounded-collection-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unbounded-collection-query.js","sourceRoot":"","sources":["../../src/concept-rules/unbounded-collection-query.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: unhandled-api-error-shape
|
|
3
|
+
*
|
|
4
|
+
* Cross-stack rule — fires when a client calls a matching project API route
|
|
5
|
+
* without a visible error path while the backend route can explicitly return
|
|
6
|
+
* an error status such as 401/403/404/422/500.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReviewFinding } from '../types.js';
|
|
9
|
+
import type { ConceptRuleContext } from './index.js';
|
|
10
|
+
export declare function unhandledApiErrorShape(ctx: ConceptRuleContext): ReviewFinding[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: unhandled-api-error-shape
|
|
3
|
+
*
|
|
4
|
+
* Cross-stack rule — fires when a client calls a matching project API route
|
|
5
|
+
* without a visible error path while the backend route can explicitly return
|
|
6
|
+
* an error status such as 401/403/404/422/500.
|
|
7
|
+
*/
|
|
8
|
+
import { createFingerprint } from '../types.js';
|
|
9
|
+
import { CROSS_STACK_EXACT_CONFIDENCE, collectRoutesAcrossGraph, findHighConfidenceRouteForMethod, findMatchingRouteForMethod, normalizeClientUrl, } from './cross-stack-utils.js';
|
|
10
|
+
import { apiCallRootCause } from './root-cause.js';
|
|
11
|
+
const ERROR_STATUS_CODES = new Set([401, 403, 404, 422, 500]);
|
|
12
|
+
export function unhandledApiErrorShape(ctx) {
|
|
13
|
+
if (!ctx.allConcepts || ctx.allConcepts.size === 0)
|
|
14
|
+
return [];
|
|
15
|
+
const serverRoutes = collectRoutesAcrossGraph(ctx.allConcepts);
|
|
16
|
+
if (serverRoutes.length === 0)
|
|
17
|
+
return [];
|
|
18
|
+
const findings = [];
|
|
19
|
+
const localConcepts = ctx.allConcepts.get(ctx.filePath) ?? ctx.concepts;
|
|
20
|
+
for (const node of localConcepts.nodes) {
|
|
21
|
+
if (node.kind !== 'effect' || node.payload.kind !== 'effect' || node.payload.subtype !== 'network')
|
|
22
|
+
continue;
|
|
23
|
+
if (node.payload.handlesApiErrors !== false)
|
|
24
|
+
continue;
|
|
25
|
+
const target = node.payload.target;
|
|
26
|
+
if (typeof target !== 'string')
|
|
27
|
+
continue;
|
|
28
|
+
const normalized = normalizeClientUrl(target);
|
|
29
|
+
if (!normalized)
|
|
30
|
+
continue;
|
|
31
|
+
if (!isRawFetchEffect(node.evidence))
|
|
32
|
+
continue;
|
|
33
|
+
const route = ctx.crossStackMode === 'audit'
|
|
34
|
+
? findMatchingRouteForMethod(normalized, node.payload.method, serverRoutes)
|
|
35
|
+
: findHighConfidenceRouteForMethod(normalized, node.payload.method, serverRoutes);
|
|
36
|
+
const statusCodes = route?.node?.payload.kind === 'entrypoint' ? route.node.payload.errorStatusCodes : undefined;
|
|
37
|
+
const relevantCodes = (statusCodes ?? []).filter((code) => ERROR_STATUS_CODES.has(code));
|
|
38
|
+
if (relevantCodes.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const codeList = relevantCodes.join('/');
|
|
41
|
+
findings.push({
|
|
42
|
+
source: 'kern',
|
|
43
|
+
ruleId: 'unhandled-api-error-shape',
|
|
44
|
+
severity: 'warning',
|
|
45
|
+
category: 'bug',
|
|
46
|
+
message: `Client calls \`${target}\` with only the success path handled, but the matching server route can return ${codeList}. Add a \`response.ok\`/status branch, catch path, or error UI for the API error shape.`,
|
|
47
|
+
primarySpan: node.primarySpan,
|
|
48
|
+
relatedSpans: route?.node ? [route.node.primarySpan] : undefined,
|
|
49
|
+
fingerprint: createFingerprint('unhandled-api-error-shape', node.primarySpan.startLine, node.primarySpan.startCol),
|
|
50
|
+
confidence: node.confidence * CROSS_STACK_EXACT_CONFIDENCE,
|
|
51
|
+
rootCause: apiCallRootCause(node, normalized, node.payload.method, route?.node),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
}
|
|
56
|
+
function isRawFetchEffect(evidence) {
|
|
57
|
+
return /^\s*(?:await\s+)?fetch\s*\(/.test(evidence);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=unhandled-api-error-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|