weavatrix 0.3.14 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.md +132 -200
- package/bin/native/darwin-arm64/weavatrix +0 -0
- package/bin/native/darwin-x64/weavatrix +0 -0
- package/bin/native/linux-arm64/weavatrix +0 -0
- package/bin/native/linux-x64/weavatrix +0 -0
- package/bin/native/win32-arm64/weavatrix.exe +0 -0
- package/bin/native/win32-x64/weavatrix.exe +0 -0
- package/bin/resolve-binary.mjs +73 -0
- package/bin/run-native.mjs +26 -0
- package/bin/weavatrix-mcp.mjs +7 -6
- package/bin/weavatrix.mjs +5 -0
- package/package.json +10 -67
- package/server.json +38 -0
- package/skill/SKILL.md +62 -374
- package/skill/agents/openai.yaml +4 -0
- package/skill/references/tool-routing.md +45 -0
- package/SECURITY.md +0 -101
- package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
- package/docs/agent-task-benchmark.md +0 -58
- package/docs/releases/v0.2.19.md +0 -33
- package/docs/releases/v0.3.0.md +0 -43
- package/docs/releases/v0.3.1.md +0 -30
- package/docs/releases/v0.3.10.md +0 -76
- package/docs/releases/v0.3.11.md +0 -55
- package/docs/releases/v0.3.12.md +0 -44
- package/docs/releases/v0.3.13.md +0 -34
- package/docs/releases/v0.3.14.md +0 -37
- package/docs/releases/v0.3.2.md +0 -15
- package/docs/releases/v0.3.3.md +0 -25
- package/docs/releases/v0.3.4.md +0 -40
- package/docs/releases/v0.3.5.md +0 -50
- package/docs/releases/v0.3.6.md +0 -44
- package/docs/releases/v0.3.7.md +0 -37
- package/docs/releases/v0.3.8.md +0 -21
- package/docs/releases/v0.3.9.md +0 -36
- package/scripts/run-agent-task-benchmark.mjs +0 -118
- package/src/analysis/allowed-test-runner.js +0 -70
- package/src/analysis/architecture/contract-graph.js +0 -119
- package/src/analysis/architecture/contract-schema.js +0 -110
- package/src/analysis/architecture/contract-storage.js +0 -35
- package/src/analysis/architecture/contract-verification.js +0 -168
- package/src/analysis/architecture-contract.js +0 -16
- package/src/analysis/audit-debt.js +0 -94
- package/src/analysis/audit-extensions.js +0 -60
- package/src/analysis/cargo-dependency-evidence.js +0 -108
- package/src/analysis/cargo-manifests.js +0 -81
- package/src/analysis/change-classification/diff-parser.js +0 -103
- package/src/analysis/change-classification/options.js +0 -40
- package/src/analysis/change-classification/symbol-classifier.js +0 -177
- package/src/analysis/change-classification.js +0 -188
- package/src/analysis/coverage-reports-tarpaulin.js +0 -112
- package/src/analysis/coverage-reports.js +0 -246
- package/src/analysis/cycle-route.js +0 -14
- package/src/analysis/data-flow-evidence.js +0 -114
- package/src/analysis/dead-check.js +0 -300
- package/src/analysis/dead-code-review/candidates.js +0 -194
- package/src/analysis/dead-code-review/policy.js +0 -23
- package/src/analysis/dead-code-review.js +0 -119
- package/src/analysis/dep-check-ecosystems.js +0 -205
- package/src/analysis/dep-check.js +0 -290
- package/src/analysis/dep-rules.js +0 -10
- package/src/analysis/dependency/conventions.js +0 -16
- package/src/analysis/dependency/scoped-dependencies.js +0 -51
- package/src/analysis/dependency/source-references.js +0 -21
- package/src/analysis/duplicate-groups.js +0 -104
- package/src/analysis/duplicates-worker.js +0 -11
- package/src/analysis/duplicates.compute.js +0 -269
- package/src/analysis/duplicates.js +0 -15
- package/src/analysis/duplicates.run.js +0 -52
- package/src/analysis/duplicates.tokenize.js +0 -199
- package/src/analysis/endpoints/common.js +0 -79
- package/src/analysis/endpoints/extract.js +0 -120
- package/src/analysis/endpoints/inventory.js +0 -84
- package/src/analysis/endpoints/mounts.js +0 -129
- package/src/analysis/endpoints-java.js +0 -260
- package/src/analysis/endpoints-rust.js +0 -124
- package/src/analysis/endpoints.js +0 -3
- package/src/analysis/findings.js +0 -70
- package/src/analysis/git-history/analytics.js +0 -177
- package/src/analysis/git-history/collector.js +0 -67
- package/src/analysis/git-history/options.js +0 -66
- package/src/analysis/git-history.js +0 -143
- package/src/analysis/git-ref-graph.js +0 -76
- package/src/analysis/go-dependency-evidence.js +0 -62
- package/src/analysis/graph-analysis.aggregate.js +0 -296
- package/src/analysis/graph-analysis.edges.js +0 -24
- package/src/analysis/graph-analysis.js +0 -7
- package/src/analysis/graph-analysis.refs.js +0 -202
- package/src/analysis/graph-analysis.summaries.js +0 -65
- package/src/analysis/health-capabilities.js +0 -83
- package/src/analysis/hot-path-review.js +0 -266
- package/src/analysis/http-contract-wrappers.js +0 -241
- package/src/analysis/http-contracts/analysis.js +0 -179
- package/src/analysis/http-contracts/client-call-detection.js +0 -82
- package/src/analysis/http-contracts/client-call-parser.js +0 -273
- package/src/analysis/http-contracts/graph-context.js +0 -143
- package/src/analysis/http-contracts/matching.js +0 -61
- package/src/analysis/http-contracts/shared.js +0 -85
- package/src/analysis/http-contracts.js +0 -6
- package/src/analysis/internal-audit/dependency-health.js +0 -113
- package/src/analysis/internal-audit/python-manifests.js +0 -78
- package/src/analysis/internal-audit/repo-files.js +0 -50
- package/src/analysis/internal-audit/supply-chain.js +0 -149
- package/src/analysis/internal-audit.collect.js +0 -213
- package/src/analysis/internal-audit.js +0 -9
- package/src/analysis/internal-audit.reach.js +0 -201
- package/src/analysis/internal-audit.run.js +0 -256
- package/src/analysis/java-source.js +0 -36
- package/src/analysis/jvm-artifact-index.js +0 -87
- package/src/analysis/jvm-dependency-evidence.js +0 -138
- package/src/analysis/jvm-manifests.js +0 -114
- package/src/analysis/manifests.js +0 -123
- package/src/analysis/package-reachability.js +0 -39
- package/src/analysis/source-complexity.ast.js +0 -97
- package/src/analysis/source-complexity.constants.js +0 -55
- package/src/analysis/source-complexity.js +0 -14
- package/src/analysis/source-complexity.report.js +0 -81
- package/src/analysis/source-complexity.walk.js +0 -228
- package/src/analysis/source-correctness/retry-patterns.js +0 -93
- package/src/analysis/source-correctness.js +0 -222
- package/src/analysis/static-test-reachability.js +0 -150
- package/src/analysis/structure/dependency-graph.js +0 -151
- package/src/analysis/structure/findings.js +0 -162
- package/src/analysis/task-retrieval.js +0 -107
- package/src/analysis/transport-contracts.js +0 -224
- package/src/analysis/transport-runtime-evidence.js +0 -155
- package/src/analysis-kit.mjs +0 -17
- package/src/bounds.js +0 -4
- package/src/build-graph.js +0 -209
- package/src/child-env.js +0 -7
- package/src/config.js +0 -5
- package/src/extension/local-services.mjs +0 -75
- package/src/git-exec.js +0 -67
- package/src/graph/build-worker.js +0 -49
- package/src/graph/builder/go-receiver-resolution.js +0 -112
- package/src/graph/builder/js/dynamic-import.js +0 -26
- package/src/graph/builder/js/queries.js +0 -48
- package/src/graph/builder/lang-csharp.js +0 -40
- package/src/graph/builder/lang-css.js +0 -29
- package/src/graph/builder/lang-go.js +0 -140
- package/src/graph/builder/lang-html.js +0 -29
- package/src/graph/builder/lang-java-calls.js +0 -142
- package/src/graph/builder/lang-java.js +0 -215
- package/src/graph/builder/lang-js.js +0 -291
- package/src/graph/builder/lang-python.js +0 -235
- package/src/graph/builder/lang-rust-calls.js +0 -63
- package/src/graph/builder/lang-rust.js +0 -299
- package/src/graph/builder/lang-solidity.js +0 -126
- package/src/graph/builder/lang-sql.js +0 -263
- package/src/graph/builder/pass2-resolution.js +0 -81
- package/src/graph/builder/spec-pkg.js +0 -87
- package/src/graph/community.js +0 -27
- package/src/graph/edge-provenance.js +0 -31
- package/src/graph/file-lock.js +0 -69
- package/src/graph/freshness-probe.js +0 -142
- package/src/graph/graph-filter.js +0 -63
- package/src/graph/incremental-refresh.js +0 -235
- package/src/graph/internal-builder.barrels.js +0 -235
- package/src/graph/internal-builder.build.js +0 -300
- package/src/graph/internal-builder.java.js +0 -33
- package/src/graph/internal-builder.js +0 -12
- package/src/graph/internal-builder.langs.js +0 -157
- package/src/graph/internal-builder.pass2.js +0 -163
- package/src/graph/internal-builder.resolvers.js +0 -286
- package/src/graph/layout.js +0 -51
- package/src/graph/node-id.js +0 -7
- package/src/graph/parser-artifact-boundary.js +0 -55
- package/src/graph/relations.js +0 -4
- package/src/graph/repo-registry.js +0 -124
- package/src/graph/resolvers/rust.js +0 -117
- package/src/infra/infra-items.js +0 -255
- package/src/infra/infra-registry.js +0 -184
- package/src/infra/infra.detect.js +0 -119
- package/src/infra/infra.js +0 -38
- package/src/infra/infra.match.js +0 -102
- package/src/infra/infra.scan.js +0 -93
- package/src/mcp/actions/graph-lifecycle.mjs +0 -148
- package/src/mcp/architecture-bootstrap.mjs +0 -168
- package/src/mcp/architecture-starter.mjs +0 -234
- package/src/mcp/catalog.mjs +0 -233
- package/src/mcp/company-contract-page.mjs +0 -142
- package/src/mcp/company-contract-reconcile.mjs +0 -87
- package/src/mcp/company-contract-verdict.mjs +0 -42
- package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
- package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
- package/src/mcp/evidence/package-graph-common.mjs +0 -115
- package/src/mcp/evidence/package-lock-graph.mjs +0 -92
- package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
- package/src/mcp/evidence-snapshot.common.mjs +0 -158
- package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
- package/src/mcp/evidence-snapshot.health.mjs +0 -102
- package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
- package/src/mcp/evidence-snapshot.mjs +0 -50
- package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
- package/src/mcp/evidence-snapshot.structure.mjs +0 -84
- package/src/mcp/extension-api.mjs +0 -77
- package/src/mcp/git-output.mjs +0 -7
- package/src/mcp/graph/context-core.mjs +0 -111
- package/src/mcp/graph/context-seeds.mjs +0 -207
- package/src/mcp/graph/context-state.mjs +0 -85
- package/src/mcp/graph/reverse-reach.mjs +0 -42
- package/src/mcp/graph/tools-core.mjs +0 -143
- package/src/mcp/graph/tools-query.mjs +0 -276
- package/src/mcp/graph-context.mjs +0 -5
- package/src/mcp/graph-diff.mjs +0 -218
- package/src/mcp/health/audit-format.mjs +0 -199
- package/src/mcp/health/audit.mjs +0 -199
- package/src/mcp/health/dead-code.mjs +0 -110
- package/src/mcp/health/duplicates.mjs +0 -121
- package/src/mcp/health/endpoints.mjs +0 -71
- package/src/mcp/health/structure.mjs +0 -220
- package/src/mcp/runtime-version.mjs +0 -31
- package/src/mcp/server/auto-refresh.mjs +0 -66
- package/src/mcp/server/runtime-config.mjs +0 -43
- package/src/mcp/server/shutdown.mjs +0 -40
- package/src/mcp/staleness-notice.mjs +0 -20
- package/src/mcp/sync/evidence-architecture.mjs +0 -54
- package/src/mcp/sync/evidence-common.mjs +0 -88
- package/src/mcp/sync/evidence-duplicates.mjs +0 -97
- package/src/mcp/sync/evidence-health.mjs +0 -56
- package/src/mcp/sync/evidence-packages.mjs +0 -95
- package/src/mcp/sync/payload-common.mjs +0 -97
- package/src/mcp/sync/payload-v2.mjs +0 -54
- package/src/mcp/sync/payload-v3.mjs +0 -79
- package/src/mcp/sync-evidence.mjs +0 -23
- package/src/mcp/sync-payload.mjs +0 -70
- package/src/mcp/tool-result.mjs +0 -54
- package/src/mcp/tools-actions.mjs +0 -11
- package/src/mcp/tools-architecture.mjs +0 -177
- package/src/mcp/tools-company.mjs +0 -251
- package/src/mcp/tools-context.mjs +0 -222
- package/src/mcp/tools-endpoints.mjs +0 -207
- package/src/mcp/tools-graph-hubs.mjs +0 -78
- package/src/mcp/tools-graph.mjs +0 -17
- package/src/mcp/tools-health.mjs +0 -24
- package/src/mcp/tools-history.mjs +0 -28
- package/src/mcp/tools-impact-change.mjs +0 -220
- package/src/mcp/tools-impact-precision.mjs +0 -89
- package/src/mcp/tools-impact.mjs +0 -169
- package/src/mcp/tools-source.mjs +0 -238
- package/src/mcp/tools-verified-change.mjs +0 -200
- package/src/mcp-rg.mjs +0 -50
- package/src/mcp-runtime.mjs +0 -2
- package/src/mcp-server.mjs +0 -298
- package/src/mcp-source-tools.mjs +0 -163
- package/src/path-classification.js +0 -226
- package/src/path-ignore.js +0 -69
- package/src/precision/lsp-client/constants.js +0 -12
- package/src/precision/lsp-client/environment.js +0 -20
- package/src/precision/lsp-client/errors.js +0 -15
- package/src/precision/lsp-client/lifecycle.js +0 -114
- package/src/precision/lsp-client/message-parser.js +0 -81
- package/src/precision/lsp-client/protocol.js +0 -212
- package/src/precision/lsp-client/registry.js +0 -59
- package/src/precision/lsp-client/repo-uri.js +0 -60
- package/src/precision/lsp-client/stdio-client.js +0 -140
- package/src/precision/lsp-client.js +0 -10
- package/src/precision/lsp-overlay/budget-policy.js +0 -78
- package/src/precision/lsp-overlay/build.js +0 -261
- package/src/precision/lsp-overlay/contract.js +0 -61
- package/src/precision/lsp-overlay/reference-results.js +0 -108
- package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
- package/src/precision/lsp-overlay/source-session.js +0 -142
- package/src/precision/lsp-overlay/store.js +0 -150
- package/src/precision/lsp-overlay/target-index.js +0 -152
- package/src/precision/lsp-overlay/target-query.js +0 -55
- package/src/precision/lsp-overlay.js +0 -11
- package/src/precision/symbol-query.js +0 -224
- package/src/precision/typescript-lsp-provider.js +0 -12
- package/src/precision/typescript-provider/client.js +0 -89
- package/src/precision/typescript-provider/discovery.js +0 -124
- package/src/precision/typescript-provider/isolated-runtime.js +0 -39
- package/src/precision/typescript-provider/project-host.js +0 -254
- package/src/precision/typescript-provider/project-safety.js +0 -166
- package/src/precision/typescript-provider/reference-usage.js +0 -53
- package/src/process.js +0 -95
- package/src/repo-path.js +0 -53
- package/src/scan/discover.inventory.js +0 -78
- package/src/scan/discover.js +0 -5
- package/src/scan/discover.list.js +0 -79
- package/src/scan/discover.stack.js +0 -227
- package/src/scan/search.core.js +0 -24
- package/src/scan/search.git.js +0 -102
- package/src/scan/search.js +0 -9
- package/src/scan/search.node.js +0 -83
- package/src/scan/search.preview.js +0 -49
- package/src/scan/search.rg.js +0 -145
- package/src/security/advisory-store.js +0 -172
- package/src/security/installed-jvm-rust.js +0 -46
- package/src/security/installed.js +0 -280
- package/src/security/malware-file-heuristics.js +0 -121
- package/src/security/malware-heuristics.exclusions.js +0 -134
- package/src/security/malware-heuristics.js +0 -15
- package/src/security/malware-heuristics.roots.js +0 -145
- package/src/security/malware-heuristics.scan.js +0 -89
- package/src/security/malware-heuristics.sweep.js +0 -122
- package/src/security/malware-scoring.js +0 -105
- package/src/security/match.js +0 -85
- package/src/security/registry-sig.classify.js +0 -137
- package/src/security/registry-sig.js +0 -18
- package/src/security/registry-sig.rules.js +0 -192
- package/src/security/rust-advisory-report.js +0 -60
- package/src/security/typosquat.js +0 -81
- package/src/util.js +0 -40
- package/src/version.js +0 -7
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
// JS / TS / JSX / TSX extractor for the built-in graph builder.
|
|
2
|
-
// Symbols: functions, generators, classes, methods, top-level const/let/var (data + arrow/fn consts).
|
|
3
|
-
// Imports: ESM `import`, CJS `require`, path-aliases (tsconfig/vite), and barrel re-exports (`export … from`).
|
|
4
|
-
// Calls: bare `foo()`. Heritage: `class X extends Y`. Everything runs through the shared ctx (see internal-builder.js).
|
|
5
|
-
import {
|
|
6
|
-
CALLABLE, FUNCS, TOPVARS, TYPES, REQUIRE, parseExportSpecifiers,
|
|
7
|
-
importTypeOnly, reexportTypeOnly,
|
|
8
|
-
} from './js/queries.js';
|
|
9
|
-
import {boundedUrlImportTarget} from './js/dynamic-import.js';
|
|
10
|
-
|
|
11
|
-
export default {
|
|
12
|
-
family: "js",
|
|
13
|
-
grammars: ["javascript", "typescript", "tsx"],
|
|
14
|
-
exts: { ".js": "javascript", ".jsx": "javascript", ".mjs": "javascript", ".cjs": "javascript", ".ts": "typescript", ".tsx": "tsx" },
|
|
15
|
-
isWeb: false,
|
|
16
|
-
calls: `(call_expression function: (identifier) @callee)`,
|
|
17
|
-
heritage: [`(class_heritage (identifier) @super)`, `(class_heritage (extends_clause value: (identifier) @super))`],
|
|
18
|
-
|
|
19
|
-
pass1(ctx) {
|
|
20
|
-
const { grammar, tree, fileRel, caps, field, addSym, addImportEdge, addExternalImport, markExported, imports, resolveJsImport, resolveAlias } = ctx;
|
|
21
|
-
const recordJsExport = typeof ctx.recordJsExport === "function" ? ctx.recordJsExport : () => {};
|
|
22
|
-
// exported-ness of a declaration = an export_statement ancestor (export function/class/const …).
|
|
23
|
-
// BOUNDED climb: web-tree-sitter's node.parent is O(depth) (re-walks a cursor from the root each call),
|
|
24
|
-
// so an UNBOUNDED walk to `program` for every symbol was O(depth^3) and HUNG the build for minutes/hours
|
|
25
|
-
// on deeply-nested / minified / bundled JS (regression). An export_statement is always a NEAR ancestor of
|
|
26
|
-
// the declaration head (≤ ~5 hops), and hitting a statement_block means we're nested inside a function →
|
|
27
|
-
// not a module-level export → bail immediately. See [[graph-builder-internalization]].
|
|
28
|
-
// Early-out for nested scopes keeps this O(1); class members are intentionally never module exports even
|
|
29
|
-
// when their owner class is exported. The hop cap remains a backstop for malformed/deep syntax trees.
|
|
30
|
-
const exportStatementOf = (node) => {
|
|
31
|
-
let p = node.parent;
|
|
32
|
-
for (let hops = 0; p && hops < 6; hops++) {
|
|
33
|
-
if (p.type === "export_statement") return p;
|
|
34
|
-
if (p.type === "program" || p.type === "statement_block" || p.type === "class_body") return null;
|
|
35
|
-
p = p.parent;
|
|
36
|
-
}
|
|
37
|
-
return null;
|
|
38
|
-
};
|
|
39
|
-
const isExportedDecl = (node) => !!exportStatementOf(node);
|
|
40
|
-
const isModuleDeclaration = (node) => {
|
|
41
|
-
let p = node.parent;
|
|
42
|
-
for (let hops = 0; p && hops < 8; hops++) {
|
|
43
|
-
if (p.type === "program") return true;
|
|
44
|
-
if (p.type === "statement_block" || p.type === "class_body") return false;
|
|
45
|
-
p = p.parent;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
48
|
-
};
|
|
49
|
-
// a bare (package) specifier = non-relative AND not a path alias; alias-that-missed is a broken local, not a dep
|
|
50
|
-
const isBareSpec = (spec) => !!spec && !spec.startsWith(".") && resolveAlias(fileRel, spec) == null;
|
|
51
|
-
// broken local import (relative or alias path resolving to no file) — recorded for the "unresolved-import"
|
|
52
|
-
// finding. Asset imports (svg/css-modules-adjacent/fonts/…) and ?query-suffixed specs that resolve once
|
|
53
|
-
// the query is stripped (Vite ?raw/?url/?worker) are NOT code-graph concerns.
|
|
54
|
-
const ASSET_RE = /\.(svg|png|jpe?g|gif|webp|avif|ico|bmp|woff2?|ttf|eot|otf|mp[34]|webm|wasm|pdf|txt|md|html?)$/i;
|
|
55
|
-
const recordUnresolved = (rawSpec, kind, line, typeOnly = false) => {
|
|
56
|
-
const clean = String(rawSpec || "").split("?")[0];
|
|
57
|
-
if (!clean || ASSET_RE.test(clean)) return;
|
|
58
|
-
if (clean !== rawSpec && resolveJsImport(fileRel, clean)) return; // only the ?query broke resolution
|
|
59
|
-
addExternalImport({ spec: rawSpec, kind, line, unresolved: true, typeOnly });
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
// ---- symbols (export flag captured at declaration time) ----
|
|
63
|
-
const methodMetadata = (nameNode) => {
|
|
64
|
-
let method = nameNode.parent;
|
|
65
|
-
while (method && method.type !== "method_definition") method = method.parent;
|
|
66
|
-
let owner = method?.parent;
|
|
67
|
-
while (owner && !["class_declaration", "class"].includes(owner.type)) owner = owner.parent;
|
|
68
|
-
const ownerName = owner && field(owner, "name")?.text;
|
|
69
|
-
const visibility = /^\s*private\b/.test(method?.text || "") ? "private"
|
|
70
|
-
: /^\s*protected\b/.test(method?.text || "") ? "protected" : "public";
|
|
71
|
-
return { symbolKind: "method", ...(ownerName ? { memberOf: ownerName } : {}), visibility };
|
|
72
|
-
};
|
|
73
|
-
for (const cap of caps(grammar, FUNCS, tree.rootNode)) {
|
|
74
|
-
const isMethod = cap.name === "method";
|
|
75
|
-
const exportStatement = !isMethod && exportStatementOf(cap.node);
|
|
76
|
-
addSym(cap.node.text, cap.node.startPosition.row + 1, cap.name !== "class", {
|
|
77
|
-
sourceNode: cap.node.parent,
|
|
78
|
-
selectionNode: cap.node,
|
|
79
|
-
...(exportStatement ? { exported: true } : {}),
|
|
80
|
-
...(isMethod
|
|
81
|
-
? {...methodMetadata(cap.node), symbolSpace: "value"}
|
|
82
|
-
: {
|
|
83
|
-
symbolKind: cap.name === "class" ? "class" : "function",
|
|
84
|
-
symbolSpace: cap.name === "class" ? "both" : "value",
|
|
85
|
-
moduleDeclaration: isModuleDeclaration(cap.node),
|
|
86
|
-
})
|
|
87
|
-
});
|
|
88
|
-
if (exportStatement) {
|
|
89
|
-
recordJsExport({
|
|
90
|
-
kind: "local",
|
|
91
|
-
exported: /^\s*export\s+default\b/.test(exportStatement.text) ? "default" : cap.node.text,
|
|
92
|
-
local: cap.node.text,
|
|
93
|
-
typeOnly: false,
|
|
94
|
-
line: exportStatement.startPosition.row + 1,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
for (const cap of caps(grammar, TOPVARS, tree.rootNode)) {
|
|
99
|
-
const nameNode = field(cap.node, "name"); if (!nameNode || nameNode.type !== "identifier") continue;
|
|
100
|
-
const val = field(cap.node, "value");
|
|
101
|
-
const exported = isExportedDecl(cap.node);
|
|
102
|
-
addSym(nameNode.text, nameNode.startPosition.row + 1, !!(val && CALLABLE.test(val.type)), {
|
|
103
|
-
sourceNode: val || cap.node,
|
|
104
|
-
selectionNode: nameNode,
|
|
105
|
-
...(exported ? { exported: true } : {}),
|
|
106
|
-
symbolKind: "variable",
|
|
107
|
-
symbolSpace: "value",
|
|
108
|
-
moduleDeclaration: true
|
|
109
|
-
});
|
|
110
|
-
if (exported) recordJsExport({
|
|
111
|
-
kind: "local", exported: nameNode.text, local: nameNode.text, typeOnly: false,
|
|
112
|
-
line: exportStatementOf(cap.node)?.startPosition.row + 1 || nameNode.startPosition.row + 1,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// TypeScript has separate type and value namespaces. Keep interface/type declarations as
|
|
117
|
-
// first-class type-space nodes instead of folding them into a same-named runtime declaration.
|
|
118
|
-
// Enums and classes intentionally occupy both spaces because TypeScript emits runtime values.
|
|
119
|
-
if (grammar !== "javascript") for (const cap of caps(grammar, TYPES, tree.rootNode)) {
|
|
120
|
-
const declaration = cap.node.parent;
|
|
121
|
-
const exportStatement = exportStatementOf(cap.node);
|
|
122
|
-
const symbolKind = cap.name === "interface" ? "interface" : cap.name === "type" ? "type" : "enum";
|
|
123
|
-
const symbolSpace = cap.name === "enum" ? "both" : "type";
|
|
124
|
-
addSym(cap.node.text, cap.node.startPosition.row + 1, false, {
|
|
125
|
-
sourceNode: declaration,
|
|
126
|
-
selectionNode: cap.node,
|
|
127
|
-
idSuffix: symbolSpace === "type" ? ":type" : "",
|
|
128
|
-
...(exportStatement ? {exported: true} : {}),
|
|
129
|
-
symbolKind,
|
|
130
|
-
symbolSpace,
|
|
131
|
-
moduleDeclaration: isModuleDeclaration(cap.node),
|
|
132
|
-
});
|
|
133
|
-
if (exportStatement) recordJsExport({
|
|
134
|
-
kind: "local",
|
|
135
|
-
exported: cap.node.text,
|
|
136
|
-
local: cap.node.text,
|
|
137
|
-
typeOnly: symbolSpace === "type",
|
|
138
|
-
line: exportStatement.startPosition.row + 1,
|
|
139
|
-
symbolSpace,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ---- ESM imports ----
|
|
144
|
-
for (const cap of caps(grammar, `(import_statement) @imp`, tree.rootNode)) {
|
|
145
|
-
const node = cap.node; const srcNode = field(node, "source");
|
|
146
|
-
const rawSpec = srcNode ? srcNode.text.replace(/^['"`]|['"`]$/g, "") : "";
|
|
147
|
-
const line = node.startPosition.row + 1;
|
|
148
|
-
const typeOnly = importTypeOnly(node);
|
|
149
|
-
const tgt = resolveJsImport(fileRel, rawSpec);
|
|
150
|
-
if (!tgt) {
|
|
151
|
-
if (isBareSpec(rawSpec)) addExternalImport({ spec: rawSpec, kind: "esm", line, typeOnly });
|
|
152
|
-
else if (rawSpec) recordUnresolved(rawSpec, "esm", line, typeOnly);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
addImportEdge(tgt, { typeOnly, line, specifier: rawSpec });
|
|
156
|
-
const clause = node.namedChildren.find((c) => c.type === "import_clause"); if (!clause) continue;
|
|
157
|
-
for (const c of clause.namedChildren) {
|
|
158
|
-
if (c.type === "identifier") imports.set(c.text, { imported: "default", targetFile: tgt, typeOnly, line, specifier: rawSpec });
|
|
159
|
-
else if (c.type === "namespace_import") { const id = c.namedChildren.find((x) => x.type === "identifier"); if (id) imports.set(id.text, { imported: "*", targetFile: tgt, typeOnly, line, specifier: rawSpec }); }
|
|
160
|
-
else if (c.type === "named_imports") for (const s of c.namedChildren) { if (s.type !== "import_specifier") continue; const nm = field(s, "name"), al = field(s, "alias"); if (nm) imports.set((al || nm).text, { imported: nm.text, targetFile: tgt, typeOnly: typeOnly || /^\s*type\b/.test(s.text), line, specifier: rawSpec }); }
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ---- CJS require (declarator form: local bindings + import edges) ----
|
|
165
|
-
for (const cap of caps(grammar, REQUIRE, tree.rootNode)) {
|
|
166
|
-
if (cap.name !== "src") continue;
|
|
167
|
-
let dv = cap.node; while (dv && dv.type !== "variable_declarator") dv = dv.parent;
|
|
168
|
-
const tgt = resolveJsImport(fileRel, cap.node.text); if (!tgt) continue;
|
|
169
|
-
addImportEdge(tgt, { typeOnly: false, line: cap.node.startPosition.row + 1, specifier: cap.node.text });
|
|
170
|
-
const lhs = dv && field(dv, "name");
|
|
171
|
-
if (lhs && lhs.type === "identifier") imports.set(lhs.text, { imported: "*", targetFile: tgt, line: cap.node.startPosition.row + 1, specifier: cap.node.text });
|
|
172
|
-
else if (lhs && lhs.type === "object_pattern") for (const p of lhs.namedChildren) { const key = field(p, "key") || (p.type === "shorthand_property_identifier_pattern" ? p : null); const val = field(p, "value"); const local = (val && val.type === "identifier") ? val : key; if (key && local) imports.set(local.text, { imported: key.text, targetFile: tgt, line: cap.node.startPosition.row + 1, specifier: cap.node.text }); }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ---- CJS require, ALL forms (side-effect require("dotenv/config") included): bare-pkg records +
|
|
176
|
-
// non-literal require(x) → dynamic marker so dep analysis can suppress false "unused" positives ----
|
|
177
|
-
for (const cap of caps(grammar, `(call_expression function: (identifier) @fn)`, tree.rootNode)) {
|
|
178
|
-
if (cap.node.text !== "require") continue;
|
|
179
|
-
const call = cap.node.parent; const args = field(call, "arguments");
|
|
180
|
-
const arg = args && args.namedChildren ? args.namedChildren[0] : null;
|
|
181
|
-
const line = call.startPosition.row + 1;
|
|
182
|
-
if (arg && arg.type === "string") {
|
|
183
|
-
const rawSpec = arg.text.replace(/^['"`]|['"`]$/g, "");
|
|
184
|
-
if (resolveJsImport(fileRel, rawSpec)) continue;
|
|
185
|
-
if (isBareSpec(rawSpec)) addExternalImport({ spec: rawSpec, kind: "cjs", line });
|
|
186
|
-
else recordUnresolved(rawSpec, "cjs", line);
|
|
187
|
-
} else {
|
|
188
|
-
addExternalImport({ dynamic: true, kind: "cjs", line });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// ---- dynamic import(): literal bare → package record; literal local → dynamic marker with target;
|
|
193
|
-
// non-literal → bare dynamic marker ----
|
|
194
|
-
for (const cap of caps(grammar, `(call_expression function: (import) @imp)`, tree.rootNode)) {
|
|
195
|
-
const call = cap.node.parent; const args = field(call, "arguments");
|
|
196
|
-
const arg = args && args.namedChildren ? args.namedChildren[0] : null;
|
|
197
|
-
const line = call.startPosition.row + 1;
|
|
198
|
-
if (arg && arg.type === "string") {
|
|
199
|
-
const rawSpec = arg.text.replace(/^['"`]|['"`]$/g, "");
|
|
200
|
-
const tgt = resolveJsImport(fileRel, rawSpec);
|
|
201
|
-
if (tgt) addExternalImport({ dynamic: true, spec: rawSpec, target: tgt, kind: "dynamic", line });
|
|
202
|
-
else if (isBareSpec(rawSpec)) addExternalImport({ spec: rawSpec, kind: "dynamic", line });
|
|
203
|
-
else if (rawSpec) recordUnresolved(rawSpec, "dynamic", line);
|
|
204
|
-
} else {
|
|
205
|
-
const bounded = boundedUrlImportTarget(arg, {field, fileRel, resolveJsImport});
|
|
206
|
-
addExternalImport({ dynamic: true, kind: "dynamic", line, ...(bounded ? {spec: bounded.rawSpec, target: bounded.target} : {}) });
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// ---- re-exports (barrel/index files): edge so the real target isn't falsely DEAD; bare source → external ----
|
|
211
|
-
for (const cap of caps(grammar, `(export_statement) @exp`, tree.rootNode)) {
|
|
212
|
-
const node = cap.node; const srcNode = field(node, "source");
|
|
213
|
-
if (!srcNode) continue;
|
|
214
|
-
const rawSpec = srcNode.text.replace(/^['"`]|['"`]$/g, "");
|
|
215
|
-
const line = node.startPosition.row + 1;
|
|
216
|
-
const typeOnly = reexportTypeOnly(node);
|
|
217
|
-
const tgt = resolveJsImport(fileRel, rawSpec);
|
|
218
|
-
if (tgt) {
|
|
219
|
-
addImportEdge(tgt, { relation: "re_exports", typeOnly, line, specifier: rawSpec });
|
|
220
|
-
const text = node.text.trim();
|
|
221
|
-
const star = text.match(/^export\s+(type\s+)?\*\s+from\b/);
|
|
222
|
-
const namespace = text.match(/^export\s+(type\s+)?\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\b/);
|
|
223
|
-
const clause = text.match(/^export\s+(type\s+)?\{([\s\S]*?)\}\s+from\b/);
|
|
224
|
-
if (namespace) {
|
|
225
|
-
recordJsExport({ kind: "namespace", exported: namespace[2], imported: "*", targetFile: tgt, typeOnly: !!namespace[1], line, specifier: rawSpec });
|
|
226
|
-
} else if (star) {
|
|
227
|
-
recordJsExport({ kind: "star", exported: "*", imported: "*", targetFile: tgt, typeOnly: !!star[1], line, specifier: rawSpec });
|
|
228
|
-
} else if (clause) {
|
|
229
|
-
for (const spec of parseExportSpecifiers(clause[2])) {
|
|
230
|
-
recordJsExport({ kind: "named", exported: spec.exported, imported: spec.imported, targetFile: tgt, typeOnly: !!clause[1] || spec.typeOnly, line, specifier: rawSpec });
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
else if (isBareSpec(rawSpec)) addExternalImport({ spec: rawSpec, kind: "reexport", line, typeOnly });
|
|
235
|
-
else if (rawSpec) recordUnresolved(rawSpec, "reexport", line, typeOnly);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Local aliases (`export { internal as publicName }`) and default identifiers are part of the
|
|
239
|
-
// same export table used by the post-pass barrel resolver. Sourced clauses were recorded above.
|
|
240
|
-
for (const cap of caps(grammar, `(export_statement) @exp`, tree.rootNode)) {
|
|
241
|
-
const node = cap.node;
|
|
242
|
-
if (field(node, "source")) continue;
|
|
243
|
-
const text = node.text.trim();
|
|
244
|
-
const clause = text.match(/^export\s+(type\s+)?\{([\s\S]*?)\}/);
|
|
245
|
-
if (clause) for (const spec of parseExportSpecifiers(clause[2])) {
|
|
246
|
-
recordJsExport({ kind: "local", exported: spec.exported, local: spec.imported, typeOnly: !!clause[1] || spec.typeOnly, line: node.startPosition.row + 1 });
|
|
247
|
-
}
|
|
248
|
-
const identifierDefault = text.match(/^export\s+default\s+([A-Za-z_$][\w$]*)\s*;?$/);
|
|
249
|
-
if (identifierDefault) recordJsExport({ kind: "local", exported: "default", local: identifierDefault[1], typeOnly: false, line: node.startPosition.row + 1 });
|
|
250
|
-
const defaultValue = field(node, "value");
|
|
251
|
-
if (defaultValue?.type === "object") {
|
|
252
|
-
// Service facades commonly expose local helpers through `export default { getSchema,
|
|
253
|
-
// save: persist }`. Record the public member -> local binding instead of treating the
|
|
254
|
-
// object as an opaque default export.
|
|
255
|
-
recordJsExport({kind: "facade-root", exported: "default", local: "default", typeOnly: false, line: node.startPosition.row + 1});
|
|
256
|
-
for (const property of defaultValue.namedChildren || []) {
|
|
257
|
-
if (["shorthand_property_identifier", "shorthand_property_identifier_pattern"].includes(property.type)) {
|
|
258
|
-
recordJsExport({kind: "facade-member", member: property.text, local: property.text, typeOnly: false, line: node.startPosition.row + 1});
|
|
259
|
-
markExported(property.text);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (property.type !== "pair") continue;
|
|
263
|
-
const key = field(property, "key");
|
|
264
|
-
const value = field(property, "value");
|
|
265
|
-
if (!key || value?.type !== "identifier") continue;
|
|
266
|
-
const member = key.text.replace(/^['"`]|['"`]$/g, "");
|
|
267
|
-
if (!/^[A-Za-z_$][\w$]*$/.test(member)) continue;
|
|
268
|
-
recordJsExport({kind: "facade-member", member, local: value.text, typeOnly: false, line: node.startPosition.row + 1});
|
|
269
|
-
markExported(value.text);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// ---- export markers beyond declarations: `export { a, b }`, `export default X`, CJS module.exports ----
|
|
275
|
-
for (const cap of caps(grammar, `(export_statement (export_clause (export_specifier name: (identifier) @n)))`, tree.rootNode)) {
|
|
276
|
-
let st = cap.node.parent; while (st && st.type !== "export_statement") st = st.parent;
|
|
277
|
-
if (st && !field(st, "source")) markExported(cap.node.text); // with a source it's a re-export, not a local symbol
|
|
278
|
-
}
|
|
279
|
-
for (const cap of caps(grammar, `(export_statement value: (identifier) @def)`, tree.rootNode)) markExported(cap.node.text);
|
|
280
|
-
for (const cap of caps(grammar, `(assignment_expression left: (member_expression) @lhs right: (identifier) @rhs)`, tree.rootNode)) {
|
|
281
|
-
if (cap.name !== "rhs") continue;
|
|
282
|
-
const lhs = field(cap.node.parent, "left"); const l = lhs ? lhs.text : "";
|
|
283
|
-
if (l === "module.exports" || l.startsWith("exports.") || l.startsWith("module.exports.")) markExported(cap.node.text);
|
|
284
|
-
}
|
|
285
|
-
for (const cap of caps(grammar, `(assignment_expression left: (member_expression) right: (object (shorthand_property_identifier) @p))`, tree.rootNode)) {
|
|
286
|
-
let ae = cap.node.parent; while (ae && ae.type !== "assignment_expression") ae = ae.parent;
|
|
287
|
-
const lhs = ae && field(ae, "left");
|
|
288
|
-
if (lhs && (lhs.text === "module.exports" || lhs.text.startsWith("exports."))) markExported(cap.node.text);
|
|
289
|
-
}
|
|
290
|
-
},
|
|
291
|
-
};
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// Python extractor. Symbols: def/class (methods included) + module-level assignments (data consts).
|
|
2
|
-
// Imports: `import a.b [as c]` and `from [.]*mod import name [as alias]` with dotted + relative (leading-dot)
|
|
3
|
-
// resolution to <path>.py / <path>/__init__.py — tried from the repo root, src/ (src-layout), and the
|
|
4
|
-
// importing file's own directory (script-style sys.path). Absolute imports that resolve to NO repo file
|
|
5
|
-
// are recorded as external imports (stdlib → builtin, else ecosystem "PyPI") for dependency analysis.
|
|
6
|
-
// Calls: bare `foo()`. Heritage: class superclasses.
|
|
7
|
-
import { pySpecToPkg, PY_STDLIB } from "./spec-pkg.js";
|
|
8
|
-
|
|
9
|
-
export default {
|
|
10
|
-
family: "py",
|
|
11
|
-
grammars: ["python"],
|
|
12
|
-
exts: { ".py": "python", ".pyi": "python" },
|
|
13
|
-
isWeb: false,
|
|
14
|
-
customCalls: true,
|
|
15
|
-
calls: `(call function: (identifier) @callee)`,
|
|
16
|
-
heritage: [`(class_definition superclasses: (argument_list (identifier) @super))`],
|
|
17
|
-
|
|
18
|
-
pass1(ctx) {
|
|
19
|
-
const { grammar, tree, fileRel, code, caps, field, addSym, addImportEdge, addExternalImport, markExported, imports, links, nameToId, resolvePyPath, pyBaseDir, pyTopDirs } = ctx;
|
|
20
|
-
// the importing file's own dir is on sys.path when it runs as a script — try siblings for absolute
|
|
21
|
-
// imports, EXCEPT stdlib names (a sibling logging.py must not shadow `import logging`; py3 absolute
|
|
22
|
-
// imports always pick the stdlib, and a wrong internal edge distorts dead-code + reachability)
|
|
23
|
-
const scriptDir = pyBaseDir(fileRel, 1);
|
|
24
|
-
const resolveAbs = (parts) => resolvePyPath("", parts) || (scriptDir && !PY_STDLIB.has(parts[0]) ? resolvePyPath(scriptDir, parts) : null);
|
|
25
|
-
const recordExternal = (dottedName, line) => {
|
|
26
|
-
const top = String(dottedName).split(".")[0];
|
|
27
|
-
if (pyTopDirs && pyTopDirs.has(top)) return; // repo-local namespace package (no __init__.py) — internal, not a dep
|
|
28
|
-
const r = pySpecToPkg(top);
|
|
29
|
-
if (r && !r.ambiguous) addExternalImport({ spec: dottedName, pkg: r.pkg, builtin: r.builtin, ecosystem: "PyPI", kind: "py-import", line });
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// ---- symbols (decorated defs are framework-entered — @app.route/@app.event/@pytest.fixture — the
|
|
33
|
-
// flag keeps dead-code checks from calling them dead; tree wraps them in decorated_definition) ----
|
|
34
|
-
const ownedMethods = [];
|
|
35
|
-
for (const cap of caps(grammar, `(function_definition name: (identifier) @fn) (class_definition name: (identifier) @class)`, tree.rootNode)) {
|
|
36
|
-
const decorated = cap.node.parent?.parent?.type === "decorated_definition";
|
|
37
|
-
let ownerName = null;
|
|
38
|
-
if (cap.name === "fn") {
|
|
39
|
-
let parent = cap.node.parent?.parent;
|
|
40
|
-
while (parent && !["function_definition", "class_definition", "module"].includes(parent.type)) parent = parent.parent;
|
|
41
|
-
if (parent?.type === "class_definition") ownerName = field(parent, "name")?.text || null;
|
|
42
|
-
}
|
|
43
|
-
const visibility = /^__[^_].*/.test(cap.node.text) ? "private"
|
|
44
|
-
: /^_[^_]/.test(cap.node.text) ? "protected" : "public";
|
|
45
|
-
const id = addSym(cap.node.text, cap.node.startPosition.row + 1, cap.name === "fn", {
|
|
46
|
-
sourceNode: cap.node.parent,
|
|
47
|
-
selectionNode: cap.node,
|
|
48
|
-
...(decorated ? { decorated: true } : {}),
|
|
49
|
-
symbolKind: cap.name === "class" ? "class" : ownerName ? "method" : "function",
|
|
50
|
-
...(ownerName ? {memberOf: ownerName, visibility} : {moduleDeclaration: true}),
|
|
51
|
-
});
|
|
52
|
-
if (ownerName) ownedMethods.push({ownerName, id});
|
|
53
|
-
}
|
|
54
|
-
for (const method of ownedMethods) {
|
|
55
|
-
const ownerId = nameToId.get(method.ownerName);
|
|
56
|
-
if (ownerId) links.push({source: ownerId, target: method.id, relation: "contains", confidence: "EXTRACTED"});
|
|
57
|
-
}
|
|
58
|
-
for (const cap of caps(grammar, `(module (expression_statement (assignment left: (identifier) @var)))`, tree.rootNode))
|
|
59
|
-
addSym(cap.node.text, cap.node.startPosition.row + 1, false, { sourceNode: cap.node.parent, selectionNode: cap.node, symbolKind: "variable", moduleDeclaration: true });
|
|
60
|
-
|
|
61
|
-
// Static __all__ is authoritative for wildcard imports. The assignment itself remains indexed,
|
|
62
|
-
// while listed declarations are marked as the explicit public module surface.
|
|
63
|
-
const allMatch = String(code || "").match(/(?:^|\n)\s*__all__\s*=\s*[\[(]([\s\S]*?)[\])]/m);
|
|
64
|
-
if (allMatch) for (const match of allMatch[1].matchAll(/["']([A-Za-z_]\w*)["']/g)) markExported(match[1]);
|
|
65
|
-
|
|
66
|
-
// ---- import a.b [as c] ----
|
|
67
|
-
for (const cap of caps(grammar, `(import_statement) @imp`, tree.rootNode)) {
|
|
68
|
-
for (const child of cap.node.namedChildren) {
|
|
69
|
-
const modNode = child.type === "aliased_import" ? field(child, "name") : (child.type === "dotted_name" ? child : null);
|
|
70
|
-
if (!modNode) continue;
|
|
71
|
-
const parts = modNode.text.split(".");
|
|
72
|
-
const tgt = resolveAbs(parts);
|
|
73
|
-
if (tgt) { addImportEdge(tgt); const local = child.type === "aliased_import" ? field(child, "alias")?.text : parts[0]; if (local) imports.set(local, { imported: "*", targetFile: tgt }); }
|
|
74
|
-
else recordExternal(modNode.text, modNode.startPosition.row + 1);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// ---- from [.]*mod import name [, name as alias] ----
|
|
79
|
-
for (const cap of caps(grammar, `(import_from_statement) @imp`, tree.rootNode)) {
|
|
80
|
-
const node = cap.node; const modNode = field(node, "module_name");
|
|
81
|
-
let dots = 0, modParts = [];
|
|
82
|
-
if (modNode) {
|
|
83
|
-
if (modNode.type === "relative_import") { const t = modNode.text; while (dots < t.length && t[dots] === ".") dots++; const rest = t.slice(dots); modParts = rest ? rest.split(".") : []; }
|
|
84
|
-
else if (modNode.type === "dotted_name") modParts = modNode.text.split(".");
|
|
85
|
-
}
|
|
86
|
-
const baseDir = pyBaseDir(fileRel, dots);
|
|
87
|
-
const wildcard = node.namedChildren.some((child) => child.type === "wildcard_import");
|
|
88
|
-
if (wildcard) {
|
|
89
|
-
const modFile = resolvePyPath(baseDir, modParts) || (dots === 0 ? resolveAbs(modParts) : null);
|
|
90
|
-
if (modFile) {
|
|
91
|
-
addImportEdge(modFile);
|
|
92
|
-
imports.set(`*:${node.startPosition.row + 1}`, {imported: "*", targetFile: modFile, wildcard: true, line: node.startPosition.row + 1});
|
|
93
|
-
} else if (dots === 0 && modParts.length) recordExternal(modParts.join("."), node.startPosition.row + 1);
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const names = node.namedChildren.filter((c) => c !== modNode && (c.type === "dotted_name" || c.type === "aliased_import"));
|
|
97
|
-
let externalDone = false; // one record per statement, not per imported name
|
|
98
|
-
for (const nm of names) {
|
|
99
|
-
const impName = nm.type === "aliased_import" ? field(nm, "name")?.text : nm.text;
|
|
100
|
-
const local = nm.type === "aliased_import" ? field(nm, "alias")?.text : impName;
|
|
101
|
-
if (!impName) continue;
|
|
102
|
-
const asSub = resolvePyPath(baseDir, [...modParts, impName]) || (dots === 0 ? resolveAbs([...modParts, impName]) : null); // name may itself be a submodule file
|
|
103
|
-
if (asSub) { addImportEdge(asSub); if (local) imports.set(local, { imported: "*", targetFile: asSub }); continue; }
|
|
104
|
-
const modFile = resolvePyPath(baseDir, modParts) || (dots === 0 ? resolveAbs(modParts) : null);
|
|
105
|
-
if (modFile) { addImportEdge(modFile); if (local) imports.set(local, { imported: impName, targetFile: modFile }); }
|
|
106
|
-
else if (dots === 0 && modParts.length && !externalDone) { externalDone = true; recordExternal(modParts.join("."), node.startPosition.row + 1); }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
pass2(ctx) {
|
|
112
|
-
const {grammar, tree, fileRel, caps, field, enclosing, links, nodeById, perFileSymbols, symByFileName, importedLocals, resolveCall} = ctx;
|
|
113
|
-
const imports = importedLocals.get(fileRel) || new Map();
|
|
114
|
-
const wildcardImports = [...imports.values()].filter((entry) => entry?.wildcard && entry.targetFile);
|
|
115
|
-
const emitted = new Set();
|
|
116
|
-
const addCall = (caller, target, line, evidence) => {
|
|
117
|
-
if (!caller || !target || target === caller.id) return;
|
|
118
|
-
const key = `${caller.id}\0${target}\0${line}`;
|
|
119
|
-
if (emitted.has(key) || links.some((link) => link.source === caller.id && link.target === target && link.relation === "calls" && link.line === line)) return;
|
|
120
|
-
emitted.add(key);
|
|
121
|
-
const provenance = ["receiver-type", "wildcard-import", "module-member"].includes(evidence) ? "RESOLVED" : "INFERRED";
|
|
122
|
-
links.push({source: caller.id, target, relation: "calls", confidence: "INFERRED", provenance, line, pythonResolution: evidence});
|
|
123
|
-
};
|
|
124
|
-
const moduleSymbol = (targetFile, name) => {
|
|
125
|
-
const id = symByFileName.get(targetFile)?.get(name);
|
|
126
|
-
if (!id) return null;
|
|
127
|
-
const node = nodeById.get(id);
|
|
128
|
-
return node?.member_of ? null : id;
|
|
129
|
-
};
|
|
130
|
-
const wildcardCandidates = (name) => {
|
|
131
|
-
const candidates = [];
|
|
132
|
-
for (const imp of wildcardImports) {
|
|
133
|
-
const symbols = perFileSymbols.get(imp.targetFile) || [];
|
|
134
|
-
const explicitAll = symbols.some((symbol) => symbol.name === "__all__");
|
|
135
|
-
const symbol = symbols.find((entry) => entry.name === name && !entry.memberOf);
|
|
136
|
-
if (!symbol || (explicitAll ? nodeById.get(symbol.id)?.exported !== true : name.startsWith("_"))) continue;
|
|
137
|
-
candidates.push(symbol.id);
|
|
138
|
-
}
|
|
139
|
-
return [...new Set(candidates)];
|
|
140
|
-
};
|
|
141
|
-
const classInfo = (name) => {
|
|
142
|
-
const local = symByFileName.get(fileRel)?.get(name);
|
|
143
|
-
if (local && nodeById.get(local)?.symbol_kind === "class") return {id: local, name, file: fileRel};
|
|
144
|
-
const imp = imports.get(name);
|
|
145
|
-
if (imp?.targetFile && !imp.wildcard) {
|
|
146
|
-
const targetFile = imp.originFile || imp.targetFile;
|
|
147
|
-
const targetName = imp.originName || imp.imported || name;
|
|
148
|
-
const id = symByFileName.get(targetFile)?.get(targetName);
|
|
149
|
-
if (id && nodeById.get(id)?.symbol_kind === "class") return {id, name: targetName, file: targetFile};
|
|
150
|
-
}
|
|
151
|
-
const wildcard = wildcardCandidates(name).filter((id) => nodeById.get(id)?.symbol_kind === "class");
|
|
152
|
-
if (wildcard.length === 1) {
|
|
153
|
-
const node = nodeById.get(wildcard[0]);
|
|
154
|
-
return {id: wildcard[0], name: node.label || name, file: node.source_file};
|
|
155
|
-
}
|
|
156
|
-
return null;
|
|
157
|
-
};
|
|
158
|
-
const methodOf = (klass, method) => (perFileSymbols.get(klass.file) || [])
|
|
159
|
-
.find((symbol) => symbol.memberOf === String(nodeById.get(klass.id)?.label || klass.name).replace(/\(.*$/, "") && symbol.name === method)?.id || null;
|
|
160
|
-
const bindings = new Map();
|
|
161
|
-
const shadows = new Set();
|
|
162
|
-
const scopeKey = (caller, name) => `${caller?.id || ""}\0${name}`;
|
|
163
|
-
const bind = (caller, name, klass) => { if (caller && name && klass) bindings.set(scopeKey(caller, name), klass); };
|
|
164
|
-
const typeName = (node) => {
|
|
165
|
-
const text = String(node?.text || "").replace(/^['"]|['"]$/g, "");
|
|
166
|
-
return (text.match(/[A-Za-z_]\w*/) || [])[0] || "";
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
for (const cap of caps(grammar, `(parameters (identifier) @param) (typed_parameter) @typed`, tree.rootNode)) {
|
|
170
|
-
const line = cap.node.startPosition.row + 1;
|
|
171
|
-
const caller = enclosing(fileRel, line);
|
|
172
|
-
if (!caller) continue;
|
|
173
|
-
if (cap.name === "param") {
|
|
174
|
-
shadows.add(scopeKey(caller, cap.node.text));
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
const name = cap.node.namedChildren.find((child) => child.type === "identifier")?.text;
|
|
178
|
-
if (!name) continue;
|
|
179
|
-
shadows.add(scopeKey(caller, name));
|
|
180
|
-
bind(caller, name, classInfo(typeName(field(cap.node, "type"))));
|
|
181
|
-
}
|
|
182
|
-
for (const cap of caps(grammar, `(assignment) @assign`, tree.rootNode)) {
|
|
183
|
-
const assignment = cap.node;
|
|
184
|
-
const caller = enclosing(fileRel, assignment.startPosition.row + 1);
|
|
185
|
-
if (!caller) continue;
|
|
186
|
-
const left = field(assignment, "left");
|
|
187
|
-
const right = field(assignment, "right");
|
|
188
|
-
let receiver = "";
|
|
189
|
-
if (left?.type === "identifier") receiver = left.text;
|
|
190
|
-
else if (left?.type === "attribute") receiver = left.text;
|
|
191
|
-
if (!receiver) continue;
|
|
192
|
-
shadows.add(scopeKey(caller, receiver));
|
|
193
|
-
const annotation = typeName(field(assignment, "type"));
|
|
194
|
-
const constructor = right?.type === "call" && field(right, "function")?.type === "identifier"
|
|
195
|
-
? field(right, "function").text : "";
|
|
196
|
-
bind(caller, receiver, classInfo(annotation || constructor));
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
for (const cap of caps(grammar, `(call function: (identifier) @callee)`, tree.rootNode)) {
|
|
200
|
-
const line = cap.node.startPosition.row + 1;
|
|
201
|
-
const caller = enclosing(fileRel, line);
|
|
202
|
-
if (!caller || shadows.has(scopeKey(caller, cap.node.text))) continue;
|
|
203
|
-
let target = resolveCall(cap.node.text, fileRel);
|
|
204
|
-
if (target && nodeById.get(target)?.member_of) target = null;
|
|
205
|
-
let evidence = "bare";
|
|
206
|
-
if (!target) {
|
|
207
|
-
const candidates = wildcardCandidates(cap.node.text);
|
|
208
|
-
if (candidates.length === 1) { target = candidates[0]; evidence = "wildcard-import"; }
|
|
209
|
-
}
|
|
210
|
-
addCall(caller, target, line, evidence);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for (const cap of caps(grammar, `(call function: (attribute) @attributeCall)`, tree.rootNode)) {
|
|
214
|
-
const attribute = cap.node;
|
|
215
|
-
const object = field(attribute, "object");
|
|
216
|
-
const member = field(attribute, "attribute");
|
|
217
|
-
if (!object || !member) continue;
|
|
218
|
-
const line = attribute.startPosition.row + 1;
|
|
219
|
-
const caller = enclosing(fileRel, line);
|
|
220
|
-
if (!caller) continue;
|
|
221
|
-
if (object.type === "identifier") {
|
|
222
|
-
const moduleImport = imports.get(object.text);
|
|
223
|
-
if (moduleImport?.targetFile && moduleImport.imported === "*" && !moduleImport.wildcard) {
|
|
224
|
-
addCall(caller, moduleSymbol(moduleImport.targetFile, member.text), line, "module-member");
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
let klass = null;
|
|
229
|
-
if (["self", "cls"].includes(object.text) && caller.memberOf) klass = classInfo(caller.memberOf);
|
|
230
|
-
if (!klass) klass = bindings.get(scopeKey(caller, object.text));
|
|
231
|
-
if (!klass && object.type === "call" && field(object, "function")?.type === "identifier") klass = classInfo(field(object, "function").text);
|
|
232
|
-
addCall(caller, klass && methodOf(klass, member.text), line, "receiver-type");
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Rust scoped-call resolution (`Path::method()`), split from lang-rust.js. Resolved with the full
|
|
2
|
-
// qualifier in hand so an external associated function (`OpenOptions::new()`, `Vec::from()`) never binds
|
|
3
|
-
// its bare final name to an unrelated same-named function in the directory. Only heads with real evidence
|
|
4
|
-
// produce an edge: crate/self/super anchors, `use`-bound names (including sibling-crate modules),
|
|
5
|
-
// fully-qualified sibling crates, and local types whose exact impl method is indexed. A std/external
|
|
6
|
-
// type head is left unresolved on purpose.
|
|
7
|
-
|
|
8
|
-
// Symbol kinds that can own an associated function via `Type::method`.
|
|
9
|
-
const TYPE_KINDS = new Set(["struct", "enum", "trait", "type", "union"]);
|
|
10
|
-
|
|
11
|
-
// `pathParts` and `inlineAncestors` stay owned by lang-rust.js (pass 1 uses them too) and are passed in,
|
|
12
|
-
// so this module never imports back from lang-rust.js.
|
|
13
|
-
export function rustScopedCalls(ctx, { pathParts, inlineAncestors }) {
|
|
14
|
-
const { grammar, tree, fileRel, caps, enclosing, links, nodeById, importedLocals,
|
|
15
|
-
dirSymbols, resolveNamedSymbol, resolveRustMethod, resolveRustPath, resolveRustCratePath } = ctx;
|
|
16
|
-
const dir = fileRel.includes("/") ? fileRel.slice(0, fileRel.lastIndexOf("/")) : "";
|
|
17
|
-
const localSyms = dirSymbols?.get(dir);
|
|
18
|
-
const emitCall = (node, target) => {
|
|
19
|
-
const caller = enclosing(fileRel, node.startPosition.row + 1);
|
|
20
|
-
if (caller && target && target !== caller.id) {
|
|
21
|
-
links.push({ source: caller.id, target, relation: "calls", confidence: "INFERRED", line: node.startPosition.row + 1 });
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
for (const cap of caps(grammar, `(call_expression function: (scoped_identifier) @path)`, tree.rootNode)) {
|
|
25
|
-
const node = cap.node;
|
|
26
|
-
const segments = pathParts(node);
|
|
27
|
-
if (segments.length < 2) continue;
|
|
28
|
-
const head = segments[0];
|
|
29
|
-
const method = segments.at(-1);
|
|
30
|
-
|
|
31
|
-
if (["crate", "self", "super"].includes(head)) {
|
|
32
|
-
const resolved = resolveRustPath(fileRel, segments, { inlineModules: inlineAncestors(node), unqualified: false });
|
|
33
|
-
if (resolved) emitCall(node, resolveNamedSymbol(resolved.targetFile, method, "value"));
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const bound = importedLocals?.get(fileRel)?.get(head);
|
|
38
|
-
if (bound?.targetFile) {
|
|
39
|
-
emitCall(node, resolveNamedSymbol(bound.targetFile, method, "value"));
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const crateResolved = resolveRustCratePath ? resolveRustCratePath(head, segments.slice(1, -1)) : null;
|
|
44
|
-
if (crateResolved) {
|
|
45
|
-
emitCall(node, resolveNamedSymbol(crateResolved.targetFile, method, "value"));
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Local type's associated function: resolve the exact impl member. Nothing else (enum-variant
|
|
50
|
-
// construction, external types) has an indexed target, so it produces no edge.
|
|
51
|
-
const headId = localSyms?.get(head);
|
|
52
|
-
if (headId && TYPE_KINDS.has(nodeById.get(headId)?.symbol_kind)) {
|
|
53
|
-
emitCall(node, resolveRustMethod(dir, head, method));
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Head is a locally-declared outlined module used without `use` (`mod utils; utils::init()`), the
|
|
58
|
-
// dominant flat-file idiom. resolveRustPath matches only a REAL repo module file, so an external type
|
|
59
|
-
// head (OpenOptions, Vec) resolves to null and still produces no edge — no dir-scope mis-binding.
|
|
60
|
-
const modResolved = resolveRustPath?.(fileRel, segments.slice(0, -1), { inlineModules: inlineAncestors(node), unqualified: true });
|
|
61
|
-
if (modResolved) emitCall(node, resolveNamedSymbol(modResolved.targetFile, method, "value"));
|
|
62
|
-
}
|
|
63
|
-
}
|