claude-crap 0.1.2
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/CHANGELOG.md +308 -0
- package/LICENSE +21 -0
- package/README.md +550 -0
- package/bin/claude-crap.mjs +141 -0
- package/dist/adapters/bandit.d.ts +48 -0
- package/dist/adapters/bandit.d.ts.map +1 -0
- package/dist/adapters/bandit.js +145 -0
- package/dist/adapters/bandit.js.map +1 -0
- package/dist/adapters/common.d.ts +73 -0
- package/dist/adapters/common.d.ts.map +1 -0
- package/dist/adapters/common.js +78 -0
- package/dist/adapters/common.js.map +1 -0
- package/dist/adapters/eslint.d.ts +52 -0
- package/dist/adapters/eslint.d.ts.map +1 -0
- package/dist/adapters/eslint.js +142 -0
- package/dist/adapters/eslint.js.map +1 -0
- package/dist/adapters/index.d.ts +47 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +64 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/semgrep.d.ts +30 -0
- package/dist/adapters/semgrep.d.ts.map +1 -0
- package/dist/adapters/semgrep.js +130 -0
- package/dist/adapters/semgrep.js.map +1 -0
- package/dist/adapters/stryker.d.ts +55 -0
- package/dist/adapters/stryker.d.ts.map +1 -0
- package/dist/adapters/stryker.js +165 -0
- package/dist/adapters/stryker.js.map +1 -0
- package/dist/ast/cyclomatic.d.ts +48 -0
- package/dist/ast/cyclomatic.d.ts.map +1 -0
- package/dist/ast/cyclomatic.js +106 -0
- package/dist/ast/cyclomatic.js.map +1 -0
- package/dist/ast/index.d.ts +26 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +23 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/ast/language-config.d.ts +70 -0
- package/dist/ast/language-config.d.ts.map +1 -0
- package/dist/ast/language-config.js +192 -0
- package/dist/ast/language-config.js.map +1 -0
- package/dist/ast/tree-sitter-engine.d.ts +133 -0
- package/dist/ast/tree-sitter-engine.d.ts.map +1 -0
- package/dist/ast/tree-sitter-engine.js +270 -0
- package/dist/ast/tree-sitter-engine.js.map +1 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/crap-config.d.ts +97 -0
- package/dist/crap-config.d.ts.map +1 -0
- package/dist/crap-config.js +144 -0
- package/dist/crap-config.js.map +1 -0
- package/dist/dashboard/server.d.ts +65 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +147 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +574 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/crap.d.ts +71 -0
- package/dist/metrics/crap.d.ts.map +1 -0
- package/dist/metrics/crap.js +67 -0
- package/dist/metrics/crap.js.map +1 -0
- package/dist/metrics/index.d.ts +31 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +27 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/score.d.ts +143 -0
- package/dist/metrics/score.d.ts.map +1 -0
- package/dist/metrics/score.js +224 -0
- package/dist/metrics/score.js.map +1 -0
- package/dist/metrics/tdr.d.ts +106 -0
- package/dist/metrics/tdr.d.ts.map +1 -0
- package/dist/metrics/tdr.js +117 -0
- package/dist/metrics/tdr.js.map +1 -0
- package/dist/metrics/workspace-walker.d.ts +43 -0
- package/dist/metrics/workspace-walker.d.ts.map +1 -0
- package/dist/metrics/workspace-walker.js +137 -0
- package/dist/metrics/workspace-walker.js.map +1 -0
- package/dist/sarif/index.d.ts +21 -0
- package/dist/sarif/index.d.ts.map +1 -0
- package/dist/sarif/index.js +19 -0
- package/dist/sarif/index.js.map +1 -0
- package/dist/sarif/sarif-builder.d.ts +128 -0
- package/dist/sarif/sarif-builder.d.ts.map +1 -0
- package/dist/sarif/sarif-builder.js +79 -0
- package/dist/sarif/sarif-builder.js.map +1 -0
- package/dist/sarif/sarif-store.d.ts +205 -0
- package/dist/sarif/sarif-store.d.ts.map +1 -0
- package/dist/sarif/sarif-store.js +246 -0
- package/dist/sarif/sarif-store.js.map +1 -0
- package/dist/sarif/sarif-validator.d.ts +45 -0
- package/dist/sarif/sarif-validator.d.ts.map +1 -0
- package/dist/sarif/sarif-validator.js +138 -0
- package/dist/sarif/sarif-validator.js.map +1 -0
- package/dist/schemas/tool-schemas.d.ts +216 -0
- package/dist/schemas/tool-schemas.d.ts.map +1 -0
- package/dist/schemas/tool-schemas.js +208 -0
- package/dist/schemas/tool-schemas.js.map +1 -0
- package/dist/sdk.d.ts +45 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +44 -0
- package/dist/sdk.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/test-harness.d.ts +75 -0
- package/dist/tools/test-harness.d.ts.map +1 -0
- package/dist/tools/test-harness.js +137 -0
- package/dist/tools/test-harness.js.map +1 -0
- package/dist/workspace-guard.d.ts +53 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +61 -0
- package/dist/workspace-guard.js.map +1 -0
- package/package.json +133 -0
- package/plugin/.claude-plugin/plugin.json +29 -0
- package/plugin/.mcp.json +18 -0
- package/plugin/CLAUDE.md +143 -0
- package/plugin/bundle/dashboard/public/index.html +368 -0
- package/plugin/bundle/dashboard/public/vendor/vue.global.prod.js +9 -0
- package/plugin/bundle/mcp-server.mjs +8718 -0
- package/plugin/bundle/mcp-server.mjs.map +7 -0
- package/plugin/bundle/tdr-engine.mjs +50 -0
- package/plugin/bundle/tdr-engine.mjs.map +7 -0
- package/plugin/hooks/hooks.json +62 -0
- package/plugin/hooks/lib/crap-config.mjs +152 -0
- package/plugin/hooks/lib/gatekeeper-rules.mjs +257 -0
- package/plugin/hooks/lib/hook-io.mjs +151 -0
- package/plugin/hooks/lib/quality-gate.mjs +329 -0
- package/plugin/hooks/lib/test-harness.mjs +152 -0
- package/plugin/hooks/post-tool-use.mjs +245 -0
- package/plugin/hooks/pre-tool-use.mjs +290 -0
- package/plugin/hooks/session-start.mjs +109 -0
- package/plugin/hooks/stop-quality-gate.mjs +226 -0
- package/plugin/package.json +18 -0
- package/plugin/skills/adopt/SKILL.md +74 -0
- package/plugin/skills/analyze/SKILL.md +77 -0
- package/plugin/skills/check-test/SKILL.md +50 -0
- package/plugin/skills/score/SKILL.md +31 -0
- package/scripts/bug-report.mjs +328 -0
- package/scripts/build-fast.mjs +130 -0
- package/scripts/bundle-plugin.mjs +74 -0
- package/scripts/doctor.mjs +320 -0
- package/scripts/install.mjs +192 -0
- package/scripts/lib/cli-ui.mjs +122 -0
- package/scripts/postinstall.mjs +127 -0
- package/scripts/run-tests.mjs +95 -0
- package/scripts/status.mjs +110 -0
- package/scripts/uninstall.mjs +72 -0
- package/src/adapters/bandit.ts +191 -0
- package/src/adapters/common.ts +133 -0
- package/src/adapters/eslint.ts +187 -0
- package/src/adapters/index.ts +78 -0
- package/src/adapters/semgrep.ts +150 -0
- package/src/adapters/stryker.ts +218 -0
- package/src/ast/cyclomatic.ts +131 -0
- package/src/ast/index.ts +33 -0
- package/src/ast/language-config.ts +231 -0
- package/src/ast/tree-sitter-engine.ts +385 -0
- package/src/config.ts +109 -0
- package/src/crap-config.ts +196 -0
- package/src/dashboard/public/index.html +368 -0
- package/src/dashboard/public/vendor/vue.global.prod.js +9 -0
- package/src/dashboard/server.ts +205 -0
- package/src/index.ts +696 -0
- package/src/metrics/crap.ts +101 -0
- package/src/metrics/index.ts +51 -0
- package/src/metrics/score.ts +329 -0
- package/src/metrics/tdr.ts +155 -0
- package/src/metrics/workspace-walker.ts +146 -0
- package/src/sarif/index.ts +31 -0
- package/src/sarif/sarif-builder.ts +139 -0
- package/src/sarif/sarif-store.ts +347 -0
- package/src/sarif/sarif-validator.ts +145 -0
- package/src/schemas/tool-schemas.ts +225 -0
- package/src/sdk.ts +110 -0
- package/src/tests/adapters/bandit.test.ts +111 -0
- package/src/tests/adapters/dispatch.test.ts +100 -0
- package/src/tests/adapters/eslint.test.ts +138 -0
- package/src/tests/adapters/semgrep.test.ts +125 -0
- package/src/tests/adapters/stryker.test.ts +103 -0
- package/src/tests/crap-config.test.ts +228 -0
- package/src/tests/crap.test.ts +59 -0
- package/src/tests/cyclomatic.test.ts +87 -0
- package/src/tests/dashboard-http.test.ts +108 -0
- package/src/tests/dashboard-integrity.test.ts +128 -0
- package/src/tests/integration/mcp-server.integration.test.ts +352 -0
- package/src/tests/pre-tool-use-hook.test.ts +178 -0
- package/src/tests/sarif-store.test.ts +241 -0
- package/src/tests/sarif-validator.test.ts +164 -0
- package/src/tests/score.test.ts +260 -0
- package/src/tests/skills-frontmatter.test.ts +172 -0
- package/src/tests/stop-quality-gate-strictness.test.ts +243 -0
- package/src/tests/tdr.test.ts +86 -0
- package/src/tests/test-harness.test.ts +153 -0
- package/src/tests/workspace-guard.test.ts +111 -0
- package/src/tools/index.ts +24 -0
- package/src/tools/test-harness.ts +158 -0
- package/src/workspace-guard.ts +64 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public SDK entry point for the per-scanner SARIF adapters.
|
|
3
|
+
*
|
|
4
|
+
* Adapters convert a scanner's native output (SARIF, JSON, or some
|
|
5
|
+
* other structured format) into a normalized `PersistedSarif`
|
|
6
|
+
* document that the `SarifStore` can ingest directly. Every adapter
|
|
7
|
+
* enriches its findings with a stable `effortMinutes` value on the
|
|
8
|
+
* `properties` bag so the Stop quality gate and the project score
|
|
9
|
+
* engine can compute a Technical Debt Ratio.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* import {
|
|
15
|
+
* adaptScannerOutput,
|
|
16
|
+
* adaptSemgrep,
|
|
17
|
+
* adaptEslint,
|
|
18
|
+
* adaptBandit,
|
|
19
|
+
* adaptStryker,
|
|
20
|
+
* } from "claude-crap/adapters";
|
|
21
|
+
*
|
|
22
|
+
* const result = adaptScannerOutput("eslint", rawJsonFromEslint);
|
|
23
|
+
* sarifStore.ingestRun(result.document, result.sourceTool);
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @module adapters
|
|
27
|
+
*/
|
|
28
|
+
export { adaptSemgrep } from "./semgrep.js";
|
|
29
|
+
export { adaptEslint } from "./eslint.js";
|
|
30
|
+
export { adaptBandit } from "./bandit.js";
|
|
31
|
+
export { adaptStryker } from "./stryker.js";
|
|
32
|
+
export { DEFAULT_EFFORT_BY_SEVERITY, KNOWN_SCANNERS, estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
|
|
33
|
+
import { adaptSemgrep } from "./semgrep.js";
|
|
34
|
+
import { adaptEslint } from "./eslint.js";
|
|
35
|
+
import { adaptBandit } from "./bandit.js";
|
|
36
|
+
import { adaptStryker } from "./stryker.js";
|
|
37
|
+
/**
|
|
38
|
+
* Route a raw scanner output to the correct adapter based on its
|
|
39
|
+
* name. Preferred entry point for the `ingest_scanner_output` MCP
|
|
40
|
+
* tool — the dispatch is a single switch so the compiler can verify
|
|
41
|
+
* every case with `never` exhaustiveness.
|
|
42
|
+
*
|
|
43
|
+
* @param scanner One of the known scanner identifiers.
|
|
44
|
+
* @param rawOutput The scanner's native output (string or parsed).
|
|
45
|
+
* @returns A normalized `AdapterResult`.
|
|
46
|
+
* @throws When `scanner` is unknown or the raw output is malformed.
|
|
47
|
+
*/
|
|
48
|
+
export function adaptScannerOutput(scanner, rawOutput) {
|
|
49
|
+
switch (scanner) {
|
|
50
|
+
case "semgrep":
|
|
51
|
+
return adaptSemgrep(rawOutput);
|
|
52
|
+
case "eslint":
|
|
53
|
+
return adaptEslint(rawOutput);
|
|
54
|
+
case "bandit":
|
|
55
|
+
return adaptBandit(rawOutput);
|
|
56
|
+
case "stryker":
|
|
57
|
+
return adaptStryker(rawOutput);
|
|
58
|
+
default: {
|
|
59
|
+
const exhaustive = scanner;
|
|
60
|
+
throw new Error(`[adapters] Unknown scanner: ${String(exhaustive)}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAqB,EACrB,SAAkB;IAElB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,OAAO,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep adapter.
|
|
3
|
+
*
|
|
4
|
+
* Semgrep already emits SARIF 2.1.0 natively when invoked with
|
|
5
|
+
* `--sarif`, so this adapter's job is not translation but
|
|
6
|
+
* **enrichment**: we walk every `result` entry and stamp a
|
|
7
|
+
* `properties.effortMinutes` value so the Stop quality gate can
|
|
8
|
+
* compute a Technical Debt Ratio, plus we normalize the
|
|
9
|
+
* `properties.sourceTool` field so downstream consumers always know
|
|
10
|
+
* the finding came from Semgrep.
|
|
11
|
+
*
|
|
12
|
+
* If the caller passes a string, we parse it as JSON. If they pass an
|
|
13
|
+
* object that already matches the SARIF 2.1.0 envelope, we use it
|
|
14
|
+
* directly. Anything else throws a descriptive error that the MCP
|
|
15
|
+
* tool handler surfaces back to the LLM.
|
|
16
|
+
*
|
|
17
|
+
* @module adapters/semgrep
|
|
18
|
+
*/
|
|
19
|
+
import { type AdapterResult } from "./common.js";
|
|
20
|
+
/**
|
|
21
|
+
* Accept a Semgrep SARIF document (as a string or object) and return
|
|
22
|
+
* an enriched `PersistedSarif` with effort estimates and a normalized
|
|
23
|
+
* `sourceTool` field.
|
|
24
|
+
*
|
|
25
|
+
* @param input Raw SARIF document from Semgrep (`JSON.stringify`ed or parsed).
|
|
26
|
+
* @returns The enriched document plus per-run stats.
|
|
27
|
+
* @throws When the input is not a SARIF 2.1.0 document.
|
|
28
|
+
*/
|
|
29
|
+
export declare function adaptSemgrep(input: unknown): AdapterResult;
|
|
30
|
+
//# sourceMappingURL=semgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.d.ts","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AAiBrB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA8D1D"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semgrep adapter.
|
|
3
|
+
*
|
|
4
|
+
* Semgrep already emits SARIF 2.1.0 natively when invoked with
|
|
5
|
+
* `--sarif`, so this adapter's job is not translation but
|
|
6
|
+
* **enrichment**: we walk every `result` entry and stamp a
|
|
7
|
+
* `properties.effortMinutes` value so the Stop quality gate can
|
|
8
|
+
* compute a Technical Debt Ratio, plus we normalize the
|
|
9
|
+
* `properties.sourceTool` field so downstream consumers always know
|
|
10
|
+
* the finding came from Semgrep.
|
|
11
|
+
*
|
|
12
|
+
* If the caller passes a string, we parse it as JSON. If they pass an
|
|
13
|
+
* object that already matches the SARIF 2.1.0 envelope, we use it
|
|
14
|
+
* directly. Anything else throws a descriptive error that the MCP
|
|
15
|
+
* tool handler surfaces back to the LLM.
|
|
16
|
+
*
|
|
17
|
+
* @module adapters/semgrep
|
|
18
|
+
*/
|
|
19
|
+
import { estimateEffortMinutes, } from "./common.js";
|
|
20
|
+
const SEMGREP = "semgrep";
|
|
21
|
+
/**
|
|
22
|
+
* Rule-id effort overrides. Semgrep emits lots of stylistic rules
|
|
23
|
+
* that take less than a minute to fix and a handful of deep security
|
|
24
|
+
* rules that deserve more budget than the default warning tier. The
|
|
25
|
+
* list below is intentionally short — teams should extend it.
|
|
26
|
+
*/
|
|
27
|
+
const SEMGREP_EFFORT_OVERRIDES = new Map([
|
|
28
|
+
[/security\./i, 90],
|
|
29
|
+
[/sqli|xss|ssrf|rce|deserial|crypto/i, 120],
|
|
30
|
+
[/style\./i, 5],
|
|
31
|
+
[/formatting\./i, 3],
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Accept a Semgrep SARIF document (as a string or object) and return
|
|
35
|
+
* an enriched `PersistedSarif` with effort estimates and a normalized
|
|
36
|
+
* `sourceTool` field.
|
|
37
|
+
*
|
|
38
|
+
* @param input Raw SARIF document from Semgrep (`JSON.stringify`ed or parsed).
|
|
39
|
+
* @returns The enriched document plus per-run stats.
|
|
40
|
+
* @throws When the input is not a SARIF 2.1.0 document.
|
|
41
|
+
*/
|
|
42
|
+
export function adaptSemgrep(input) {
|
|
43
|
+
const doc = coerceToSarif(input);
|
|
44
|
+
let findingCount = 0;
|
|
45
|
+
let totalEffortMinutes = 0;
|
|
46
|
+
// We deep-clone the document so callers don't observe a mutation on
|
|
47
|
+
// the value they passed us. JSON round-trip is cheap here; a
|
|
48
|
+
// typical Semgrep SARIF report is well under 1 MB.
|
|
49
|
+
//
|
|
50
|
+
// We operate on the cloned value through a loose `Record`-based view
|
|
51
|
+
// to keep the adapter agnostic to the full SARIF schema — the
|
|
52
|
+
// canonical types live in `sarif-store.ts` and we only care about a
|
|
53
|
+
// handful of fields here. The final return casts through `unknown`
|
|
54
|
+
// because the JSON round-trip is shape-preserving by construction.
|
|
55
|
+
const cloned = JSON.parse(JSON.stringify(doc));
|
|
56
|
+
const runs = Array.isArray(cloned.runs) ? cloned.runs : [];
|
|
57
|
+
for (const run of runs) {
|
|
58
|
+
const rawResults = run["results"];
|
|
59
|
+
const results = Array.isArray(rawResults) ? rawResults : [];
|
|
60
|
+
for (const result of results) {
|
|
61
|
+
findingCount += 1;
|
|
62
|
+
const ruleId = typeof result["ruleId"] === "string" ? result["ruleId"] : "";
|
|
63
|
+
const level = result["level"];
|
|
64
|
+
const override = matchOverride(ruleId);
|
|
65
|
+
const effort = estimateEffortMinutes(level, override);
|
|
66
|
+
totalEffortMinutes += effort;
|
|
67
|
+
const existingProps = result["properties"] && typeof result["properties"] === "object"
|
|
68
|
+
? result["properties"]
|
|
69
|
+
: {};
|
|
70
|
+
result["properties"] = {
|
|
71
|
+
...existingProps,
|
|
72
|
+
sourceTool: SEMGREP,
|
|
73
|
+
effortMinutes: effort,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Overwrite tool.driver.name so store-level filters always match
|
|
77
|
+
// `"semgrep"` regardless of the label Semgrep reported. Preserve
|
|
78
|
+
// the existing version and rules[] array when present.
|
|
79
|
+
const existingTool = run["tool"] ?? {};
|
|
80
|
+
const existingDriver = existingTool["driver"] ?? {};
|
|
81
|
+
const driverOut = {
|
|
82
|
+
name: SEMGREP,
|
|
83
|
+
version: typeof existingDriver["version"] === "string" ? existingDriver["version"] : "unknown",
|
|
84
|
+
};
|
|
85
|
+
if (Array.isArray(existingDriver["rules"])) {
|
|
86
|
+
driverOut["rules"] = existingDriver["rules"];
|
|
87
|
+
}
|
|
88
|
+
run["tool"] = { driver: driverOut };
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
document: cloned,
|
|
92
|
+
sourceTool: SEMGREP,
|
|
93
|
+
findingCount,
|
|
94
|
+
totalEffortMinutes,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Accept either a pre-parsed SARIF object or a JSON string and return
|
|
99
|
+
* a strongly-typed `PersistedSarif`. Throws on malformed input.
|
|
100
|
+
*
|
|
101
|
+
* @param input Raw caller-provided value.
|
|
102
|
+
*/
|
|
103
|
+
function coerceToSarif(input) {
|
|
104
|
+
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
105
|
+
if (!parsed || typeof parsed !== "object") {
|
|
106
|
+
throw new Error(`[adapter:semgrep] input is not a SARIF object`);
|
|
107
|
+
}
|
|
108
|
+
const doc = parsed;
|
|
109
|
+
if (doc.version !== "2.1.0") {
|
|
110
|
+
throw new Error(`[adapter:semgrep] expected SARIF version 2.1.0, got ${String(doc.version)}`);
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(doc.runs)) {
|
|
113
|
+
throw new Error(`[adapter:semgrep] document is missing a runs[] array`);
|
|
114
|
+
}
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Return the effort override (in minutes) matching the first pattern
|
|
119
|
+
* that matches the given rule id, or `undefined` when none match.
|
|
120
|
+
*
|
|
121
|
+
* @param ruleId Semgrep rule identifier.
|
|
122
|
+
*/
|
|
123
|
+
function matchOverride(ruleId) {
|
|
124
|
+
for (const [pattern, minutes] of SEMGREP_EFFORT_OVERRIDES) {
|
|
125
|
+
if (pattern.test(ruleId))
|
|
126
|
+
return minutes;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=semgrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stryker (JavaScript mutation testing) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Stryker emits a JSON report under `reports/mutation/mutation.json`.
|
|
5
|
+
* The report shape is documented at
|
|
6
|
+
* https://stryker-mutator.io/docs/mutation-testing-elements/mutant-result-schema/ :
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* "schemaVersion": "1.0",
|
|
10
|
+
* "thresholds": { ... },
|
|
11
|
+
* "files": {
|
|
12
|
+
* "src/foo.ts": {
|
|
13
|
+
* "language": "typescript",
|
|
14
|
+
* "source": "...",
|
|
15
|
+
* "mutants": [
|
|
16
|
+
* {
|
|
17
|
+
* "id": "1",
|
|
18
|
+
* "mutatorName": "ConditionalExpression",
|
|
19
|
+
* "replacement": "false",
|
|
20
|
+
* "location": {
|
|
21
|
+
* "start": { "line": 10, "column": 5 },
|
|
22
|
+
* "end": { "line": 10, "column": 15 }
|
|
23
|
+
* },
|
|
24
|
+
* "status": "Survived", // Killed | Survived | Timeout | NoCoverage | RuntimeError | CompileError | Ignored
|
|
25
|
+
* "statusReason": "..."
|
|
26
|
+
* }
|
|
27
|
+
* ]
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* This adapter treats every **surviving mutant** as a SARIF
|
|
33
|
+
* `error`-level finding — surviving mutants are exactly the ones the
|
|
34
|
+
* Golden Rule forbids, because they prove the test suite does not
|
|
35
|
+
* pin the code's behavior tightly enough to notice a change.
|
|
36
|
+
*
|
|
37
|
+
* Mutants with status `NoCoverage` become `warning`-level findings
|
|
38
|
+
* (not blocking the Stop gate by themselves, but still ingested so
|
|
39
|
+
* the dashboard can surface uncovered lines). All other statuses are
|
|
40
|
+
* ignored — they do not represent defects.
|
|
41
|
+
*
|
|
42
|
+
* @module adapters/stryker
|
|
43
|
+
*/
|
|
44
|
+
import { type AdapterResult } from "./common.js";
|
|
45
|
+
/**
|
|
46
|
+
* Accept a Stryker JSON mutation report and return a normalized
|
|
47
|
+
* `PersistedSarif` document with one finding per surviving mutant
|
|
48
|
+
* and per uncovered mutant.
|
|
49
|
+
*
|
|
50
|
+
* @param input Raw Stryker report (string or parsed object).
|
|
51
|
+
* @returns Adapter result.
|
|
52
|
+
* @throws When the input is not a Stryker mutation report.
|
|
53
|
+
*/
|
|
54
|
+
export declare function adaptStryker(input: unknown): AdapterResult;
|
|
55
|
+
//# sourceMappingURL=stryker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stryker.d.ts","sourceRoot":"","sources":["../../src/adapters/stryker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AA0BrB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA8D1D"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stryker (JavaScript mutation testing) adapter.
|
|
3
|
+
*
|
|
4
|
+
* Stryker emits a JSON report under `reports/mutation/mutation.json`.
|
|
5
|
+
* The report shape is documented at
|
|
6
|
+
* https://stryker-mutator.io/docs/mutation-testing-elements/mutant-result-schema/ :
|
|
7
|
+
*
|
|
8
|
+
* {
|
|
9
|
+
* "schemaVersion": "1.0",
|
|
10
|
+
* "thresholds": { ... },
|
|
11
|
+
* "files": {
|
|
12
|
+
* "src/foo.ts": {
|
|
13
|
+
* "language": "typescript",
|
|
14
|
+
* "source": "...",
|
|
15
|
+
* "mutants": [
|
|
16
|
+
* {
|
|
17
|
+
* "id": "1",
|
|
18
|
+
* "mutatorName": "ConditionalExpression",
|
|
19
|
+
* "replacement": "false",
|
|
20
|
+
* "location": {
|
|
21
|
+
* "start": { "line": 10, "column": 5 },
|
|
22
|
+
* "end": { "line": 10, "column": 15 }
|
|
23
|
+
* },
|
|
24
|
+
* "status": "Survived", // Killed | Survived | Timeout | NoCoverage | RuntimeError | CompileError | Ignored
|
|
25
|
+
* "statusReason": "..."
|
|
26
|
+
* }
|
|
27
|
+
* ]
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* This adapter treats every **surviving mutant** as a SARIF
|
|
33
|
+
* `error`-level finding — surviving mutants are exactly the ones the
|
|
34
|
+
* Golden Rule forbids, because they prove the test suite does not
|
|
35
|
+
* pin the code's behavior tightly enough to notice a change.
|
|
36
|
+
*
|
|
37
|
+
* Mutants with status `NoCoverage` become `warning`-level findings
|
|
38
|
+
* (not blocking the Stop gate by themselves, but still ingested so
|
|
39
|
+
* the dashboard can surface uncovered lines). All other statuses are
|
|
40
|
+
* ignored — they do not represent defects.
|
|
41
|
+
*
|
|
42
|
+
* @module adapters/stryker
|
|
43
|
+
*/
|
|
44
|
+
import { estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
|
|
45
|
+
const STRYKER = "stryker";
|
|
46
|
+
/**
|
|
47
|
+
* Accept a Stryker JSON mutation report and return a normalized
|
|
48
|
+
* `PersistedSarif` document with one finding per surviving mutant
|
|
49
|
+
* and per uncovered mutant.
|
|
50
|
+
*
|
|
51
|
+
* @param input Raw Stryker report (string or parsed object).
|
|
52
|
+
* @returns Adapter result.
|
|
53
|
+
* @throws When the input is not a Stryker mutation report.
|
|
54
|
+
*/
|
|
55
|
+
export function adaptStryker(input) {
|
|
56
|
+
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
57
|
+
if (!parsed || typeof parsed !== "object") {
|
|
58
|
+
throw new Error(`[adapter:stryker] expected a JSON object`);
|
|
59
|
+
}
|
|
60
|
+
const report = parsed;
|
|
61
|
+
if (!report.files || typeof report.files !== "object") {
|
|
62
|
+
throw new Error(`[adapter:stryker] report is missing a files{} map`);
|
|
63
|
+
}
|
|
64
|
+
const results = [];
|
|
65
|
+
let totalEffortMinutes = 0;
|
|
66
|
+
for (const [filename, fileReport] of Object.entries(report.files)) {
|
|
67
|
+
const mutants = Array.isArray(fileReport?.mutants) ? fileReport.mutants : [];
|
|
68
|
+
for (const mutant of mutants) {
|
|
69
|
+
const level = classifyMutant(mutant.status);
|
|
70
|
+
if (level === null)
|
|
71
|
+
continue; // Killed / Ignored / CompileError — not a defect
|
|
72
|
+
const startLine = mutant.location?.start?.line ?? 1;
|
|
73
|
+
const startColumn = mutant.location?.start?.column ?? 1;
|
|
74
|
+
const endLine = mutant.location?.end?.line;
|
|
75
|
+
const endColumn = mutant.location?.end?.column;
|
|
76
|
+
// Surviving mutants cost more to fix than the default error
|
|
77
|
+
// budget because the agent has to first write a killing test,
|
|
78
|
+
// THEN possibly rewrite the code.
|
|
79
|
+
const effortOverride = level === "error" ? 45 : 15;
|
|
80
|
+
const effort = estimateEffortMinutes(level, effortOverride);
|
|
81
|
+
totalEffortMinutes += effort;
|
|
82
|
+
const mutator = mutant.mutatorName ?? "Unknown";
|
|
83
|
+
const ruleId = `stryker.${mutator}`;
|
|
84
|
+
const statusText = mutant.status ?? "Unknown";
|
|
85
|
+
const message = `${statusText}: ${mutator} mutant on '${mutant.replacement ?? "?"}'` +
|
|
86
|
+
(mutant.statusReason ? ` — ${mutant.statusReason}` : "");
|
|
87
|
+
results.push(buildSarifResult({
|
|
88
|
+
ruleId,
|
|
89
|
+
level,
|
|
90
|
+
message,
|
|
91
|
+
uri: filename,
|
|
92
|
+
startLine,
|
|
93
|
+
startColumn,
|
|
94
|
+
endLine: typeof endLine === "number" ? endLine : undefined,
|
|
95
|
+
endColumn: typeof endColumn === "number" ? endColumn : undefined,
|
|
96
|
+
effortMinutes: effort,
|
|
97
|
+
mutantId: mutant.id,
|
|
98
|
+
mutator,
|
|
99
|
+
mutantStatus: statusText,
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
document: wrapResultsInSarif(STRYKER, String(report.schemaVersion ?? "unknown"), results),
|
|
105
|
+
sourceTool: STRYKER,
|
|
106
|
+
findingCount: results.length,
|
|
107
|
+
totalEffortMinutes,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Classify a Stryker mutant status into a SARIF level, or `null` when
|
|
112
|
+
* the status represents a mutant that was correctly handled by the
|
|
113
|
+
* test suite and should not produce a finding.
|
|
114
|
+
*/
|
|
115
|
+
function classifyMutant(status) {
|
|
116
|
+
switch ((status ?? "").toLowerCase()) {
|
|
117
|
+
case "survived":
|
|
118
|
+
return "error";
|
|
119
|
+
case "nocoverage":
|
|
120
|
+
return "warning";
|
|
121
|
+
case "timeout":
|
|
122
|
+
// Timeout mutants are suspicious but not proof of defect —
|
|
123
|
+
// they deserve a note so the dashboard highlights them.
|
|
124
|
+
return "note";
|
|
125
|
+
case "killed":
|
|
126
|
+
case "ignored":
|
|
127
|
+
case "compileerror":
|
|
128
|
+
case "runtimeerror":
|
|
129
|
+
default:
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Build the SARIF `result` object for a single Stryker mutant. We
|
|
135
|
+
* propagate the mutant id, mutator name, and raw status into
|
|
136
|
+
* `properties` so the dashboard can display them as tags.
|
|
137
|
+
*/
|
|
138
|
+
function buildSarifResult(opts) {
|
|
139
|
+
return {
|
|
140
|
+
ruleId: opts.ruleId,
|
|
141
|
+
level: opts.level,
|
|
142
|
+
message: { text: opts.message },
|
|
143
|
+
locations: [
|
|
144
|
+
{
|
|
145
|
+
physicalLocation: {
|
|
146
|
+
artifactLocation: { uri: opts.uri },
|
|
147
|
+
region: {
|
|
148
|
+
startLine: opts.startLine,
|
|
149
|
+
startColumn: opts.startColumn,
|
|
150
|
+
...(opts.endLine !== undefined ? { endLine: opts.endLine } : {}),
|
|
151
|
+
...(opts.endColumn !== undefined ? { endColumn: opts.endColumn } : {}),
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
properties: {
|
|
157
|
+
sourceTool: STRYKER,
|
|
158
|
+
effortMinutes: opts.effortMinutes,
|
|
159
|
+
...(opts.mutantId ? { mutantId: opts.mutantId } : {}),
|
|
160
|
+
...(opts.mutator ? { mutator: opts.mutator } : {}),
|
|
161
|
+
...(opts.mutantStatus ? { mutantStatus: opts.mutantStatus } : {}),
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=stryker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stryker.js","sourceRoot":"","sources":["../../src/adapters/stryker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic cyclomatic complexity walker.
|
|
3
|
+
*
|
|
4
|
+
* Given a tree-sitter subtree rooted at a function node, this module walks
|
|
5
|
+
* the tree and returns the McCabe cyclomatic complexity number. The
|
|
6
|
+
* algorithm is the standard "1 + (branching nodes + short-circuit operators)"
|
|
7
|
+
* formulation:
|
|
8
|
+
*
|
|
9
|
+
* CC = 1
|
|
10
|
+
* + count of branching-statement nodes (if, while, for, case, catch, ...)
|
|
11
|
+
* + count of short-circuit operators (&&, ||, ??, `and`, `or`)
|
|
12
|
+
* + count of ternary expressions (counted as branching nodes)
|
|
13
|
+
*
|
|
14
|
+
* The baseline of 1 represents the straight-line path through the function.
|
|
15
|
+
* Every additional branching point multiplies the set of reachable paths.
|
|
16
|
+
*
|
|
17
|
+
* The walker is deliberately language-agnostic: it consults the
|
|
18
|
+
* {@link LanguageConfig} passed in to decide which node types to count.
|
|
19
|
+
* Nested functions inside the subtree are **skipped** — each function's
|
|
20
|
+
* complexity is reported independently by {@link TreeSitterEngine}.
|
|
21
|
+
*
|
|
22
|
+
* @module ast/cyclomatic
|
|
23
|
+
*/
|
|
24
|
+
import type { LanguageConfig } from "./language-config.js";
|
|
25
|
+
/**
|
|
26
|
+
* Minimal structural contract of a tree-sitter node. We intentionally
|
|
27
|
+
* avoid importing `web-tree-sitter` types here so this module stays
|
|
28
|
+
* unit-testable with a hand-rolled mock tree.
|
|
29
|
+
*/
|
|
30
|
+
export interface AstNode {
|
|
31
|
+
/** Node type name from the grammar (e.g. `"if_statement"`). */
|
|
32
|
+
readonly type: string;
|
|
33
|
+
/** Raw source text for operator detection. May be large — do not log. */
|
|
34
|
+
readonly text: string;
|
|
35
|
+
/** Zero-based child count. */
|
|
36
|
+
readonly childCount: number;
|
|
37
|
+
/** Retrieve a child by index. Returns `null` if out of range. */
|
|
38
|
+
child(index: number): AstNode | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compute the cyclomatic complexity of a function subtree.
|
|
42
|
+
*
|
|
43
|
+
* @param root Node rooted at the function (method, arrow, lambda, ...).
|
|
44
|
+
* @param languageConfig Language classification tables for node types.
|
|
45
|
+
* @returns The McCabe cyclomatic complexity (always ≥ 1).
|
|
46
|
+
*/
|
|
47
|
+
export declare function computeCyclomaticComplexity(root: AstNode, languageConfig: LanguageConfig): number;
|
|
48
|
+
//# sourceMappingURL=cyclomatic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cyclomatic.d.ts","sourceRoot":"","sources":["../../src/ast/cyclomatic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAiBjG"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic cyclomatic complexity walker.
|
|
3
|
+
*
|
|
4
|
+
* Given a tree-sitter subtree rooted at a function node, this module walks
|
|
5
|
+
* the tree and returns the McCabe cyclomatic complexity number. The
|
|
6
|
+
* algorithm is the standard "1 + (branching nodes + short-circuit operators)"
|
|
7
|
+
* formulation:
|
|
8
|
+
*
|
|
9
|
+
* CC = 1
|
|
10
|
+
* + count of branching-statement nodes (if, while, for, case, catch, ...)
|
|
11
|
+
* + count of short-circuit operators (&&, ||, ??, `and`, `or`)
|
|
12
|
+
* + count of ternary expressions (counted as branching nodes)
|
|
13
|
+
*
|
|
14
|
+
* The baseline of 1 represents the straight-line path through the function.
|
|
15
|
+
* Every additional branching point multiplies the set of reachable paths.
|
|
16
|
+
*
|
|
17
|
+
* The walker is deliberately language-agnostic: it consults the
|
|
18
|
+
* {@link LanguageConfig} passed in to decide which node types to count.
|
|
19
|
+
* Nested functions inside the subtree are **skipped** — each function's
|
|
20
|
+
* complexity is reported independently by {@link TreeSitterEngine}.
|
|
21
|
+
*
|
|
22
|
+
* @module ast/cyclomatic
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Compute the cyclomatic complexity of a function subtree.
|
|
26
|
+
*
|
|
27
|
+
* @param root Node rooted at the function (method, arrow, lambda, ...).
|
|
28
|
+
* @param languageConfig Language classification tables for node types.
|
|
29
|
+
* @returns The McCabe cyclomatic complexity (always ≥ 1).
|
|
30
|
+
*/
|
|
31
|
+
export function computeCyclomaticComplexity(root, languageConfig) {
|
|
32
|
+
let complexity = 1;
|
|
33
|
+
walk(root, languageConfig, true, (node) => {
|
|
34
|
+
if (languageConfig.branchingNodeTypes.has(node.type)) {
|
|
35
|
+
complexity += 1;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Boolean / short-circuit operators are usually represented as
|
|
39
|
+
// "binary_expression" nodes with an operator token child. To avoid
|
|
40
|
+
// coupling to a specific grammar's node shape, we inspect the raw
|
|
41
|
+
// text of the node's direct operator child.
|
|
42
|
+
if (isBooleanExpression(node, languageConfig)) {
|
|
43
|
+
complexity += 1;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return complexity;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Depth-first walk that skips any nested function subtree so that its
|
|
51
|
+
* complexity is not attributed to the enclosing function.
|
|
52
|
+
*
|
|
53
|
+
* @param node Current node being visited.
|
|
54
|
+
* @param languageConfig Language tables used to detect nested functions.
|
|
55
|
+
* @param isRoot `true` for the starting node (we do not skip it).
|
|
56
|
+
* @param visit Callback invoked for every non-function descendant.
|
|
57
|
+
*/
|
|
58
|
+
function walk(node, languageConfig, isRoot, visit) {
|
|
59
|
+
if (!isRoot && languageConfig.functionNodeTypes.has(node.type)) {
|
|
60
|
+
// Nested function — stop the walk here. Its complexity is reported
|
|
61
|
+
// separately when the engine iterates the top-level function list.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
visit(node);
|
|
65
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
66
|
+
const child = node.child(i);
|
|
67
|
+
if (child)
|
|
68
|
+
walk(child, languageConfig, false, visit);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Return `true` when `node` is a boolean / short-circuit expression that
|
|
73
|
+
* should add one to the cyclomatic complexity. We inspect the node's
|
|
74
|
+
* immediate children for an operator token whose text matches one of the
|
|
75
|
+
* language's short-circuit operators.
|
|
76
|
+
*
|
|
77
|
+
* This is a heuristic — grammars differ in how they represent operators
|
|
78
|
+
* — but it is stable enough for the five supported languages because:
|
|
79
|
+
*
|
|
80
|
+
* - JavaScript / TypeScript / Java: binary expression with `"&&"` or `"||"` token child.
|
|
81
|
+
* - Python: `boolean_operator` node with `"and"` / `"or"` token child.
|
|
82
|
+
* - C#: binary expression with `"&&"`, `"||"`, or `"??"` token child.
|
|
83
|
+
*
|
|
84
|
+
* @param node Candidate node.
|
|
85
|
+
* @param languageConfig Tables with the language's boolean operator set.
|
|
86
|
+
* @returns `true` when the node is a counted boolean expression.
|
|
87
|
+
*/
|
|
88
|
+
function isBooleanExpression(node, languageConfig) {
|
|
89
|
+
// Common type names across supported grammars. We check the type first
|
|
90
|
+
// to avoid scanning text for every node in the tree.
|
|
91
|
+
if (node.type !== "binary_expression" &&
|
|
92
|
+
node.type !== "boolean_operator" &&
|
|
93
|
+
node.type !== "logical_expression") {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
97
|
+
const child = node.child(i);
|
|
98
|
+
if (!child)
|
|
99
|
+
continue;
|
|
100
|
+
if (languageConfig.booleanOperators.includes(child.text)) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=cyclomatic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cyclomatic.js","sourceRoot":"","sources":["../../src/ast/cyclomatic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAoBH;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAa,EAAE,cAA8B;IACvF,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;QACxC,IAAI,cAAc,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrD,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QACD,+DAA+D;QAC/D,mEAAmE;QACnE,kEAAkE;QAClE,4CAA4C;QAC5C,IAAI,mBAAmB,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC;YAC9C,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,IAAI,CACX,IAAa,EACb,cAA8B,EAC9B,MAAe,EACf,KAA2B;IAE3B,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,mEAAmE;QACnE,mEAAmE;QACnE,OAAO;IACT,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,KAAK;YAAE,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,mBAAmB,CAAC,IAAa,EAAE,cAA8B;IACxE,uEAAuE;IACvE,qDAAqD;IACrD,IACE,IAAI,CAAC,IAAI,KAAK,mBAAmB;QACjC,IAAI,CAAC,IAAI,KAAK,kBAAkB;QAChC,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,cAAc,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public SDK entry point for the tree-sitter based AST engine and the
|
|
3
|
+
* cyclomatic complexity walker.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* import {
|
|
9
|
+
* TreeSitterEngine,
|
|
10
|
+
* computeCyclomaticComplexity,
|
|
11
|
+
* detectLanguageFromPath,
|
|
12
|
+
* type FileMetrics,
|
|
13
|
+
* type FunctionMetrics,
|
|
14
|
+
* type SupportedLanguage,
|
|
15
|
+
* } from "claude-crap/ast";
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @module ast
|
|
19
|
+
*/
|
|
20
|
+
export { TreeSitterEngine } from "./tree-sitter-engine.js";
|
|
21
|
+
export type { AnalyzeFileRequest, FileMetrics, FunctionMetrics, TreeSitterEngineOptions, } from "./tree-sitter-engine.js";
|
|
22
|
+
export { computeCyclomaticComplexity } from "./cyclomatic.js";
|
|
23
|
+
export type { AstNode } from "./cyclomatic.js";
|
|
24
|
+
export { LANGUAGE_TABLE, detectLanguageFromPath } from "./language-config.js";
|
|
25
|
+
export type { LanguageConfig, SupportedLanguage } from "./language-config.js";
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|