al-sem 0.0.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/LICENSE +21 -0
- package/README.md +361 -0
- package/package.json +64 -0
- package/scripts/d40-diff.ts +44 -0
- package/scripts/fetch-native-parser.ts +179 -0
- package/scripts/precision-sample.ts +99 -0
- package/scripts/precision-study.ts +42 -0
- package/scripts/precision-tabulate.ts +52 -0
- package/src/cli/baseline.ts +31 -0
- package/src/cli/diff.ts +199 -0
- package/src/cli/events-chains.ts +56 -0
- package/src/cli/events-fanout.ts +87 -0
- package/src/cli/exit-code.ts +30 -0
- package/src/cli/fingerprint-indexes.ts +130 -0
- package/src/cli/fingerprint-query.ts +543 -0
- package/src/cli/fingerprint-witness.ts +493 -0
- package/src/cli/fingerprint.ts +292 -0
- package/src/cli/format-compact-json.ts +45 -0
- package/src/cli/format-events.ts +77 -0
- package/src/cli/format-fingerprint.ts +295 -0
- package/src/cli/format-html.ts +503 -0
- package/src/cli/format-json.ts +13 -0
- package/src/cli/format-policy.ts +95 -0
- package/src/cli/format-sarif.ts +186 -0
- package/src/cli/format-terminal.ts +153 -0
- package/src/cli/index.ts +566 -0
- package/src/cli/policy.ts +204 -0
- package/src/config/roots-config.ts +302 -0
- package/src/deps/cache-versions.ts +74 -0
- package/src/deps/canonical-json.ts +27 -0
- package/src/deps/dependency-artifact.ts +144 -0
- package/src/deps/dependency-cache.ts +262 -0
- package/src/deps/dependency-dag.ts +128 -0
- package/src/deps/dependency-package-discovery.ts +85 -0
- package/src/deps/dependency-pipeline.ts +483 -0
- package/src/deps/dependency-projection.ts +211 -0
- package/src/deps/dependency-resolver.ts +154 -0
- package/src/deps/workspace-dependencies.ts +114 -0
- package/src/detectors/capability-query.ts +145 -0
- package/src/detectors/confidence.ts +52 -0
- package/src/detectors/d1-db-op-in-loop.ts +457 -0
- package/src/detectors/d10-self-modifying-loop.ts +114 -0
- package/src/detectors/d11-modify-without-get.ts +129 -0
- package/src/detectors/d12-dead-integration-event.ts +81 -0
- package/src/detectors/d13-cross-app-internal-call.ts +105 -0
- package/src/detectors/d14-dead-routine.ts +151 -0
- package/src/detectors/d16-obsolete-routine-call.ts +94 -0
- package/src/detectors/d17-min-version-drift.ts +157 -0
- package/src/detectors/d18-constant-filter-in-loop.ts +151 -0
- package/src/detectors/d19-unused-parameter.ts +116 -0
- package/src/detectors/d2-event-fanout-in-loop.ts +240 -0
- package/src/detectors/d20-unreachable-after-exit.ts +92 -0
- package/src/detectors/d21-read-without-load.ts +128 -0
- package/src/detectors/d22-flowfield-without-calcfields.ts +168 -0
- package/src/detectors/d29-subscriber-modify-on-event-record.ts +163 -0
- package/src/detectors/d3-load-state.ts +72 -0
- package/src/detectors/d3-missing-setloadfields.ts +234 -0
- package/src/detectors/d32-constant-boolean-parameter.ts +185 -0
- package/src/detectors/d33-unfiltered-bulk-write.ts +173 -0
- package/src/detectors/d34-commit-in-loop.ts +206 -0
- package/src/detectors/d35-commit-in-event-subscriber.ts +138 -0
- package/src/detectors/d36-late-setloadfields.ts +162 -0
- package/src/detectors/d37-validate-without-persist.ts +271 -0
- package/src/detectors/d38-subscriber-to-obsolete-event.ts +140 -0
- package/src/detectors/d39-record-left-dirty-across-chain.ts +165 -0
- package/src/detectors/d4-repeated-lookup-in-loop.ts +128 -0
- package/src/detectors/d40-transitive-load-missing.ts +217 -0
- package/src/detectors/d41-transitive-filter-loss.ts +200 -0
- package/src/detectors/d42-cross-call-wrong-setloadfields.ts +243 -0
- package/src/detectors/d43-event-ishandled-skip.ts +257 -0
- package/src/detectors/d44-event-multi-subscriber-overlap.ts +223 -0
- package/src/detectors/d45-event-transitive-table-exposure.ts +159 -0
- package/src/detectors/d5-set-based-opportunity.ts +162 -0
- package/src/detectors/d7-recursive-event-expansion.ts +151 -0
- package/src/detectors/d8-commit-in-transaction.ts +132 -0
- package/src/detectors/d9-transaction-span-summary.ts +107 -0
- package/src/detectors/detector-context.ts +121 -0
- package/src/detectors/finding-grouping.ts +61 -0
- package/src/detectors/path-merge.ts +174 -0
- package/src/detectors/registry.ts +176 -0
- package/src/detectors/table-display.ts +42 -0
- package/src/diff/diff-abi.ts +195 -0
- package/src/diff/diff-capabilities.ts +179 -0
- package/src/diff/diff-engine.ts +146 -0
- package/src/diff/diff-events.ts +323 -0
- package/src/diff/diff-identity.ts +73 -0
- package/src/diff/diff-indexes.ts +199 -0
- package/src/diff/diff-permissions.ts +260 -0
- package/src/diff/diff-policy.ts +101 -0
- package/src/diff/diff-preflight.ts +66 -0
- package/src/diff/diff-renames.ts +104 -0
- package/src/diff/diff-schema.ts +232 -0
- package/src/diff/format-diff.ts +148 -0
- package/src/engine/attribute-parser.ts +50 -0
- package/src/engine/capability-cone.ts +531 -0
- package/src/engine/combined-graph.ts +357 -0
- package/src/engine/control-flow-walker.ts +1317 -0
- package/src/engine/dispatch-sites.ts +199 -0
- package/src/engine/effect-lattice.ts +81 -0
- package/src/engine/entry-points.ts +57 -0
- package/src/engine/event-flow.ts +524 -0
- package/src/engine/event-relay.ts +92 -0
- package/src/engine/op-classification.ts +92 -0
- package/src/engine/path-walker.ts +189 -0
- package/src/engine/reverse-call-graph.ts +23 -0
- package/src/engine/root-classifier-overlay.ts +194 -0
- package/src/engine/root-classifier.ts +135 -0
- package/src/engine/scc.ts +110 -0
- package/src/engine/source-anchor.ts +25 -0
- package/src/engine/summary-context.ts +104 -0
- package/src/engine/summary-engine.ts +296 -0
- package/src/engine/summary-runner.ts +560 -0
- package/src/engine/transaction-spans.ts +112 -0
- package/src/engine/uncertainty-util.ts +54 -0
- package/src/hash.ts +31 -0
- package/src/index/attribute-from-node.ts +141 -0
- package/src/index/callee-from-node.ts +181 -0
- package/src/index/capability/background.ts +90 -0
- package/src/index/capability/commit.ts +44 -0
- package/src/index/capability/dispatch.ts +164 -0
- package/src/index/capability/events.ts +65 -0
- package/src/index/capability/extractor.ts +124 -0
- package/src/index/capability/file-blob.ts +137 -0
- package/src/index/capability/http.ts +159 -0
- package/src/index/capability/hyperlink.ts +60 -0
- package/src/index/capability/isolated-storage.ts +179 -0
- package/src/index/capability/table.ts +113 -0
- package/src/index/capability/telemetry.ts +84 -0
- package/src/index/capability/ui.ts +55 -0
- package/src/index/capability/value-source.ts +202 -0
- package/src/index/expression-from-node.ts +117 -0
- package/src/index/indexer.ts +102 -0
- package/src/index/intraprocedural-body.ts +1467 -0
- package/src/index/intraprocedural-ops.ts +253 -0
- package/src/index/intraprocedural-refs.ts +188 -0
- package/src/index/object-indexer.ts +279 -0
- package/src/index/routine-indexer.ts +282 -0
- package/src/index/routine-signature.ts +46 -0
- package/src/index/variable-indexer.ts +134 -0
- package/src/index/variable-initializer-extractor.ts +155 -0
- package/src/index/variable-type-normalizer.ts +83 -0
- package/src/index.ts +267 -0
- package/src/mcp/server.ts +72 -0
- package/src/mcp/session.ts +49 -0
- package/src/mcp/tools/explain-path.ts +75 -0
- package/src/mcp/tools/get-analysis-health.ts +62 -0
- package/src/mcp/tools/get-finding.ts +47 -0
- package/src/mcp/tools/get-routine-summary.ts +126 -0
- package/src/mcp/tools/list-findings.ts +85 -0
- package/src/mcp/tools/list-hotspots.ts +78 -0
- package/src/mcp/tools/list-rollups.ts +103 -0
- package/src/mcp/tools/validators.ts +25 -0
- package/src/model/attributes.ts +120 -0
- package/src/model/callee.ts +45 -0
- package/src/model/capability.ts +187 -0
- package/src/model/coverage.ts +85 -0
- package/src/model/entities.ts +628 -0
- package/src/model/expression.ts +98 -0
- package/src/model/finding.ts +110 -0
- package/src/model/graph-edge.ts +93 -0
- package/src/model/graph.ts +62 -0
- package/src/model/identity.ts +81 -0
- package/src/model/ids.ts +90 -0
- package/src/model/index.ts +13 -0
- package/src/model/model.ts +51 -0
- package/src/model/permission.ts +76 -0
- package/src/model/root-classification.ts +116 -0
- package/src/model/stable-identity.ts +102 -0
- package/src/model/summary.ts +96 -0
- package/src/parser/ast.ts +82 -0
- package/src/parser/native/ffi.ts +145 -0
- package/src/parser/native/parse-index-pool.ts +148 -0
- package/src/parser/native/parse-index-worker.ts +94 -0
- package/src/parser/native/wrapper.ts +353 -0
- package/src/parser/parser-init.ts +43 -0
- package/src/perf/profiler.ts +66 -0
- package/src/policy/policy-default.yaml +83 -0
- package/src/policy/policy-engine.ts +339 -0
- package/src/policy/policy-loader.ts +257 -0
- package/src/policy/policy-schema.json +379 -0
- package/src/policy/policy-types.ts +81 -0
- package/src/policy/predicate-compiler.ts +151 -0
- package/src/policy/predicate-evaluator.ts +267 -0
- package/src/policy/predicate-fields.ts +439 -0
- package/src/projection/actionable-anchor.ts +48 -0
- package/src/projection/finding-filters.ts +44 -0
- package/src/projection/finding-fingerprint.ts +54 -0
- package/src/projection/finding-groups.ts +41 -0
- package/src/projection/finding-summary.ts +110 -0
- package/src/projection/rollup-findings.ts +105 -0
- package/src/providers/discover.ts +88 -0
- package/src/providers/external.ts +46 -0
- package/src/providers/types.ts +36 -0
- package/src/providers/workspace.ts +117 -0
- package/src/resolve/call-resolver.ts +117 -0
- package/src/resolve/coverage.ts +61 -0
- package/src/resolve/event-graph.ts +166 -0
- package/src/resolve/implicit-edges.ts +53 -0
- package/src/resolve/record-types.ts +36 -0
- package/src/resolve/resolver.ts +23 -0
- package/src/resolve/semantic-graph.ts +29 -0
- package/src/resolve/symbol-table.ts +69 -0
- package/src/snapshot/app-snapshot.ts +74 -0
- package/src/snapshot/compose.ts +100 -0
- package/src/snapshot/derive/callsite-evidence.ts +76 -0
- package/src/snapshot/derive/capability-facts.ts +70 -0
- package/src/snapshot/derive/contracts.ts +131 -0
- package/src/snapshot/derive/coverage.ts +35 -0
- package/src/snapshot/derive/event-declarations.ts +140 -0
- package/src/snapshot/derive/identity-table.ts +58 -0
- package/src/snapshot/derive/inputs.ts +91 -0
- package/src/snapshot/derive/operation-evidence.ts +70 -0
- package/src/snapshot/derive/permissions.ts +186 -0
- package/src/snapshot/derive/root-classifications.ts +56 -0
- package/src/snapshot/derive/schema.ts +130 -0
- package/src/snapshot/derive/typed-edges.ts +60 -0
- package/src/snapshot/derive/workspace-fingerprint.ts +19 -0
- package/src/snapshot/deserialize.ts +40 -0
- package/src/snapshot/serialize-cbor-gz.ts +12 -0
- package/src/snapshot/serialize-cbor.ts +19 -0
- package/src/snapshot/serialize-json.ts +22 -0
- package/src/snapshot/shard.ts +134 -0
- package/src/snapshot/types.ts +181 -0
- package/src/symbols/app-manifest.ts +96 -0
- package/src/symbols/app-package-zip.ts +50 -0
- package/src/symbols/embedded-source-reader.ts +41 -0
- package/src/symbols/package-hash.ts +81 -0
- package/src/symbols/symbol-reader.ts +101 -0
- package/src/symbols/symbol-reference-parser.ts +378 -0
- package/src/symbols/symbol-reference-reader.ts +27 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { ParameterSymbol, RecordVariable, VariableSymbol } from "../model/entities.ts";
|
|
2
|
+
import type { SourceAnchor } from "../model/identity.ts";
|
|
3
|
+
import type { RoutineId } from "../model/ids.ts";
|
|
4
|
+
import type { Node } from "../parser/native/wrapper.ts";
|
|
5
|
+
import { extractInitializer } from "./variable-initializer-extractor.ts";
|
|
6
|
+
import { canonicalizeTypeText, normalizeDeclaredType } from "./variable-type-normalizer.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Build a unified `VariableSymbol[]` for a routine's lexical scope.
|
|
10
|
+
*
|
|
11
|
+
* Output order:
|
|
12
|
+
* 1. Parameters in declaration order (parameterIndex ascending).
|
|
13
|
+
* 2. Local var declarations in source order.
|
|
14
|
+
*
|
|
15
|
+
* Records appear here AND in the existing `IntraproceduralFeatures.recordVariables`
|
|
16
|
+
* for backward compatibility. The `recordVariables` argument is used to mirror
|
|
17
|
+
* `tableId` resolution onto record-typed `VariableSymbol` entries (so callers
|
|
18
|
+
* don't have to cross-reference).
|
|
19
|
+
*
|
|
20
|
+
* Parameters do NOT carry initializers (body assignments to parameters are
|
|
21
|
+
* re-assignments, not initial assignments — capturing them would muddy the
|
|
22
|
+
* "where does this var's value come from?" semantics).
|
|
23
|
+
*
|
|
24
|
+
* Never throws.
|
|
25
|
+
*/
|
|
26
|
+
export function extractVariables(
|
|
27
|
+
routineNode: Node,
|
|
28
|
+
routineId: RoutineId,
|
|
29
|
+
sourceUnitId: string,
|
|
30
|
+
parameters: ParameterSymbol[],
|
|
31
|
+
recordVariables: RecordVariable[],
|
|
32
|
+
): VariableSymbol[] {
|
|
33
|
+
try {
|
|
34
|
+
const recordByNameLc = new Map(recordVariables.map((rv) => [rv.name.toLowerCase(), rv]));
|
|
35
|
+
const out: VariableSymbol[] = [];
|
|
36
|
+
|
|
37
|
+
// 1. Parameters — one VariableSymbol per ParameterSymbol.
|
|
38
|
+
for (const p of parameters) {
|
|
39
|
+
const lcName = p.name.toLowerCase();
|
|
40
|
+
const recordMirror = recordByNameLc.get(lcName);
|
|
41
|
+
const sym: VariableSymbol = {
|
|
42
|
+
name: lcName,
|
|
43
|
+
declaredType: canonicalizeTypeText(p.typeText),
|
|
44
|
+
isParameter: true,
|
|
45
|
+
parameterIndex: p.index,
|
|
46
|
+
sourceAnchor: syntheticParamAnchor(routineId, sourceUnitId),
|
|
47
|
+
...(recordMirror?.tableId !== undefined ? { tableId: recordMirror.tableId } : {}),
|
|
48
|
+
};
|
|
49
|
+
out.push(sym);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. Locals — walk `var_section` blocks in the routine node.
|
|
53
|
+
const bodyNode = findCodeBlock(routineNode);
|
|
54
|
+
for (const decl of findVariableDeclarations(routineNode)) {
|
|
55
|
+
const nameNode = decl.childForFieldName?.("name") ?? decl.namedChild(0);
|
|
56
|
+
if (nameNode === null || nameNode === undefined) continue;
|
|
57
|
+
const nameText = (nameNode as { text?: unknown }).text;
|
|
58
|
+
if (typeof nameText !== "string") continue;
|
|
59
|
+
const lcName = nameText.toLowerCase();
|
|
60
|
+
|
|
61
|
+
// Skip parameter shadowing — parameters already emitted above.
|
|
62
|
+
if (out.some((v) => v.isParameter && v.name === lcName)) continue;
|
|
63
|
+
|
|
64
|
+
const declaredType = normalizeDeclaredType(decl);
|
|
65
|
+
const recordMirror = recordByNameLc.get(lcName);
|
|
66
|
+
const initializer = bodyNode !== null ? extractInitializer(bodyNode, lcName) : undefined;
|
|
67
|
+
|
|
68
|
+
const sym: VariableSymbol = {
|
|
69
|
+
name: lcName,
|
|
70
|
+
declaredType,
|
|
71
|
+
isParameter: false,
|
|
72
|
+
sourceAnchor: anchorFromNode(decl, sourceUnitId, routineId),
|
|
73
|
+
...(recordMirror?.tableId !== undefined ? { tableId: recordMirror.tableId } : {}),
|
|
74
|
+
...(initializer !== undefined ? { initializer } : {}),
|
|
75
|
+
};
|
|
76
|
+
out.push(sym);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return out;
|
|
80
|
+
} catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function findCodeBlock(routineNode: Node): Node | null {
|
|
86
|
+
for (let i = 0; i < routineNode.namedChildCount; i++) {
|
|
87
|
+
const c = routineNode.namedChild(i);
|
|
88
|
+
if (c === null) continue;
|
|
89
|
+
if (c.type === "code_block") return c;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findVariableDeclarations(routineNode: Node): Node[] {
|
|
95
|
+
const out: Node[] = [];
|
|
96
|
+
function visit(n: Node) {
|
|
97
|
+
if (n.type === "variable_declaration") {
|
|
98
|
+
out.push(n);
|
|
99
|
+
return; // don't descend into a found declaration
|
|
100
|
+
}
|
|
101
|
+
// Don't descend into the routine body — locals are declared in
|
|
102
|
+
// `var_section` at the routine level, not nested inside the body.
|
|
103
|
+
if (n.type === "code_block") return;
|
|
104
|
+
for (let i = 0; i < n.namedChildCount; i++) {
|
|
105
|
+
const c = n.namedChild(i);
|
|
106
|
+
if (c !== null) visit(c);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
visit(routineNode);
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function syntheticParamAnchor(routineId: RoutineId, sourceUnitId: string): SourceAnchor {
|
|
114
|
+
return {
|
|
115
|
+
sourceUnitId,
|
|
116
|
+
range: { startLine: 0, startColumn: 0, endLine: 0, endColumn: 0 },
|
|
117
|
+
enclosingRoutineId: routineId,
|
|
118
|
+
syntaxKind: "parameter",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function anchorFromNode(node: Node, sourceUnitId: string, routineId: RoutineId): SourceAnchor {
|
|
123
|
+
return {
|
|
124
|
+
sourceUnitId,
|
|
125
|
+
range: {
|
|
126
|
+
startLine: node.startPosition.row,
|
|
127
|
+
startColumn: node.startPosition.column,
|
|
128
|
+
endLine: node.endPosition.row,
|
|
129
|
+
endColumn: node.endPosition.column,
|
|
130
|
+
},
|
|
131
|
+
enclosingRoutineId: routineId,
|
|
132
|
+
syntaxKind: "variable_declaration",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { ValueSource } from "../model/capability.ts";
|
|
2
|
+
import type { Node } from "../parser/native/wrapper.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Walk a routine body and return the simplest `ValueSource` describing the
|
|
6
|
+
* initial assignment to a named variable. Phase 0b-α captures ONE HOP:
|
|
7
|
+
* - `X := 'literal'` → `{kind: "literal", value: "literal"}`
|
|
8
|
+
* - `X := 42` → `{kind: "literal", value: "42"}`
|
|
9
|
+
* - `X := MyEnum::Value` → `{kind: "enum", enumName, member}`
|
|
10
|
+
* - `X := OtherVar` → `{kind: "constant-var", varName: "othervar",
|
|
11
|
+
* initializer: {kind: "unknown"}}`
|
|
12
|
+
* - any other RHS → `{kind: "expression"}`
|
|
13
|
+
* - no assignment found → `undefined`
|
|
14
|
+
*
|
|
15
|
+
* First assignment wins. Phase 0b-β chases chains.
|
|
16
|
+
*
|
|
17
|
+
* MUST NEVER throw.
|
|
18
|
+
*
|
|
19
|
+
* Probe findings (assignment_statement):
|
|
20
|
+
* - LHS field: "left" → identifier node
|
|
21
|
+
* - RHS field: "right" → string_literal | integer | qualified_enum_value | identifier | …
|
|
22
|
+
* - qualified_enum_value: namedChild(0) = quoted_identifier (includes ""), namedChild(1) = identifier
|
|
23
|
+
*
|
|
24
|
+
* @param bodyNode the routine's `code_block` AST node
|
|
25
|
+
* @param varNameLowercased the variable name, already lowercased
|
|
26
|
+
*/
|
|
27
|
+
export function extractInitializer(
|
|
28
|
+
bodyNode: Node,
|
|
29
|
+
varNameLowercased: string,
|
|
30
|
+
): ValueSource | undefined {
|
|
31
|
+
try {
|
|
32
|
+
const assignment = findFirstAssignmentTo(bodyNode, varNameLowercased);
|
|
33
|
+
if (assignment === null) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const rhs = getAssignmentRhs(assignment);
|
|
37
|
+
if (rhs === null) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return classifyRhs(rhs);
|
|
41
|
+
} catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findFirstAssignmentTo(node: Node, varNameLowercased: string): Node | null {
|
|
47
|
+
if (node.type === "assignment_statement") {
|
|
48
|
+
const targetText = getAssignmentTargetText(node);
|
|
49
|
+
if (targetText !== null && targetText.toLowerCase() === varNameLowercased) {
|
|
50
|
+
return node;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
54
|
+
const child = node.namedChild(i);
|
|
55
|
+
if (child === null) continue;
|
|
56
|
+
const found = findFirstAssignmentTo(child, varNameLowercased);
|
|
57
|
+
if (found !== null) return found;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getAssignmentTargetText(assignmentNode: Node): string | null {
|
|
63
|
+
// Probe confirmed: field "left" → identifier node
|
|
64
|
+
const target =
|
|
65
|
+
assignmentNode.childForFieldName?.("left") ??
|
|
66
|
+
assignmentNode.childForFieldName?.("target") ??
|
|
67
|
+
assignmentNode.namedChild(0);
|
|
68
|
+
if (target === null || target === undefined) return null;
|
|
69
|
+
const text = (target as { text?: unknown }).text;
|
|
70
|
+
return typeof text === "string" ? text : null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getAssignmentRhs(assignmentNode: Node): Node | null {
|
|
74
|
+
// Probe confirmed: field "right" → RHS node
|
|
75
|
+
const rhs =
|
|
76
|
+
assignmentNode.childForFieldName?.("right") ??
|
|
77
|
+
assignmentNode.childForFieldName?.("value") ??
|
|
78
|
+
assignmentNode.namedChild(assignmentNode.namedChildCount - 1);
|
|
79
|
+
return (rhs ?? null) as Node | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function classifyRhs(rhs: Node): ValueSource {
|
|
83
|
+
const type = rhs.type;
|
|
84
|
+
|
|
85
|
+
// Probe confirmed: string literal type is "string_literal" (includes surrounding quotes)
|
|
86
|
+
if (type === "string_literal" || type === "string_literal_value" || type === "text_literal") {
|
|
87
|
+
return { kind: "literal", value: stripSingleQuotes(rhs.text) };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Probe confirmed: integer literal type is "integer" (not "integer_literal")
|
|
91
|
+
if (
|
|
92
|
+
type === "integer" ||
|
|
93
|
+
type === "integer_literal" ||
|
|
94
|
+
type === "decimal_literal" ||
|
|
95
|
+
type === "number_literal" ||
|
|
96
|
+
type === "decimal"
|
|
97
|
+
) {
|
|
98
|
+
return { kind: "literal", value: rhs.text.trim() };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (type === "boolean_literal" || type === "true" || type === "false") {
|
|
102
|
+
return { kind: "literal", value: rhs.text.trim() };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Probe confirmed: enum-qualified node type is "qualified_enum_value"
|
|
106
|
+
// namedChild(0) = quoted_identifier (includes surrounding double-quotes)
|
|
107
|
+
// namedChild(1) = identifier (member name)
|
|
108
|
+
if (type === "qualified_enum_value") {
|
|
109
|
+
const parts = parseQualifiedEnum(rhs);
|
|
110
|
+
if (parts !== null) {
|
|
111
|
+
return { kind: "enum", enumName: parts.enumName, member: parts.member };
|
|
112
|
+
}
|
|
113
|
+
return { kind: "expression" };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Bare identifier → var-to-var reference (one hop)
|
|
117
|
+
if (type === "identifier") {
|
|
118
|
+
return {
|
|
119
|
+
kind: "constant-var",
|
|
120
|
+
varName: rhs.text.toLowerCase(),
|
|
121
|
+
initializer: { kind: "unknown" },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Anything else (function call, member expression, binary expression, etc.)
|
|
126
|
+
return { kind: "expression" };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function stripSingleQuotes(s: string): string {
|
|
130
|
+
const t = s.trim();
|
|
131
|
+
if (t.length >= 2 && t[0] === "'" && t[t.length - 1] === "'") {
|
|
132
|
+
return t.slice(1, -1);
|
|
133
|
+
}
|
|
134
|
+
return t;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function stripDoubleQuotes(s: string): string {
|
|
138
|
+
const t = s.trim();
|
|
139
|
+
if (t.length >= 2 && t[0] === '"' && t[t.length - 1] === '"') {
|
|
140
|
+
return t.slice(1, -1);
|
|
141
|
+
}
|
|
142
|
+
return t;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function parseQualifiedEnum(node: Node): { enumName: string; member: string } | null {
|
|
146
|
+
// Probe confirmed: namedChild(0) = quoted_identifier, namedChild(1) = identifier
|
|
147
|
+
const enumNameNode = node.namedChild(0);
|
|
148
|
+
const memberNode = node.namedChild(node.namedChildCount - 1);
|
|
149
|
+
if (enumNameNode === null || enumNameNode === undefined) return null;
|
|
150
|
+
if (memberNode === null || memberNode === undefined) return null;
|
|
151
|
+
// quoted_identifier text includes surrounding double-quotes: `"Document Type"`
|
|
152
|
+
const enumName = stripDoubleQuotes((enumNameNode as { text: string }).text);
|
|
153
|
+
const member = (memberNode as { text: string }).text;
|
|
154
|
+
return { enumName, member };
|
|
155
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { Node } from "../parser/native/wrapper.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a tree-sitter-al `variable_declaration` node into a normalized
|
|
5
|
+
* AL type string. Drives Phase 0b-β capability-extractor dispatch (member-
|
|
6
|
+
* call receivers resolve their `declaredType` and route to the appropriate
|
|
7
|
+
* family extractor).
|
|
8
|
+
*
|
|
9
|
+
* Output shape examples (canonical reference — must match test fixtures):
|
|
10
|
+
* - `"HttpClient"` (built-in type, no qualifier)
|
|
11
|
+
* - `"Record Customer"` (record, unquoted single-word table name)
|
|
12
|
+
* - `"Record \"Sales Line\""` (record, quoted multi-word table name)
|
|
13
|
+
* - `"Codeunit \"Sales-Post\""` (codeunit, quoted name with hyphen)
|
|
14
|
+
* - `"Page \"Customer Card\""`
|
|
15
|
+
* - `"Text"` / `"Text[100]"` (primitive with optional length)
|
|
16
|
+
* - `"Integer"`, `"Decimal"`, `"Boolean"`, etc.
|
|
17
|
+
*
|
|
18
|
+
* The function MUST NEVER throw on malformed declarations. Returns
|
|
19
|
+
* `"unknown"` when the type-expression child is missing or unrecognizable.
|
|
20
|
+
* Phase 0b-β capability extractors treat `"unknown"` as a coverage gap.
|
|
21
|
+
*
|
|
22
|
+
* AST probe findings (tree-sitter-al):
|
|
23
|
+
* - `variable_declaration` exposes the type under field name `"type"`,
|
|
24
|
+
* which maps to a `type_specification` node.
|
|
25
|
+
* - `type_specification` children include: `basic_type`, `record_type`,
|
|
26
|
+
* `object_reference_type`, `text_type` (and others for future types).
|
|
27
|
+
* - `typeNode.text` gives the verbatim source text, e.g. `Record "Sales Line"`.
|
|
28
|
+
* - Whitespace canonicalization (collapsing runs, preserving quoted content)
|
|
29
|
+
* is sufficient for a stable normalized string.
|
|
30
|
+
*/
|
|
31
|
+
export function normalizeDeclaredType(variableDeclarationNode: Node): string {
|
|
32
|
+
try {
|
|
33
|
+
// The grammar exposes the type_specification under field "type".
|
|
34
|
+
const typeNode = variableDeclarationNode.childForFieldName("type");
|
|
35
|
+
if (typeNode === null || typeNode === undefined) {
|
|
36
|
+
return "unknown";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const raw = typeNode.text;
|
|
40
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return canonicalizeTypeText(raw);
|
|
45
|
+
} catch {
|
|
46
|
+
return "unknown";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Trim and collapse internal whitespace runs to a single space, while
|
|
52
|
+
* preserving quoted AL identifiers (`"Sales Line"`) verbatim — they may
|
|
53
|
+
* contain spaces that must not be collapsed or stripped.
|
|
54
|
+
*/
|
|
55
|
+
export function canonicalizeTypeText(raw: string): string {
|
|
56
|
+
let out = "";
|
|
57
|
+
let inQuotes = false;
|
|
58
|
+
let lastWasSpace = false;
|
|
59
|
+
|
|
60
|
+
for (const ch of raw.trim()) {
|
|
61
|
+
if (ch === '"') {
|
|
62
|
+
inQuotes = !inQuotes;
|
|
63
|
+
out += ch;
|
|
64
|
+
lastWasSpace = false;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inQuotes) {
|
|
68
|
+
out += ch;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") {
|
|
72
|
+
if (!lastWasSpace) {
|
|
73
|
+
out += " ";
|
|
74
|
+
lastWasSpace = true;
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
out += ch;
|
|
79
|
+
lastWasSpace = false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return out;
|
|
83
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// al-sem: Static semantic analysis engine for Business Central AL code
|
|
2
|
+
// Public entry point and library export surface.
|
|
3
|
+
|
|
4
|
+
export * from "./model/index.ts";
|
|
5
|
+
export type { SourceUnit, SourceProvider } from "./providers/types.ts";
|
|
6
|
+
export { ExternalSourceProvider } from "./providers/external.ts";
|
|
7
|
+
export {
|
|
8
|
+
buildCompactReport,
|
|
9
|
+
formatCompactJson,
|
|
10
|
+
type CompactReport,
|
|
11
|
+
} from "./cli/format-compact-json.ts";
|
|
12
|
+
export { formatSarif } from "./cli/format-sarif.ts";
|
|
13
|
+
export {
|
|
14
|
+
projectFinding,
|
|
15
|
+
type FindingLocation,
|
|
16
|
+
type FindingSummary,
|
|
17
|
+
} from "./projection/finding-summary.ts";
|
|
18
|
+
export { filterFindings, type FilterOptions } from "./projection/finding-filters.ts";
|
|
19
|
+
export {
|
|
20
|
+
groupFindings,
|
|
21
|
+
type FindingGroup,
|
|
22
|
+
type GroupBy,
|
|
23
|
+
} from "./projection/finding-groups.ts";
|
|
24
|
+
export { fingerprintOf } from "./projection/finding-fingerprint.ts";
|
|
25
|
+
export { rollupFindings, type RolledOrSingle } from "./projection/rollup-findings.ts";
|
|
26
|
+
export {
|
|
27
|
+
applyBaseline,
|
|
28
|
+
loadBaseline,
|
|
29
|
+
saveBaseline,
|
|
30
|
+
type BaselineFile,
|
|
31
|
+
} from "./cli/baseline.ts";
|
|
32
|
+
export { computeExitCode, parseFailOn } from "./cli/exit-code.ts";
|
|
33
|
+
|
|
34
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
35
|
+
import { join, resolve } from "node:path";
|
|
36
|
+
import { loadRootsConfig } from "./config/roots-config.ts";
|
|
37
|
+
import { withDependencyArtifacts } from "./deps/dependency-artifact.ts";
|
|
38
|
+
import { resolveDependencyArtifacts } from "./deps/dependency-resolver.ts";
|
|
39
|
+
import {
|
|
40
|
+
parseWorkspaceDependencies,
|
|
41
|
+
parseWorkspaceInternalsVisibleTo,
|
|
42
|
+
} from "./deps/workspace-dependencies.ts";
|
|
43
|
+
import {
|
|
44
|
+
ALL_DETECTORS,
|
|
45
|
+
DEFAULT_DETECTORS,
|
|
46
|
+
type Detector,
|
|
47
|
+
runDetectors,
|
|
48
|
+
} from "./detectors/registry.ts";
|
|
49
|
+
import { buildCombinedGraph } from "./engine/combined-graph.ts";
|
|
50
|
+
import { overlayConfigRoots } from "./engine/root-classifier-overlay.ts";
|
|
51
|
+
import { classifyRoots } from "./engine/root-classifier.ts";
|
|
52
|
+
import { computeSummaries } from "./engine/summary-engine.ts";
|
|
53
|
+
import { buildSemanticIndex } from "./index/indexer.ts";
|
|
54
|
+
import type { DetectorStats, Diagnostic, Finding } from "./model/finding.ts";
|
|
55
|
+
import type { SemanticIndex, SemanticModel } from "./model/model.ts";
|
|
56
|
+
import { makeLap } from "./perf/profiler.ts";
|
|
57
|
+
import { discoverSources } from "./providers/discover.ts";
|
|
58
|
+
import type { ExternalSourceProvider } from "./providers/external.ts";
|
|
59
|
+
import type { SourceUnit } from "./providers/types.ts";
|
|
60
|
+
import { resolveModel } from "./resolve/resolver.ts";
|
|
61
|
+
import type { ManifestDependency } from "./symbols/app-manifest.ts";
|
|
62
|
+
|
|
63
|
+
export interface AnalyzeWorkspaceOptions {
|
|
64
|
+
workspaceRoot: string;
|
|
65
|
+
alpackagesDir?: string;
|
|
66
|
+
externalProvider?: ExternalSourceProvider;
|
|
67
|
+
/** When true, createdAt is pinned to the epoch so output is byte-deterministic. */
|
|
68
|
+
deterministic?: boolean;
|
|
69
|
+
/** Skip the behavioral dependency cold run — structural ABI only, conservative effects. */
|
|
70
|
+
noDepSummaries?: boolean;
|
|
71
|
+
/** Override the dependency cache directory (default ~/.al-sem/cache/). */
|
|
72
|
+
dependencyCacheDir?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Detector registry to run. Default: `DEFAULT_DETECTORS`. Pass `ALL_DETECTORS` to
|
|
75
|
+
* include opt-in detectors (currently: D40 — see `OPT_IN_DETECTORS`). Callers can
|
|
76
|
+
* also pass a custom `Detector[]` for fine-grained selection.
|
|
77
|
+
*/
|
|
78
|
+
detectors?: Detector[];
|
|
79
|
+
/**
|
|
80
|
+
* If true, skip loading `roots.config.json` even if it exists. §4.3 Task 7
|
|
81
|
+
* surfaces this via the `--no-roots-config` CLI flag; the snapshot pipeline
|
|
82
|
+
* records the ignore decision so diff can distinguish "no config" from
|
|
83
|
+
* "config ignored". Overlay resolution is also skipped — output equals AST.
|
|
84
|
+
*/
|
|
85
|
+
noRootsConfig?: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AnalyzeWorkspaceResult {
|
|
89
|
+
model: SemanticModel;
|
|
90
|
+
findings: Finding[];
|
|
91
|
+
diagnostics: Diagnostic[];
|
|
92
|
+
detectorStats: DetectorStats[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The raw, pre-resolve product of discovery + indexing — input to `resolveModel`. */
|
|
96
|
+
export interface IndexWorkspaceResult {
|
|
97
|
+
index: SemanticIndex;
|
|
98
|
+
/** Source units from discovery, needed by the resolver for coverage. */
|
|
99
|
+
units: SourceUnit[];
|
|
100
|
+
/** Index-phase diagnostics only — what resolveModel's coverage accounting consumes. */
|
|
101
|
+
indexDiagnostics: Diagnostic[];
|
|
102
|
+
/** Discovery + index diagnostics merged — the full list for callers. */
|
|
103
|
+
diagnostics: Diagnostic[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Discovers sources, parses, and indexes them into a raw `SemanticIndex` — without
|
|
108
|
+
* running the resolver. `analyzeWorkspace` builds on this; tests of `resolveModel` use
|
|
109
|
+
* it to obtain a genuinely unresolved index. Never throws — failures surface as
|
|
110
|
+
* diagnostics.
|
|
111
|
+
*/
|
|
112
|
+
export async function indexWorkspace(
|
|
113
|
+
options: AnalyzeWorkspaceOptions,
|
|
114
|
+
): Promise<IndexWorkspaceResult> {
|
|
115
|
+
// Normalize paths so discoverSources hashes a canonical absolute path.
|
|
116
|
+
const workspaceRoot = resolve(options.workspaceRoot);
|
|
117
|
+
const alpackagesDir = options.alpackagesDir ? resolve(options.alpackagesDir) : undefined;
|
|
118
|
+
|
|
119
|
+
const discovery = await discoverSources({
|
|
120
|
+
workspaceRoot,
|
|
121
|
+
alpackagesDir,
|
|
122
|
+
externalProvider: options.externalProvider,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const identity = options.deterministic
|
|
126
|
+
? discovery.identity
|
|
127
|
+
: { ...discovery.identity, createdAt: new Date().toISOString() };
|
|
128
|
+
|
|
129
|
+
const { index, diagnostics } = await buildSemanticIndex(
|
|
130
|
+
discovery.units,
|
|
131
|
+
identity,
|
|
132
|
+
discovery.modelInstanceId,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const allDiagnostics: Diagnostic[] = [
|
|
136
|
+
...discovery.diagnostics.map(
|
|
137
|
+
(d): Diagnostic => ({
|
|
138
|
+
severity: d.severity,
|
|
139
|
+
stage: "discover",
|
|
140
|
+
message: d.message,
|
|
141
|
+
sourceRef: d.sourceRef,
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
...diagnostics,
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
index,
|
|
149
|
+
units: discovery.units,
|
|
150
|
+
indexDiagnostics: diagnostics,
|
|
151
|
+
diagnostics: allDiagnostics,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Discovers, indexes, resolves, summarises, and runs detectors over a workspace — the full
|
|
157
|
+
* pipeline. Returns the SemanticModel (with routine summaries populated), the Finding[], and
|
|
158
|
+
* all diagnostics. Never throws — failures surface as diagnostics.
|
|
159
|
+
*/
|
|
160
|
+
export async function analyzeWorkspace(
|
|
161
|
+
options: AnalyzeWorkspaceOptions,
|
|
162
|
+
): Promise<AnalyzeWorkspaceResult> {
|
|
163
|
+
// Coarse phase profiler — routes through the structured profiler (stderr when
|
|
164
|
+
// AL_SEM_PROFILE=1; captured by the benchmark harness via collectPhases). Zero cost when off.
|
|
165
|
+
const alap = makeLap("analyze:");
|
|
166
|
+
|
|
167
|
+
const workspace = await indexWorkspace(options); // raw, pre-resolve — contract unchanged
|
|
168
|
+
alap("indexWorkspace");
|
|
169
|
+
|
|
170
|
+
// --- L1.5: resolve dependency artifacts (cache hit | cold run) ---
|
|
171
|
+
// Default to `<workspaceRoot>/.alpackages` (BC compiler convention) when not given explicitly,
|
|
172
|
+
// so the CLI's common `al-sem analyze <workspace>` form picks up deps without an extra flag.
|
|
173
|
+
const workspaceRoot = resolve(options.workspaceRoot);
|
|
174
|
+
const alpackagesDir =
|
|
175
|
+
options.alpackagesDir !== undefined
|
|
176
|
+
? resolve(options.alpackagesDir)
|
|
177
|
+
: existsSync(join(workspaceRoot, ".alpackages"))
|
|
178
|
+
? join(workspaceRoot, ".alpackages")
|
|
179
|
+
: undefined;
|
|
180
|
+
let appJsonText = "";
|
|
181
|
+
try {
|
|
182
|
+
appJsonText = readFileSync(join(workspaceRoot, "app.json"), "utf8");
|
|
183
|
+
} catch {
|
|
184
|
+
/* no app.json — parseWorkspaceDependencies returns [] */
|
|
185
|
+
}
|
|
186
|
+
const workspaceDeps: ManifestDependency[] = parseWorkspaceDependencies(appJsonText);
|
|
187
|
+
let depArtifacts: Awaited<ReturnType<typeof resolveDependencyArtifacts>> = {
|
|
188
|
+
artifacts: [],
|
|
189
|
+
diagnostics: [],
|
|
190
|
+
};
|
|
191
|
+
if (alpackagesDir !== undefined) {
|
|
192
|
+
depArtifacts = await resolveDependencyArtifacts(alpackagesDir, workspaceDeps, {
|
|
193
|
+
cacheDir: options.dependencyCacheDir,
|
|
194
|
+
noDepSummaries: options.noDepSummaries,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
alap("resolveDependencyArtifacts");
|
|
198
|
+
|
|
199
|
+
// --- merge + run the workspace resolve/summarize/detect stages over the merged index ---
|
|
200
|
+
const analysisIndex = withDependencyArtifacts(workspace.index, depArtifacts.artifacts);
|
|
201
|
+
const model = resolveModel(analysisIndex, workspace.units, workspace.indexDiagnostics);
|
|
202
|
+
model.identity.primaryDependencies = workspaceDeps;
|
|
203
|
+
model.identity.primaryInternalsVisibleTo = parseWorkspaceInternalsVisibleTo(appJsonText);
|
|
204
|
+
alap("resolveModel");
|
|
205
|
+
|
|
206
|
+
const graph = buildCombinedGraph(model);
|
|
207
|
+
alap("buildCombinedGraph");
|
|
208
|
+
const summarizeDiagnostics: Diagnostic[] = [];
|
|
209
|
+
computeSummaries(model, graph, summarizeDiagnostics);
|
|
210
|
+
alap("computeSummaries");
|
|
211
|
+
|
|
212
|
+
// §4.3 — derive AST classifications, then merge any roots.config.json
|
|
213
|
+
// overlay on top. The overlay is pure; only `analyzeWorkspace` does the
|
|
214
|
+
// file I/O (via `loadRootsConfig`). `noRootsConfig` short-circuits the
|
|
215
|
+
// loader entirely (Task 7 wires the CLI flag).
|
|
216
|
+
const astRoots = classifyRoots(model);
|
|
217
|
+
const loadedRootsConfig = options.noRootsConfig
|
|
218
|
+
? {
|
|
219
|
+
config: undefined,
|
|
220
|
+
contentHash: undefined,
|
|
221
|
+
path: undefined,
|
|
222
|
+
diagnostics: [] as Diagnostic[],
|
|
223
|
+
}
|
|
224
|
+
: loadRootsConfig(workspaceRoot);
|
|
225
|
+
const { roots, diagnostics: overlayDiagnostics } = overlayConfigRoots(
|
|
226
|
+
astRoots,
|
|
227
|
+
loadedRootsConfig.config,
|
|
228
|
+
model,
|
|
229
|
+
);
|
|
230
|
+
model.rootClassifications = roots;
|
|
231
|
+
if (loadedRootsConfig.path !== undefined && loadedRootsConfig.contentHash !== undefined) {
|
|
232
|
+
model.identity.rootsConfig = {
|
|
233
|
+
path: loadedRootsConfig.path,
|
|
234
|
+
contentHash: loadedRootsConfig.contentHash,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
alap("classifyRoots+overlay");
|
|
238
|
+
|
|
239
|
+
const {
|
|
240
|
+
findings,
|
|
241
|
+
diagnostics: detectDiagnostics,
|
|
242
|
+
detectorStats,
|
|
243
|
+
} = runDetectors(model, graph, options.detectors);
|
|
244
|
+
alap("runDetectors");
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
model,
|
|
248
|
+
findings,
|
|
249
|
+
diagnostics: [
|
|
250
|
+
...workspace.diagnostics,
|
|
251
|
+
...depArtifacts.diagnostics,
|
|
252
|
+
...summarizeDiagnostics,
|
|
253
|
+
...loadedRootsConfig.diagnostics,
|
|
254
|
+
...overlayDiagnostics,
|
|
255
|
+
...detectDiagnostics,
|
|
256
|
+
],
|
|
257
|
+
detectorStats,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { composeSnapshot } from "./snapshot/compose.ts";
|
|
262
|
+
export type { CapabilitySnapshot, SnapshotFormat, SnapshotShardingMode } from "./snapshot/types.ts";
|
|
263
|
+
export { serializeJson } from "./snapshot/serialize-json.ts";
|
|
264
|
+
export { serializeCbor } from "./snapshot/serialize-cbor.ts";
|
|
265
|
+
export { serializeCborGz } from "./snapshot/serialize-cbor-gz.ts";
|
|
266
|
+
export { serializeSharded } from "./snapshot/shard.ts";
|
|
267
|
+
export { deserializeSnapshot } from "./snapshot/deserialize.ts";
|