@optave/codegraph 3.10.0 → 3.11.1
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/README.md +40 -33
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +148 -99
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +23 -637
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +141 -98
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +28 -20
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +153 -80
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +18 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +72 -4
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +209 -137
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +108 -4
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +29 -33
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +198 -74
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +103 -87
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +55 -69
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.js +28 -36
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +10 -10
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/embed.ts +54 -4
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +223 -131
- package/src/domain/graph/builder/pipeline.ts +32 -785
- package/src/domain/graph/builder/stages/build-edges.ts +207 -142
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +29 -25
- package/src/domain/parser.ts +170 -67
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +75 -4
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +243 -153
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +103 -4
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +40 -39
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +191 -77
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +104 -82
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +59 -78
- package/src/extractors/verilog.ts +83 -15
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +56 -56
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +16 -1
|
@@ -235,30 +235,23 @@ interface EvaluateBoundariesOpts {
|
|
|
235
235
|
noTests?: boolean;
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const { valid, errors } = validateBoundaryConfig(boundaryConfig);
|
|
246
|
-
if (!valid) {
|
|
247
|
-
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const modules = resolveModules(boundaryConfig);
|
|
251
|
-
if (modules.size === 0) return { violations: [], violationCount: 0 };
|
|
252
|
-
|
|
253
|
-
let allRules: BoundaryRule[] = [];
|
|
254
|
-
if (boundaryConfig.preset) {
|
|
255
|
-
allRules = generatePresetRules(modules, boundaryConfig.preset);
|
|
256
|
-
}
|
|
238
|
+
function collectAllRules(
|
|
239
|
+
boundaryConfig: BoundaryConfig,
|
|
240
|
+
modules: Map<string, ResolvedModule>,
|
|
241
|
+
): BoundaryRule[] {
|
|
242
|
+
const rules: BoundaryRule[] = boundaryConfig.preset
|
|
243
|
+
? generatePresetRules(modules, boundaryConfig.preset)
|
|
244
|
+
: [];
|
|
257
245
|
if (boundaryConfig.rules && Array.isArray(boundaryConfig.rules)) {
|
|
258
|
-
|
|
246
|
+
return rules.concat(boundaryConfig.rules);
|
|
259
247
|
}
|
|
260
|
-
|
|
248
|
+
return rules;
|
|
249
|
+
}
|
|
261
250
|
|
|
251
|
+
function loadImportEdges(
|
|
252
|
+
db: BetterSqlite3Database,
|
|
253
|
+
opts: EvaluateBoundariesOpts,
|
|
254
|
+
): Array<{ source: string; target: string }> {
|
|
262
255
|
let edges: Array<{ source: string; target: string }>;
|
|
263
256
|
try {
|
|
264
257
|
edges = db
|
|
@@ -281,38 +274,63 @@ export function evaluateBoundaries(
|
|
|
281
274
|
const scope = new Set(opts.scopeFiles);
|
|
282
275
|
edges = edges.filter((e) => scope.has(e.source));
|
|
283
276
|
}
|
|
277
|
+
return edges;
|
|
278
|
+
}
|
|
284
279
|
|
|
285
|
-
|
|
280
|
+
function ruleViolated(rule: BoundaryRule, toModule: string): boolean {
|
|
281
|
+
if (rule.notTo?.includes(toModule)) return true;
|
|
282
|
+
if (rule.onlyTo && !rule.onlyTo.includes(toModule)) return true;
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
286
285
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
function emitEdgeViolations(
|
|
287
|
+
edge: { source: string; target: string },
|
|
288
|
+
fromModule: string,
|
|
289
|
+
toModule: string,
|
|
290
|
+
allRules: BoundaryRule[],
|
|
291
|
+
violations: BoundaryViolation[],
|
|
292
|
+
): void {
|
|
293
|
+
for (const rule of allRules) {
|
|
294
|
+
if (rule.from !== fromModule) continue;
|
|
295
|
+
if (!ruleViolated(rule, toModule)) continue;
|
|
296
|
+
violations.push({
|
|
297
|
+
rule: 'boundaries',
|
|
298
|
+
name: `${fromModule} -> ${toModule}`,
|
|
299
|
+
file: edge.source,
|
|
300
|
+
targetFile: edge.target,
|
|
301
|
+
message: rule.message || `${fromModule} must not depend on ${toModule}`,
|
|
302
|
+
value: 1,
|
|
303
|
+
threshold: 0,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
290
307
|
|
|
291
|
-
|
|
308
|
+
export function evaluateBoundaries(
|
|
309
|
+
db: BetterSqlite3Database,
|
|
310
|
+
boundaryConfig: BoundaryConfig | undefined,
|
|
311
|
+
opts: EvaluateBoundariesOpts = {},
|
|
312
|
+
): { violations: BoundaryViolation[]; violationCount: number } {
|
|
313
|
+
if (!boundaryConfig) return { violations: [], violationCount: 0 };
|
|
292
314
|
|
|
293
|
-
|
|
294
|
-
|
|
315
|
+
const { valid, errors } = validateBoundaryConfig(boundaryConfig);
|
|
316
|
+
if (!valid) {
|
|
317
|
+
throw new BoundaryError(`Invalid boundary configuration: ${errors.join('; ')}`);
|
|
318
|
+
}
|
|
295
319
|
|
|
296
|
-
|
|
320
|
+
const modules = resolveModules(boundaryConfig);
|
|
321
|
+
if (modules.size === 0) return { violations: [], violationCount: 0 };
|
|
297
322
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} else if (rule.onlyTo && !rule.onlyTo.includes(toModule)) {
|
|
301
|
-
isViolation = true;
|
|
302
|
-
}
|
|
323
|
+
const allRules = collectAllRules(boundaryConfig, modules);
|
|
324
|
+
if (allRules.length === 0) return { violations: [], violationCount: 0 };
|
|
303
325
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
threshold: 0,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
326
|
+
const edges = loadImportEdges(db, opts);
|
|
327
|
+
const violations: BoundaryViolation[] = [];
|
|
328
|
+
|
|
329
|
+
for (const edge of edges) {
|
|
330
|
+
const fromModule = classifyFile(edge.source, modules);
|
|
331
|
+
const toModule = classifyFile(edge.target, modules);
|
|
332
|
+
if (!fromModule || !toModule) continue;
|
|
333
|
+
emitEdgeViolations(edge, fromModule, toModule, allRules, violations);
|
|
316
334
|
}
|
|
317
335
|
|
|
318
336
|
return { violations, violationCount: violations.length };
|
package/src/features/cfg.ts
CHANGED
|
@@ -18,7 +18,13 @@ import {
|
|
|
18
18
|
} from '../db/index.js';
|
|
19
19
|
import { debug, info } from '../infrastructure/logger.js';
|
|
20
20
|
import { paginateResult } from '../shared/paginate.js';
|
|
21
|
-
import type {
|
|
21
|
+
import type {
|
|
22
|
+
BetterSqlite3Database,
|
|
23
|
+
CfgRulesConfig,
|
|
24
|
+
Definition,
|
|
25
|
+
NodeRow,
|
|
26
|
+
TreeSitterNode,
|
|
27
|
+
} from '../types.js';
|
|
22
28
|
import { findNodes } from './shared/find-nodes.js';
|
|
23
29
|
|
|
24
30
|
export { _makeCfgRules as makeCfgRules, CFG_RULES };
|
|
@@ -122,9 +128,8 @@ async function initCfgParsers(
|
|
|
122
128
|
let getParserFn: unknown = null;
|
|
123
129
|
|
|
124
130
|
if (needsFallback) {
|
|
125
|
-
const { createParsers } = await import('../domain/parser.js');
|
|
126
|
-
parsers = await createParsers();
|
|
127
131
|
const mod = await import('../domain/parser.js');
|
|
132
|
+
parsers = await mod.createParsers();
|
|
128
133
|
getParserFn = mod.getParser;
|
|
129
134
|
}
|
|
130
135
|
|
|
@@ -187,7 +192,7 @@ interface VisitorCfgResult {
|
|
|
187
192
|
|
|
188
193
|
function buildVisitorCfgMap(
|
|
189
194
|
tree: { rootNode: TreeSitterNode } | null | undefined,
|
|
190
|
-
cfgRules:
|
|
195
|
+
cfgRules: CfgRulesConfig,
|
|
191
196
|
symbols: FileSymbols,
|
|
192
197
|
langId: string,
|
|
193
198
|
): Map<number, VisitorCfgResult[]> | null {
|
|
@@ -203,9 +208,8 @@ function buildVisitorCfgMap(
|
|
|
203
208
|
if (!needsVisitor) return null;
|
|
204
209
|
|
|
205
210
|
const visitor = createCfgVisitor(cfgRules);
|
|
206
|
-
const typedRules = cfgRules as { functionNodes: string[] };
|
|
207
211
|
const walkerOpts = {
|
|
208
|
-
functionNodeTypes: new Set(
|
|
212
|
+
functionNodeTypes: new Set(cfgRules.functionNodes),
|
|
209
213
|
nestingNodeTypes: new Set<string>(),
|
|
210
214
|
getFunctionName: (node: TreeSitterNode) => {
|
|
211
215
|
const nameNode = node.childForFieldName?.('name');
|
|
@@ -365,79 +369,91 @@ function persistVisitorFileCfg(
|
|
|
365
369
|
return count;
|
|
366
370
|
}
|
|
367
371
|
|
|
368
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Build a single native bulk-insert entry for one definition.
|
|
374
|
+
* Returns null when the def has no CFG blocks or no associated node row.
|
|
375
|
+
*/
|
|
376
|
+
function buildNativeCfgEntry(
|
|
369
377
|
db: BetterSqlite3Database,
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
def: Definition,
|
|
379
|
+
relPath: string,
|
|
380
|
+
): Record<string, unknown> | null {
|
|
381
|
+
if (def.kind !== 'function' && def.kind !== 'method') return null;
|
|
382
|
+
if (!def.line) return null;
|
|
383
|
+
|
|
384
|
+
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
385
|
+
if (!nodeId) return null;
|
|
386
|
+
|
|
387
|
+
const cfg = def.cfg as { blocks?: CfgBuildBlock[]; edges?: CfgBuildEdge[] } | undefined;
|
|
388
|
+
if (!cfg?.blocks?.length) return null;
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
nodeId,
|
|
392
|
+
blocks: cfg.blocks.map((b) => ({
|
|
393
|
+
index: b.index,
|
|
394
|
+
blockType: b.type,
|
|
395
|
+
startLine: b.startLine ?? undefined,
|
|
396
|
+
endLine: b.endLine ?? undefined,
|
|
397
|
+
label: b.label ?? undefined,
|
|
398
|
+
})),
|
|
399
|
+
edges: (cfg.edges || []).map((e) => ({
|
|
400
|
+
sourceIndex: e.sourceIndex,
|
|
401
|
+
targetIndex: e.targetIndex,
|
|
402
|
+
kind: e.kind,
|
|
403
|
+
})),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
381
406
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Native bulk-insert fast path. The Rust bulkInsertCfg handles
|
|
409
|
+
* delete-before-insert atomically on a single rusqlite connection, so there
|
|
410
|
+
* is no dual-connection WAL conflict. Returns true if this path handled the
|
|
411
|
+
* request (caller should return early); false to fall through to WASM/JS.
|
|
412
|
+
*/
|
|
413
|
+
function tryNativeBulkInsertCfg(
|
|
414
|
+
db: BetterSqlite3Database,
|
|
415
|
+
fileSymbols: Map<string, FileSymbols>,
|
|
416
|
+
engineOpts:
|
|
417
|
+
| {
|
|
418
|
+
nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
|
|
419
|
+
suspendJsDb?: () => void;
|
|
420
|
+
resumeJsDb?: () => void;
|
|
421
|
+
}
|
|
422
|
+
| undefined,
|
|
423
|
+
): boolean {
|
|
385
424
|
const nativeDb = engineOpts?.nativeDb;
|
|
386
|
-
if (
|
|
387
|
-
const entries: Array<Record<string, unknown>> = [];
|
|
388
|
-
for (const [relPath, symbols] of fileSymbols) {
|
|
389
|
-
const ext = path.extname(relPath).toLowerCase();
|
|
390
|
-
if (!CFG_EXTENSIONS.has(ext)) continue;
|
|
425
|
+
if (!nativeDb?.bulkInsertCfg) return false;
|
|
391
426
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
397
|
-
if (!nodeId) continue;
|
|
398
|
-
|
|
399
|
-
const cfg = def.cfg as { blocks?: CfgBuildBlock[]; edges?: CfgBuildEdge[] } | undefined;
|
|
400
|
-
if (!cfg?.blocks?.length) continue;
|
|
401
|
-
|
|
402
|
-
entries.push({
|
|
403
|
-
nodeId,
|
|
404
|
-
blocks: cfg.blocks.map((b) => ({
|
|
405
|
-
index: b.index,
|
|
406
|
-
blockType: b.type,
|
|
407
|
-
startLine: b.startLine ?? undefined,
|
|
408
|
-
endLine: b.endLine ?? undefined,
|
|
409
|
-
label: b.label ?? undefined,
|
|
410
|
-
})),
|
|
411
|
-
edges: (cfg.edges || []).map((e) => ({
|
|
412
|
-
sourceIndex: e.sourceIndex,
|
|
413
|
-
targetIndex: e.targetIndex,
|
|
414
|
-
kind: e.kind,
|
|
415
|
-
})),
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
}
|
|
427
|
+
const entries: Array<Record<string, unknown>> = [];
|
|
428
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
429
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
430
|
+
if (!CFG_EXTENSIONS.has(ext)) continue;
|
|
419
431
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
engineOpts?.suspendJsDb?.();
|
|
424
|
-
inserted = nativeDb.bulkInsertCfg(entries);
|
|
425
|
-
} finally {
|
|
426
|
-
engineOpts?.resumeJsDb?.();
|
|
427
|
-
}
|
|
428
|
-
info(`CFG (native bulk): ${inserted} functions analyzed`);
|
|
432
|
+
for (const def of symbols.definitions) {
|
|
433
|
+
const entry = buildNativeCfgEntry(db, def, relPath);
|
|
434
|
+
if (entry) entries.push(entry);
|
|
429
435
|
}
|
|
430
|
-
return;
|
|
431
436
|
}
|
|
432
437
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
438
|
+
if (entries.length > 0) {
|
|
439
|
+
let inserted = 0;
|
|
440
|
+
try {
|
|
441
|
+
engineOpts?.suspendJsDb?.();
|
|
442
|
+
inserted = nativeDb.bulkInsertCfg(entries);
|
|
443
|
+
} finally {
|
|
444
|
+
engineOpts?.resumeJsDb?.();
|
|
445
|
+
}
|
|
446
|
+
info(`CFG (native bulk): ${inserted} functions analyzed`);
|
|
439
447
|
}
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
440
450
|
|
|
451
|
+
interface CfgInsertStatements {
|
|
452
|
+
insertBlock: ReturnType<BetterSqlite3Database['prepare']>;
|
|
453
|
+
insertEdge: ReturnType<BetterSqlite3Database['prepare']>;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function prepareCfgInsertStatements(db: BetterSqlite3Database): CfgInsertStatements {
|
|
441
457
|
const insertBlock = db.prepare(
|
|
442
458
|
`INSERT INTO cfg_blocks (function_node_id, block_index, block_type, start_line, end_line, label)
|
|
443
459
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -446,15 +462,31 @@ export async function buildCFGData(
|
|
|
446
462
|
`INSERT INTO cfg_edges (function_node_id, source_block_id, target_block_id, kind)
|
|
447
463
|
VALUES (?, ?, ?, ?)`,
|
|
448
464
|
);
|
|
449
|
-
|
|
465
|
+
return { insertBlock, insertEdge };
|
|
466
|
+
}
|
|
450
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Persist CFG for every CFG-eligible file inside a single transaction.
|
|
470
|
+
* Dispatches to native fast path or visitor path per file.
|
|
471
|
+
*/
|
|
472
|
+
function persistAllFileCfgs(
|
|
473
|
+
db: BetterSqlite3Database,
|
|
474
|
+
fileSymbols: Map<string, FileSymbols>,
|
|
475
|
+
rootDir: string,
|
|
476
|
+
allNative: boolean,
|
|
477
|
+
extToLang: Map<string, string>,
|
|
478
|
+
parsers: unknown,
|
|
479
|
+
getParserFn: unknown,
|
|
480
|
+
stmts: CfgInsertStatements,
|
|
481
|
+
): number {
|
|
482
|
+
let analyzed = 0;
|
|
451
483
|
const tx = db.transaction(() => {
|
|
452
484
|
for (const [relPath, symbols] of fileSymbols) {
|
|
453
485
|
const ext = path.extname(relPath).toLowerCase();
|
|
454
486
|
if (!CFG_EXTENSIONS.has(ext)) continue;
|
|
455
487
|
|
|
456
488
|
if (allNative && !symbols._tree) {
|
|
457
|
-
analyzed += persistNativeFileCfg(db, symbols, relPath, insertBlock, insertEdge);
|
|
489
|
+
analyzed += persistNativeFileCfg(db, symbols, relPath, stmts.insertBlock, stmts.insertEdge);
|
|
458
490
|
continue;
|
|
459
491
|
}
|
|
460
492
|
|
|
@@ -466,13 +498,52 @@ export async function buildCFGData(
|
|
|
466
498
|
extToLang,
|
|
467
499
|
parsers,
|
|
468
500
|
getParserFn,
|
|
469
|
-
insertBlock,
|
|
470
|
-
insertEdge,
|
|
501
|
+
stmts.insertBlock,
|
|
502
|
+
stmts.insertEdge,
|
|
471
503
|
);
|
|
472
504
|
}
|
|
473
505
|
});
|
|
474
|
-
|
|
475
506
|
tx();
|
|
507
|
+
return analyzed;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export async function buildCFGData(
|
|
511
|
+
db: BetterSqlite3Database,
|
|
512
|
+
fileSymbols: Map<string, FileSymbols>,
|
|
513
|
+
rootDir: string,
|
|
514
|
+
engineOpts?: {
|
|
515
|
+
nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
|
|
516
|
+
suspendJsDb?: () => void;
|
|
517
|
+
resumeJsDb?: () => void;
|
|
518
|
+
},
|
|
519
|
+
): Promise<void> {
|
|
520
|
+
// Fast path: when all function/method defs already have native CFG data,
|
|
521
|
+
// skip WASM parser init, tree parsing, and JS visitor entirely — just persist.
|
|
522
|
+
const allNative = allCfgNative(fileSymbols);
|
|
523
|
+
|
|
524
|
+
if (allNative && tryNativeBulkInsertCfg(db, fileSymbols, engineOpts)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const extToLang = buildExtToLangMap();
|
|
529
|
+
let parsers: unknown = null;
|
|
530
|
+
let getParserFn: unknown = null;
|
|
531
|
+
|
|
532
|
+
if (!allNative) {
|
|
533
|
+
({ parsers, getParserFn } = await initCfgParsers(fileSymbols));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const stmts = prepareCfgInsertStatements(db);
|
|
537
|
+
const analyzed = persistAllFileCfgs(
|
|
538
|
+
db,
|
|
539
|
+
fileSymbols,
|
|
540
|
+
rootDir,
|
|
541
|
+
allNative,
|
|
542
|
+
extToLang,
|
|
543
|
+
parsers,
|
|
544
|
+
getParserFn,
|
|
545
|
+
stmts,
|
|
546
|
+
);
|
|
476
547
|
|
|
477
548
|
if (analyzed > 0) {
|
|
478
549
|
info(`CFG: ${analyzed} functions analyzed`);
|
package/src/features/check.ts
CHANGED
|
@@ -22,6 +22,29 @@ interface ParsedDiff {
|
|
|
22
22
|
newFiles: Set<string>;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
26
|
+
const NEW_FILE_RE = /^\+\+\+ b\/(.+)/;
|
|
27
|
+
|
|
28
|
+
function pushHunkRanges(
|
|
29
|
+
line: string,
|
|
30
|
+
currentFile: string,
|
|
31
|
+
changedRanges: Map<string, DiffRange[]>,
|
|
32
|
+
oldRanges: Map<string, DiffRange[]>,
|
|
33
|
+
): void {
|
|
34
|
+
const hunkMatch = line.match(HUNK_RE);
|
|
35
|
+
if (!hunkMatch) return;
|
|
36
|
+
const oldStart = parseInt(hunkMatch[1]!, 10);
|
|
37
|
+
const oldCount = parseInt(hunkMatch[2] || '1', 10);
|
|
38
|
+
if (oldCount > 0) {
|
|
39
|
+
oldRanges.get(currentFile)!.push({ start: oldStart, end: oldStart + oldCount - 1 });
|
|
40
|
+
}
|
|
41
|
+
const newStart = parseInt(hunkMatch[3]!, 10);
|
|
42
|
+
const newCount = parseInt(hunkMatch[4] || '1', 10);
|
|
43
|
+
if (newCount > 0) {
|
|
44
|
+
changedRanges.get(currentFile)!.push({ start: newStart, end: newStart + newCount - 1 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
25
48
|
export function parseDiffOutput(diffOutput: string): ParsedDiff {
|
|
26
49
|
const changedRanges = new Map<string, DiffRange[]>();
|
|
27
50
|
const oldRanges = new Map<string, DiffRange[]>();
|
|
@@ -38,7 +61,7 @@ export function parseDiffOutput(diffOutput: string): ParsedDiff {
|
|
|
38
61
|
prevIsDevNull = false;
|
|
39
62
|
continue;
|
|
40
63
|
}
|
|
41
|
-
const fileMatch = line.match(
|
|
64
|
+
const fileMatch = line.match(NEW_FILE_RE);
|
|
42
65
|
if (fileMatch) {
|
|
43
66
|
currentFile = fileMatch[1]!;
|
|
44
67
|
if (!changedRanges.has(currentFile)) changedRanges.set(currentFile, []);
|
|
@@ -47,19 +70,7 @@ export function parseDiffOutput(diffOutput: string): ParsedDiff {
|
|
|
47
70
|
prevIsDevNull = false;
|
|
48
71
|
continue;
|
|
49
72
|
}
|
|
50
|
-
|
|
51
|
-
if (hunkMatch && currentFile) {
|
|
52
|
-
const oldStart = parseInt(hunkMatch[1]!, 10);
|
|
53
|
-
const oldCount = parseInt(hunkMatch[2] || '1', 10);
|
|
54
|
-
if (oldCount > 0) {
|
|
55
|
-
oldRanges.get(currentFile)!.push({ start: oldStart, end: oldStart + oldCount - 1 });
|
|
56
|
-
}
|
|
57
|
-
const newStart = parseInt(hunkMatch[3]!, 10);
|
|
58
|
-
const newCount = parseInt(hunkMatch[4] || '1', 10);
|
|
59
|
-
if (newCount > 0) {
|
|
60
|
-
changedRanges.get(currentFile)!.push({ start: newStart, end: newStart + newCount - 1 });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
73
|
+
if (currentFile) pushHunkRanges(line, currentFile, changedRanges, oldRanges);
|
|
63
74
|
}
|
|
64
75
|
return { changedRanges, oldRanges, newFiles };
|
|
65
76
|
}
|
|
@@ -96,6 +107,26 @@ interface BlastRadiusResult {
|
|
|
96
107
|
violations: BlastRadiusViolation[];
|
|
97
108
|
}
|
|
98
109
|
|
|
110
|
+
type DefRow = {
|
|
111
|
+
id: number;
|
|
112
|
+
name: string;
|
|
113
|
+
kind: string;
|
|
114
|
+
file: string;
|
|
115
|
+
line: number;
|
|
116
|
+
end_line: number | null;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function rangesOverlap(defLine: number, endLine: number, ranges: DiffRange[]): boolean {
|
|
120
|
+
for (const range of ranges) {
|
|
121
|
+
if (range.start <= endLine && range.end >= defLine) return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function defEndLine(def: DefRow, nextDef: DefRow | undefined): number {
|
|
127
|
+
return def.end_line || (nextDef ? nextDef.line - 1 : 999999);
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
export function checkMaxBlastRadius(
|
|
100
131
|
db: BetterSqlite3Database,
|
|
101
132
|
changedRanges: Map<string, DiffRange[]>,
|
|
@@ -105,34 +136,18 @@ export function checkMaxBlastRadius(
|
|
|
105
136
|
): BlastRadiusResult {
|
|
106
137
|
const violations: BlastRadiusViolation[] = [];
|
|
107
138
|
let maxFound = 0;
|
|
139
|
+
const defsStmt = db.prepare(
|
|
140
|
+
`SELECT * FROM nodes WHERE file = ? AND kind IN ('function', 'method', 'class') ORDER BY line`,
|
|
141
|
+
);
|
|
108
142
|
|
|
109
143
|
for (const [file, ranges] of changedRanges) {
|
|
110
144
|
if (noTests && isTestFile(file)) continue;
|
|
111
|
-
const defs =
|
|
112
|
-
.prepare(
|
|
113
|
-
`SELECT * FROM nodes WHERE file = ? AND kind IN ('function', 'method', 'class') ORDER BY line`,
|
|
114
|
-
)
|
|
115
|
-
.all(file) as Array<{
|
|
116
|
-
id: number;
|
|
117
|
-
name: string;
|
|
118
|
-
kind: string;
|
|
119
|
-
file: string;
|
|
120
|
-
line: number;
|
|
121
|
-
end_line: number | null;
|
|
122
|
-
}>;
|
|
145
|
+
const defs = defsStmt.all(file) as DefRow[];
|
|
123
146
|
|
|
124
147
|
for (let i = 0; i < defs.length; i++) {
|
|
125
148
|
const def = defs[i]!;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
let overlaps = false;
|
|
129
|
-
for (const range of ranges) {
|
|
130
|
-
if (range.start <= endLine && range.end >= def.line) {
|
|
131
|
-
overlaps = true;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (!overlaps) continue;
|
|
149
|
+
const endLine = defEndLine(def, defs[i + 1]);
|
|
150
|
+
if (!rangesOverlap(def.line, endLine, ranges)) continue;
|
|
136
151
|
|
|
137
152
|
const { totalDependents: totalCallers } = bfsTransitiveCallers(db, def.id, {
|
|
138
153
|
noTests,
|
|
@@ -364,11 +379,13 @@ function runPredicates(
|
|
|
364
379
|
return predicates;
|
|
365
380
|
}
|
|
366
381
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
382
|
+
function makeEmptyCheck(): CheckResult {
|
|
383
|
+
return {
|
|
384
|
+
predicates: [],
|
|
385
|
+
summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
|
|
386
|
+
passed: true,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
372
389
|
|
|
373
390
|
export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}): CheckResult {
|
|
374
391
|
const db = openReadonlyOrFail(customDbPath);
|
|
@@ -394,10 +411,10 @@ export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}
|
|
|
394
411
|
return { error: `Failed to run git diff: ${(e as Error).message}` };
|
|
395
412
|
}
|
|
396
413
|
|
|
397
|
-
if (!diffOutput.trim()) return
|
|
414
|
+
if (!diffOutput.trim()) return makeEmptyCheck();
|
|
398
415
|
|
|
399
416
|
const diff = parseDiffOutput(diffOutput);
|
|
400
|
-
if (diff.changedRanges.size === 0) return
|
|
417
|
+
if (diff.changedRanges.size === 0) return makeEmptyCheck();
|
|
401
418
|
|
|
402
419
|
const predicates = runPredicates(db, diff, flags, repoRoot, noTests, maxDepth);
|
|
403
420
|
|