gitnexus 1.6.3-rc.8 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/dist/_shared/graph/types.d.ts +16 -0
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +20 -2
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +11 -0
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/scope-resolution/def-index.js +2 -2
- package/dist/_shared/scope-resolution/def-index.js.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/method-dispatch-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/method-dispatch-index.js +2 -2
- package/dist/_shared/scope-resolution/method-dispatch-index.js.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.d.ts +8 -0
- package/dist/_shared/scope-resolution/module-scope-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/module-scope-index.js +10 -2
- package/dist/_shared/scope-resolution/module-scope-index.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +76 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/parsed-file.js +54 -0
- package/dist/_shared/scope-resolution/parsed-file.js.map +1 -0
- package/dist/_shared/scope-resolution/position-index.d.ts +12 -0
- package/dist/_shared/scope-resolution/position-index.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/position-index.js +2 -2
- package/dist/_shared/scope-resolution/position-index.js.map +1 -1
- package/dist/_shared/scope-resolution/qualified-name-index.js +2 -2
- package/dist/_shared/scope-resolution/qualified-name-index.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +75 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/reference-site.js +24 -0
- package/dist/_shared/scope-resolution/reference-site.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts +27 -0
- package/dist/_shared/scope-resolution/registries/class-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js +30 -0
- package/dist/_shared/scope-resolution/registries/class-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts +69 -0
- package/dist/_shared/scope-resolution/registries/context.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/context.js +44 -0
- package/dist/_shared/scope-resolution/registries/context.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts +56 -0
- package/dist/_shared/scope-resolution/registries/evidence.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/evidence.js +150 -0
- package/dist/_shared/scope-resolution/registries/evidence.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts +26 -0
- package/dist/_shared/scope-resolution/registries/field-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js +31 -0
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts +81 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js +332 -0
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts +33 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js +56 -0
- package/dist/_shared/scope-resolution/registries/lookup-qualified.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts +36 -0
- package/dist/_shared/scope-resolution/registries/method-registry.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js +32 -0
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts +43 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.d.ts.map +1 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js +60 -0
- package/dist/_shared/scope-resolution/registries/tie-breaks.js.map +1 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts +1 -10
- package/dist/_shared/scope-resolution/resolve-type-ref.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/resolve-type-ref.js +6 -0
- package/dist/_shared/scope-resolution/resolve-type-ref.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts +4 -4
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js +3 -2
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts +6 -2
- package/dist/_shared/scope-resolution/shadow/aggregate.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/shadow/aggregate.js +5 -0
- package/dist/_shared/scope-resolution/shadow/aggregate.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +11 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.js +35 -4
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +31 -1
- package/dist/cli/clean.js +19 -1
- package/dist/cli/group.js +73 -0
- package/dist/cli/index-repo.js +8 -1
- package/dist/cli/index.js +26 -1
- package/dist/cli/list.js +11 -1
- package/dist/cli/remove.d.ts +30 -0
- package/dist/cli/remove.js +99 -0
- package/dist/cli/setup.js +185 -57
- package/dist/cli/tool.d.ts +5 -0
- package/dist/cli/tool.js +42 -0
- package/dist/config/ignore-service.d.ts +9 -0
- package/dist/config/ignore-service.js +80 -13
- package/dist/core/embedding-mode.d.ts +30 -0
- package/dist/core/embedding-mode.js +30 -0
- package/dist/core/embeddings/ast-utils.js +22 -22
- package/dist/core/embeddings/chunker.js +30 -25
- package/dist/core/embeddings/embedding-pipeline.d.ts +6 -0
- package/dist/core/embeddings/embedding-pipeline.js +15 -6
- package/dist/core/embeddings/text-generator.d.ts +1 -1
- package/dist/core/embeddings/text-generator.js +33 -24
- package/dist/core/embeddings/types.d.ts +43 -1
- package/dist/core/embeddings/types.js +101 -29
- package/dist/core/git-staleness.d.ts +18 -0
- package/dist/core/git-staleness.js +108 -0
- package/dist/core/graph/graph.js +115 -20
- package/dist/core/graph/types.d.ts +12 -1
- package/dist/core/group/config-parser.d.ts +4 -0
- package/dist/core/group/config-parser.js +18 -1
- package/dist/core/group/cross-impact.d.ts +41 -0
- package/dist/core/group/cross-impact.js +441 -0
- package/dist/core/group/extractors/http-patterns/php.js +126 -18
- package/dist/core/group/group-path-utils.d.ts +17 -0
- package/dist/core/group/group-path-utils.js +40 -0
- package/dist/core/group/resolve-at-member.d.ts +10 -0
- package/dist/core/group/resolve-at-member.js +31 -0
- package/dist/core/group/service.d.ts +9 -0
- package/dist/core/group/service.js +259 -25
- package/dist/core/group/types.d.ts +30 -0
- package/dist/core/ingestion/ast-cache.d.ts +16 -1
- package/dist/core/ingestion/ast-cache.js +14 -2
- package/dist/core/ingestion/call-processor.js +9 -0
- package/dist/core/ingestion/emit-references.d.ts +88 -0
- package/dist/core/ingestion/emit-references.js +229 -0
- package/dist/core/ingestion/filesystem-walker.js +6 -4
- package/dist/core/ingestion/finalize-orchestrator.d.ts +63 -0
- package/dist/core/ingestion/finalize-orchestrator.js +139 -0
- package/dist/core/ingestion/framework-detection.js +6 -2
- package/dist/core/ingestion/import-processor.js +4 -0
- package/dist/core/ingestion/import-resolvers/python.js +9 -6
- package/dist/core/ingestion/import-target-adapter.d.ts +73 -0
- package/dist/core/ingestion/import-target-adapter.js +95 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -33
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +21 -0
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +56 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.d.ts +26 -0
- package/dist/core/ingestion/languages/csharp/arity-metadata.js +46 -0
- package/dist/core/ingestion/languages/csharp/arity.d.ts +23 -0
- package/dist/core/ingestion/languages/csharp/arity.js +37 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/csharp/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/captures.js +249 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.d.ts +19 -0
- package/dist/core/ingestion/languages/csharp/import-decomposer.js +93 -0
- package/dist/core/ingestion/languages/csharp/import-target.d.ts +25 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +123 -0
- package/dist/core/ingestion/languages/csharp/index.d.ts +82 -0
- package/dist/core/ingestion/languages/csharp/index.js +82 -0
- package/dist/core/ingestion/languages/csharp/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/csharp/interpret.js +132 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.d.ts +27 -0
- package/dist/core/ingestion/languages/csharp/merge-bindings.js +55 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.d.ts +50 -0
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +374 -0
- package/dist/core/ingestion/languages/csharp/query.d.ts +35 -0
- package/dist/core/ingestion/languages/csharp/query.js +515 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.d.ts +31 -0
- package/dist/core/ingestion/languages/csharp/receiver-binding.js +135 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.d.ts +10 -0
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +63 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.d.ts +53 -0
- package/dist/core/ingestion/languages/csharp/simple-hooks.js +76 -0
- package/dist/core/ingestion/languages/csharp.js +14 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +24 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +45 -0
- package/dist/core/ingestion/languages/python/arity.d.ts +22 -0
- package/dist/core/ingestion/languages/python/arity.js +38 -0
- package/dist/core/ingestion/languages/python/cache-stats.d.ts +17 -0
- package/dist/core/ingestion/languages/python/cache-stats.js +28 -0
- package/dist/core/ingestion/languages/python/captures.d.ts +19 -0
- package/dist/core/ingestion/languages/python/captures.js +106 -0
- package/dist/core/ingestion/languages/python/import-decomposer.d.ts +15 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +112 -0
- package/dist/core/ingestion/languages/python/import-target.d.ts +21 -0
- package/dist/core/ingestion/languages/python/import-target.js +99 -0
- package/dist/core/ingestion/languages/python/index.d.ts +80 -0
- package/dist/core/ingestion/languages/python/index.js +80 -0
- package/dist/core/ingestion/languages/python/interpret.d.ts +15 -0
- package/dist/core/ingestion/languages/python/interpret.js +191 -0
- package/dist/core/ingestion/languages/python/merge-bindings.d.ts +16 -0
- package/dist/core/ingestion/languages/python/merge-bindings.js +44 -0
- package/dist/core/ingestion/languages/python/query.d.ts +9 -0
- package/dist/core/ingestion/languages/python/query.js +267 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +21 -0
- package/dist/core/ingestion/languages/python/receiver-binding.js +116 -0
- package/dist/core/ingestion/languages/python/scope-resolver.d.ts +16 -0
- package/dist/core/ingestion/languages/python/scope-resolver.js +53 -0
- package/dist/core/ingestion/languages/python/simple-hooks.d.ts +23 -0
- package/dist/core/ingestion/languages/python/simple-hooks.js +35 -0
- package/dist/core/ingestion/languages/python.js +14 -0
- package/dist/core/ingestion/model/method-registry.d.ts +9 -0
- package/dist/core/ingestion/model/method-registry.js +4 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.d.ts +59 -0
- package/dist/core/ingestion/model/scope-resolution-indexes.js +42 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +64 -0
- package/dist/core/ingestion/model/semantic-model.js +55 -0
- package/dist/core/ingestion/mro-processor.js +38 -22
- package/dist/core/ingestion/parsing-processor.d.ts +18 -1
- package/dist/core/ingestion/parsing-processor.js +45 -11
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +17 -2
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +18 -0
- package/dist/core/ingestion/pipeline.js +2 -1
- package/dist/core/ingestion/registry-primary-flag.d.ts +86 -0
- package/dist/core/ingestion/registry-primary-flag.js +111 -0
- package/dist/core/ingestion/resolve-references.d.ts +63 -0
- package/dist/core/ingestion/resolve-references.js +175 -0
- package/dist/core/ingestion/scope-extractor-bridge.d.ts +32 -0
- package/dist/core/ingestion/scope-extractor-bridge.js +44 -0
- package/dist/core/ingestion/scope-extractor.d.ts +86 -0
- package/dist/core/ingestion/scope-extractor.js +758 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +372 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +212 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +43 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +79 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +57 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +112 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +46 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.d.ts +19 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +30 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +37 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +113 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +38 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +73 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +198 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +27 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +131 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.d.ts +48 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-return-types.js +130 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.d.ts +42 -0
- package/dist/core/ingestion/scope-resolution/passes/mro.js +99 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +26 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +61 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +46 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +327 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +47 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +130 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.d.ts +68 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +125 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.d.ts +17 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +66 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +157 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +36 -0
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +52 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +127 -0
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +349 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +52 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.js +61 -0
- package/dist/core/ingestion/shadow-harness.d.ts +113 -0
- package/dist/core/ingestion/shadow-harness.js +148 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +19 -1
- package/dist/core/ingestion/utils/ast-helpers.js +70 -0
- package/dist/core/ingestion/utils/max-file-size.d.ts +20 -0
- package/dist/core/ingestion/utils/max-file-size.js +52 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +9 -0
- package/dist/core/ingestion/workers/parse-worker.js +57 -21
- package/dist/core/lbug/lbug-adapter.d.ts +22 -2
- package/dist/core/lbug/lbug-adapter.js +58 -14
- package/dist/core/lbug/pool-adapter.d.ts +17 -0
- package/dist/core/lbug/pool-adapter.js +24 -14
- package/dist/core/run-analyze.d.ts +32 -0
- package/dist/core/run-analyze.js +74 -19
- package/dist/core/search/bm25-index.d.ts +18 -0
- package/dist/core/search/bm25-index.js +125 -12
- package/dist/core/tree-sitter/parser-loader.js +6 -1
- package/dist/mcp/local/local-backend.d.ts +67 -3
- package/dist/mcp/local/local-backend.js +296 -34
- package/dist/mcp/resources.d.ts +31 -0
- package/dist/mcp/resources.js +100 -17
- package/dist/mcp/tools.d.ts +4 -1
- package/dist/mcp/tools.js +75 -54
- package/dist/server/api.js +6 -2
- package/dist/storage/git.d.ts +49 -0
- package/dist/storage/git.js +111 -0
- package/dist/storage/repo-manager.d.ts +246 -1
- package/dist/storage/repo-manager.js +391 -9
- package/package.json +7 -6
- package/scripts/bench-scope-resolution.ts +134 -0
- package/scripts/ci-list-migrated-languages.ts +24 -0
- package/skills/gitnexus-cli.md +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to the existing `resolvePythonImportInternal` (PEP-328
|
|
5
|
+
* relative resolution + standard suffix matching). The `WorkspaceIndex`
|
|
6
|
+
* is opaque at this layer; consumers wire a `PythonResolveContext`
|
|
7
|
+
* shape carrying `fromFile` + `allFilePaths`.
|
|
8
|
+
*
|
|
9
|
+
* Returning `null` lets the finalize algorithm mark the edge as
|
|
10
|
+
* `linkStatus: 'unresolved'`.
|
|
11
|
+
*/
|
|
12
|
+
import type { ParsedImport, WorkspaceIndex } from '../../../../_shared/index.js';
|
|
13
|
+
export interface PythonResolveContext {
|
|
14
|
+
readonly fromFile: string;
|
|
15
|
+
/** Mutable `Set` because the legacy `resolvePythonImportInternal`
|
|
16
|
+
* chain downstream is typed to accept `Set<string>`. Callers that
|
|
17
|
+
* only hold a `ReadonlySet` should copy via `new Set(...)` at the
|
|
18
|
+
* adapter boundary. */
|
|
19
|
+
readonly allFilePaths: Set<string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function resolvePythonImportTarget(parsedImport: ParsedImport, workspaceIndex: WorkspaceIndex): string | null;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
|
|
3
|
+
*
|
|
4
|
+
* Delegates to the existing `resolvePythonImportInternal` (PEP-328
|
|
5
|
+
* relative resolution + standard suffix matching). The `WorkspaceIndex`
|
|
6
|
+
* is opaque at this layer; consumers wire a `PythonResolveContext`
|
|
7
|
+
* shape carrying `fromFile` + `allFilePaths`.
|
|
8
|
+
*
|
|
9
|
+
* Returning `null` lets the finalize algorithm mark the edge as
|
|
10
|
+
* `linkStatus: 'unresolved'`.
|
|
11
|
+
*/
|
|
12
|
+
import { resolvePythonImportInternal } from '../../import-resolvers/python.js';
|
|
13
|
+
export function resolvePythonImportTarget(parsedImport, workspaceIndex) {
|
|
14
|
+
// WorkspaceIndex is `unknown` in the shared contract (Ring 1
|
|
15
|
+
// placeholder). The scope-resolution orchestrator hands us a
|
|
16
|
+
// PythonResolveContext-shaped object; narrow structurally rather
|
|
17
|
+
// than via a cast chain so unexpected shapes return null cleanly.
|
|
18
|
+
const ctx = workspaceIndex;
|
|
19
|
+
if (ctx === undefined ||
|
|
20
|
+
typeof ctx.fromFile !== 'string' ||
|
|
21
|
+
!(ctx.allFilePaths instanceof Set)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (parsedImport.kind === 'dynamic-unresolved')
|
|
25
|
+
return null;
|
|
26
|
+
if (parsedImport.targetRaw === null || parsedImport.targetRaw === '')
|
|
27
|
+
return null;
|
|
28
|
+
// PEP-328 relative + single-segment proximity bare imports.
|
|
29
|
+
const internal = resolvePythonImportInternal(ctx.fromFile, parsedImport.targetRaw, ctx.allFilePaths);
|
|
30
|
+
if (internal !== null)
|
|
31
|
+
return internal;
|
|
32
|
+
// PEP-328: unresolved relative imports must NOT fall through to suffix
|
|
33
|
+
// matching. Mirrors `pythonImportStrategy` in `configs/python.ts`.
|
|
34
|
+
if (parsedImport.targetRaw.startsWith('.'))
|
|
35
|
+
return null;
|
|
36
|
+
// External dotted imports like `django.apps` must not fall through to
|
|
37
|
+
// generic suffix matching when the repo has unrelated local files such
|
|
38
|
+
// as `accounts/apps.py`. Mirrors `pythonImportStrategy`'s
|
|
39
|
+
// `hasRepoCandidate` check: only suffix-match if the leading segment
|
|
40
|
+
// looks like a local package/module somewhere in-repo.
|
|
41
|
+
const pathLike = parsedImport.targetRaw.replace(/\./g, '/');
|
|
42
|
+
if (pathLike.includes('/')) {
|
|
43
|
+
const [leadingSegment] = pathLike.split('/').filter(Boolean);
|
|
44
|
+
if (!leadingSegment || !hasRepoCandidate(leadingSegment, ctx.allFilePaths)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Multi-segment absolute resolve: try exact paths first, then suffix
|
|
49
|
+
// match in nested repos. Using direct `Set.has` + `endsWith` instead of
|
|
50
|
+
// `suffixResolve`'s shared helper because that helper requires a
|
|
51
|
+
// pre-built `SuffixIndex` to disambiguate ties — without one it falls
|
|
52
|
+
// back to an O(files) scan that silently picks the wrong file when
|
|
53
|
+
// the last segment collides across directories (e.g. `accounts.models`
|
|
54
|
+
// matching `billing/models.py` when both files exist).
|
|
55
|
+
return resolveAbsoluteFromFiles(pathLike, ctx.allFilePaths);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Resolve `package/sub/module` style paths (already dot-flattened) to a
|
|
59
|
+
* concrete file in `allFilePaths`. Tries the exact path first, then the
|
|
60
|
+
* `__init__.py` variant, then a suffix match for nested layouts.
|
|
61
|
+
* Returns the original (un-normalized) path from the set.
|
|
62
|
+
*/
|
|
63
|
+
function resolveAbsoluteFromFiles(pathLike, allFilePaths) {
|
|
64
|
+
const directFile = `${pathLike}.py`;
|
|
65
|
+
const directPkg = `${pathLike}/__init__.py`;
|
|
66
|
+
const suffixFile = `/${directFile}`;
|
|
67
|
+
const suffixPkg = `/${directPkg}`;
|
|
68
|
+
let suffixMatch = null;
|
|
69
|
+
for (const raw of allFilePaths) {
|
|
70
|
+
const f = raw.replace(/\\/g, '/');
|
|
71
|
+
if (f === directFile || f === directPkg)
|
|
72
|
+
return raw;
|
|
73
|
+
if (suffixMatch === null && (f.endsWith(suffixFile) || f.endsWith(suffixPkg))) {
|
|
74
|
+
suffixMatch = raw;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return suffixMatch;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Does the repo contain a module/package named `leadingSegment` at the top
|
|
81
|
+
* level? Used to guard against false-positive suffix matches on external
|
|
82
|
+
* dotted imports (e.g. `django.apps` matching a local `accounts/apps.py`).
|
|
83
|
+
*
|
|
84
|
+
* Checks, in order: `<segment>.py` root file, `<segment>/__init__.py`
|
|
85
|
+
* regular package, or any `<segment>/**.py` file (namespace package).
|
|
86
|
+
*/
|
|
87
|
+
function hasRepoCandidate(leadingSegment, allFilePaths) {
|
|
88
|
+
const prefix = `${leadingSegment}/`;
|
|
89
|
+
const rootFile = `${leadingSegment}.py`;
|
|
90
|
+
const initFile = `${leadingSegment}/__init__.py`;
|
|
91
|
+
for (const raw of allFilePaths) {
|
|
92
|
+
const f = raw.replace(/\\/g, '/');
|
|
93
|
+
if (f === rootFile || f === initFile)
|
|
94
|
+
return true;
|
|
95
|
+
if (f.startsWith(prefix) && f.endsWith('.py'))
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python scope-resolution hooks (RFC #909 Ring 3, RFC §5).
|
|
3
|
+
*
|
|
4
|
+
* Public API barrel. Consumers should import from this file rather than
|
|
5
|
+
* the individual modules — that keeps the per-hook organization an
|
|
6
|
+
* implementation detail we can refactor without touching the provider
|
|
7
|
+
* wiring.
|
|
8
|
+
*
|
|
9
|
+
* Module layout (each file is a single concern):
|
|
10
|
+
*
|
|
11
|
+
* - `query.ts` — tree-sitter query string + lazy parser/query singletons
|
|
12
|
+
* - `ast-utils.ts` — generic `SyntaxNode` helpers
|
|
13
|
+
* - `import-decomposer.ts` — `import a, b` / `from m import x, y` → one match per name
|
|
14
|
+
* - `receiver-binding.ts` — synthesize `self`/`cls` type bindings on methods
|
|
15
|
+
* - `captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
|
|
16
|
+
* - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
|
|
17
|
+
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
18
|
+
* - `merge-bindings.ts` — Python LEGB precedence
|
|
19
|
+
* - `arity.ts` — Python arity check (`*args`, `**kwargs`, defaults)
|
|
20
|
+
* - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
|
|
21
|
+
* - `simple-hooks.ts` — small/no-op hooks made explicit
|
|
22
|
+
*
|
|
23
|
+
* ## Known limitations
|
|
24
|
+
*
|
|
25
|
+
* The Python registry-primary path intentionally does NOT resolve the
|
|
26
|
+
* following. Each is a conscious trade-off at migration time; lifting any
|
|
27
|
+
* of them is tracked as a separate follow-up rather than silently
|
|
28
|
+
* "maybe-resolving" and emitting low-confidence edges.
|
|
29
|
+
*
|
|
30
|
+
* 1. **Dynamic attribute access** — `getattr(obj, 'name')` and
|
|
31
|
+
* `setattr` bind at runtime. We emit no edge; the call site
|
|
32
|
+
* surfaces as an unresolved reference.
|
|
33
|
+
* 2. **Dynamic imports** — `importlib.import_module(...)` and
|
|
34
|
+
* `__import__(...)` are not followed. Static `import x` and
|
|
35
|
+
* `from m import x` are fully resolved.
|
|
36
|
+
* 3. **Metaclass-driven dispatch** — C3 linearization drives MRO
|
|
37
|
+
* (see `mro-processor.ts`), but method resolution that depends
|
|
38
|
+
* on `__getattribute__` overrides or metaclass `__call__`
|
|
39
|
+
* remains unresolved.
|
|
40
|
+
* 4. **Union / Optional type hints** — `def f(x: Union[A, B])` or
|
|
41
|
+
* `x: Optional[A]`: `arity.ts` validates parameter count only;
|
|
42
|
+
* receiver-binding and field-type resolution pick the first arm
|
|
43
|
+
* and emit a single edge rather than branching. `List[T]` /
|
|
44
|
+
* `Dict[K, V]` strip the outer generic for receiver typing (see
|
|
45
|
+
* `interpret.ts`).
|
|
46
|
+
* 5. **Decorators that rewrite signatures** — `@dataclass`,
|
|
47
|
+
* `@property`, `@classmethod`, `@staticmethod` are recognized
|
|
48
|
+
* by `receiver-binding.ts`. Arbitrary decorators (e.g.
|
|
49
|
+
* `functools.wraps`, custom retry wrappers) preserve the wrapped
|
|
50
|
+
* function's declared signature; a decorator that returns a
|
|
51
|
+
* different callable is followed only through the declared
|
|
52
|
+
* return type.
|
|
53
|
+
* 6. **`typing.TYPE_CHECKING`-guarded imports** — treated like any
|
|
54
|
+
* other `import` for reference resolution. We do not distinguish
|
|
55
|
+
* runtime-visible from type-checker-only imports; this is
|
|
56
|
+
* intentional (type-only imports are still structurally valid
|
|
57
|
+
* type references).
|
|
58
|
+
* 7. **`*args` / `**kwargs` type flow-through** — `arity.ts`
|
|
59
|
+
* accepts any call count when a variadic is present, but no
|
|
60
|
+
* type information flows through the variadic into the callee
|
|
61
|
+
* body. Receiver-binding still works for explicit parameters.
|
|
62
|
+
* 8. **`super()` outside a method with a literal class binding** —
|
|
63
|
+
* resolved for the standard `class Child(Parent): def m(self):
|
|
64
|
+
* super().m()` pattern. Zero-arg `super()` inside a nested
|
|
65
|
+
* function, a `functools.wraps`-rewrapped method, or a call
|
|
66
|
+
* site where the enclosing class can't be statically determined
|
|
67
|
+
* is left unresolved.
|
|
68
|
+
*
|
|
69
|
+
* Shadow-harness corpus parity is the authoritative signal for which
|
|
70
|
+
* of these matter in practice. The CI parity gate blocks any PR that
|
|
71
|
+
* regresses either the legacy or registry-primary run of
|
|
72
|
+
* `test/integration/resolvers/python.test.ts`.
|
|
73
|
+
*/
|
|
74
|
+
export { emitPythonScopeCaptures } from './captures.js';
|
|
75
|
+
export { getPythonCaptureCacheStats, resetPythonCaptureCacheStats } from './cache-stats.js';
|
|
76
|
+
export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.js';
|
|
77
|
+
export { pythonMergeBindings } from './merge-bindings.js';
|
|
78
|
+
export { pythonArityCompatibility } from './arity.js';
|
|
79
|
+
export { resolvePythonImportTarget, type PythonResolveContext } from './import-target.js';
|
|
80
|
+
export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python scope-resolution hooks (RFC #909 Ring 3, RFC §5).
|
|
3
|
+
*
|
|
4
|
+
* Public API barrel. Consumers should import from this file rather than
|
|
5
|
+
* the individual modules — that keeps the per-hook organization an
|
|
6
|
+
* implementation detail we can refactor without touching the provider
|
|
7
|
+
* wiring.
|
|
8
|
+
*
|
|
9
|
+
* Module layout (each file is a single concern):
|
|
10
|
+
*
|
|
11
|
+
* - `query.ts` — tree-sitter query string + lazy parser/query singletons
|
|
12
|
+
* - `ast-utils.ts` — generic `SyntaxNode` helpers
|
|
13
|
+
* - `import-decomposer.ts` — `import a, b` / `from m import x, y` → one match per name
|
|
14
|
+
* - `receiver-binding.ts` — synthesize `self`/`cls` type bindings on methods
|
|
15
|
+
* - `captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
|
|
16
|
+
* - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
|
|
17
|
+
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
18
|
+
* - `merge-bindings.ts` — Python LEGB precedence
|
|
19
|
+
* - `arity.ts` — Python arity check (`*args`, `**kwargs`, defaults)
|
|
20
|
+
* - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
|
|
21
|
+
* - `simple-hooks.ts` — small/no-op hooks made explicit
|
|
22
|
+
*
|
|
23
|
+
* ## Known limitations
|
|
24
|
+
*
|
|
25
|
+
* The Python registry-primary path intentionally does NOT resolve the
|
|
26
|
+
* following. Each is a conscious trade-off at migration time; lifting any
|
|
27
|
+
* of them is tracked as a separate follow-up rather than silently
|
|
28
|
+
* "maybe-resolving" and emitting low-confidence edges.
|
|
29
|
+
*
|
|
30
|
+
* 1. **Dynamic attribute access** — `getattr(obj, 'name')` and
|
|
31
|
+
* `setattr` bind at runtime. We emit no edge; the call site
|
|
32
|
+
* surfaces as an unresolved reference.
|
|
33
|
+
* 2. **Dynamic imports** — `importlib.import_module(...)` and
|
|
34
|
+
* `__import__(...)` are not followed. Static `import x` and
|
|
35
|
+
* `from m import x` are fully resolved.
|
|
36
|
+
* 3. **Metaclass-driven dispatch** — C3 linearization drives MRO
|
|
37
|
+
* (see `mro-processor.ts`), but method resolution that depends
|
|
38
|
+
* on `__getattribute__` overrides or metaclass `__call__`
|
|
39
|
+
* remains unresolved.
|
|
40
|
+
* 4. **Union / Optional type hints** — `def f(x: Union[A, B])` or
|
|
41
|
+
* `x: Optional[A]`: `arity.ts` validates parameter count only;
|
|
42
|
+
* receiver-binding and field-type resolution pick the first arm
|
|
43
|
+
* and emit a single edge rather than branching. `List[T]` /
|
|
44
|
+
* `Dict[K, V]` strip the outer generic for receiver typing (see
|
|
45
|
+
* `interpret.ts`).
|
|
46
|
+
* 5. **Decorators that rewrite signatures** — `@dataclass`,
|
|
47
|
+
* `@property`, `@classmethod`, `@staticmethod` are recognized
|
|
48
|
+
* by `receiver-binding.ts`. Arbitrary decorators (e.g.
|
|
49
|
+
* `functools.wraps`, custom retry wrappers) preserve the wrapped
|
|
50
|
+
* function's declared signature; a decorator that returns a
|
|
51
|
+
* different callable is followed only through the declared
|
|
52
|
+
* return type.
|
|
53
|
+
* 6. **`typing.TYPE_CHECKING`-guarded imports** — treated like any
|
|
54
|
+
* other `import` for reference resolution. We do not distinguish
|
|
55
|
+
* runtime-visible from type-checker-only imports; this is
|
|
56
|
+
* intentional (type-only imports are still structurally valid
|
|
57
|
+
* type references).
|
|
58
|
+
* 7. **`*args` / `**kwargs` type flow-through** — `arity.ts`
|
|
59
|
+
* accepts any call count when a variadic is present, but no
|
|
60
|
+
* type information flows through the variadic into the callee
|
|
61
|
+
* body. Receiver-binding still works for explicit parameters.
|
|
62
|
+
* 8. **`super()` outside a method with a literal class binding** —
|
|
63
|
+
* resolved for the standard `class Child(Parent): def m(self):
|
|
64
|
+
* super().m()` pattern. Zero-arg `super()` inside a nested
|
|
65
|
+
* function, a `functools.wraps`-rewrapped method, or a call
|
|
66
|
+
* site where the enclosing class can't be statically determined
|
|
67
|
+
* is left unresolved.
|
|
68
|
+
*
|
|
69
|
+
* Shadow-harness corpus parity is the authoritative signal for which
|
|
70
|
+
* of these matter in practice. The CI parity gate blocks any PR that
|
|
71
|
+
* regresses either the legacy or registry-primary run of
|
|
72
|
+
* `test/integration/resolvers/python.test.ts`.
|
|
73
|
+
*/
|
|
74
|
+
export { emitPythonScopeCaptures } from './captures.js';
|
|
75
|
+
export { getPythonCaptureCacheStats, resetPythonCaptureCacheStats } from './cache-stats.js';
|
|
76
|
+
export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.js';
|
|
77
|
+
export { pythonMergeBindings } from './merge-bindings.js';
|
|
78
|
+
export { pythonArityCompatibility } from './arity.js';
|
|
79
|
+
export { resolvePythonImportTarget } from './import-target.js';
|
|
80
|
+
export { pythonBindingScopeFor, pythonImportOwningScope, pythonReceiverBinding, } from './simple-hooks.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-match → semantic-shape interpreters.
|
|
3
|
+
*
|
|
4
|
+
* Two pure functions, both consumed by the central scope extractor:
|
|
5
|
+
*
|
|
6
|
+
* - `interpretPythonImport` → `ParsedImport`
|
|
7
|
+
* - `interpretPythonTypeBinding` → `ParsedTypeBinding`
|
|
8
|
+
*
|
|
9
|
+
* The matches arrive pre-decomposed by `emitPythonScopeCaptures`
|
|
10
|
+
* (one imported name per match; synthesized `self`/`cls` markers
|
|
11
|
+
* already attached) so these functions are straight-line tag readers.
|
|
12
|
+
*/
|
|
13
|
+
import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
|
|
14
|
+
export declare function interpretPythonImport(captures: CaptureMatch): ParsedImport | null;
|
|
15
|
+
export declare function interpretPythonTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-match → semantic-shape interpreters.
|
|
3
|
+
*
|
|
4
|
+
* Two pure functions, both consumed by the central scope extractor:
|
|
5
|
+
*
|
|
6
|
+
* - `interpretPythonImport` → `ParsedImport`
|
|
7
|
+
* - `interpretPythonTypeBinding` → `ParsedTypeBinding`
|
|
8
|
+
*
|
|
9
|
+
* The matches arrive pre-decomposed by `emitPythonScopeCaptures`
|
|
10
|
+
* (one imported name per match; synthesized `self`/`cls` markers
|
|
11
|
+
* already attached) so these functions are straight-line tag readers.
|
|
12
|
+
*/
|
|
13
|
+
// ─── interpretImport ──────────────────────────────────────────────────────
|
|
14
|
+
export function interpretPythonImport(captures) {
|
|
15
|
+
// Markers attached by `splitImportStatement` (import-decomposer.ts):
|
|
16
|
+
// `@import.kind` : 'plain' | 'aliased' | 'from' | 'from-alias' | 'wildcard' | 'dynamic'
|
|
17
|
+
// `@import.name` : the imported symbol name (or module name for plain imports)
|
|
18
|
+
// `@import.alias` : the local alias name (for `as` forms)
|
|
19
|
+
// `@import.source`: the module path (always present except for `dynamic`)
|
|
20
|
+
const kindCap = captures['@import.kind'];
|
|
21
|
+
const nameCap = captures['@import.name'];
|
|
22
|
+
const aliasCap = captures['@import.alias'];
|
|
23
|
+
const sourceCap = captures['@import.source'];
|
|
24
|
+
const kind = kindCap?.text;
|
|
25
|
+
if (kind === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
switch (kind) {
|
|
28
|
+
case 'plain': {
|
|
29
|
+
// `import numpy`
|
|
30
|
+
if (sourceCap === undefined)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
kind: 'namespace',
|
|
34
|
+
localName: sourceCap.text.split('.')[0], // `import a.b.c` exposes `a`
|
|
35
|
+
importedName: sourceCap.text,
|
|
36
|
+
targetRaw: sourceCap.text,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
case 'aliased': {
|
|
40
|
+
// `import numpy as np`
|
|
41
|
+
if (sourceCap === undefined || aliasCap === undefined)
|
|
42
|
+
return null;
|
|
43
|
+
return {
|
|
44
|
+
kind: 'namespace',
|
|
45
|
+
localName: aliasCap.text,
|
|
46
|
+
importedName: sourceCap.text,
|
|
47
|
+
targetRaw: sourceCap.text,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
case 'from': {
|
|
51
|
+
// `from m import x`
|
|
52
|
+
if (sourceCap === undefined || nameCap === undefined)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
kind: 'named',
|
|
56
|
+
localName: nameCap.text,
|
|
57
|
+
importedName: nameCap.text,
|
|
58
|
+
targetRaw: sourceCap.text,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
case 'from-alias': {
|
|
62
|
+
// `from m import x as y`
|
|
63
|
+
if (sourceCap === undefined || nameCap === undefined || aliasCap === undefined)
|
|
64
|
+
return null;
|
|
65
|
+
return {
|
|
66
|
+
kind: 'alias',
|
|
67
|
+
localName: aliasCap.text,
|
|
68
|
+
importedName: nameCap.text,
|
|
69
|
+
alias: aliasCap.text,
|
|
70
|
+
targetRaw: sourceCap.text,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
case 'wildcard': {
|
|
74
|
+
// `from m import *`
|
|
75
|
+
if (sourceCap === undefined)
|
|
76
|
+
return null;
|
|
77
|
+
return { kind: 'wildcard', targetRaw: sourceCap.text };
|
|
78
|
+
}
|
|
79
|
+
case 'dynamic': {
|
|
80
|
+
// `importlib.import_module(...)` — preserved for diagnostics.
|
|
81
|
+
return {
|
|
82
|
+
kind: 'dynamic-unresolved',
|
|
83
|
+
localName: '',
|
|
84
|
+
targetRaw: sourceCap?.text ?? null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ─── interpretTypeBinding ─────────────────────────────────────────────────
|
|
92
|
+
export function interpretPythonTypeBinding(captures) {
|
|
93
|
+
// Synthesized `self` / `cls` captures carry `@type-binding.name` and
|
|
94
|
+
// `@type-binding.type` directly — same shape as parameter annotations,
|
|
95
|
+
// source differs.
|
|
96
|
+
const nameCap = captures['@type-binding.name'];
|
|
97
|
+
const typeCap = captures['@type-binding.type'];
|
|
98
|
+
if (nameCap === undefined || typeCap === undefined)
|
|
99
|
+
return null;
|
|
100
|
+
// Strip surrounding quotes for PEP 484 forward references:
|
|
101
|
+
// `def f(x: "User")`. Then unwrap nullable unions — `User | None`,
|
|
102
|
+
// `None | User`, `Optional[User]` — to the concrete class name so
|
|
103
|
+
// receiver-typed resolution treats nullable receivers identically to
|
|
104
|
+
// non-nullable ones. Finally strip single-arg generic wrappers so
|
|
105
|
+
// `list[User]` / `Iterable[User]` behave like `User` for iterable
|
|
106
|
+
// for-loop chain propagation.
|
|
107
|
+
const rawType = stripGeneric(stripNullable(stripForwardRefQuotes(typeCap.text.trim())));
|
|
108
|
+
// Order matters: more specific anchor captures take precedence. `self`
|
|
109
|
+
// and `cls` are synthesized with their own marker captures; the SCM
|
|
110
|
+
// anchor topic captures (`@type-binding.parameter`,
|
|
111
|
+
// `@type-binding.annotation`, `@type-binding.constructor`) distinguish
|
|
112
|
+
// the variable-annotation and constructor-inferred forms from the
|
|
113
|
+
// classic parameter annotation.
|
|
114
|
+
let source = 'parameter-annotation';
|
|
115
|
+
if (captures['@type-binding.self'] !== undefined)
|
|
116
|
+
source = 'self';
|
|
117
|
+
// `cls` is a self-like receiver; share the source label so downstream
|
|
118
|
+
// `Registry.lookup` Step 2 treats them identically.
|
|
119
|
+
else if (captures['@type-binding.cls'] !== undefined)
|
|
120
|
+
source = 'self';
|
|
121
|
+
else if (captures['@type-binding.constructor'] !== undefined)
|
|
122
|
+
source = 'constructor-inferred';
|
|
123
|
+
else if (captures['@type-binding.annotation'] !== undefined)
|
|
124
|
+
source = 'annotation';
|
|
125
|
+
else if (captures['@type-binding.alias'] !== undefined)
|
|
126
|
+
source = 'assignment-inferred';
|
|
127
|
+
else if (captures['@type-binding.return'] !== undefined)
|
|
128
|
+
source = 'return-annotation';
|
|
129
|
+
return { boundName: nameCap.text, rawTypeName: rawType, source };
|
|
130
|
+
}
|
|
131
|
+
function stripForwardRefQuotes(text) {
|
|
132
|
+
if ((text.startsWith('"') && text.endsWith('"')) ||
|
|
133
|
+
(text.startsWith("'") && text.endsWith("'"))) {
|
|
134
|
+
return text.slice(1, -1);
|
|
135
|
+
}
|
|
136
|
+
return text;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Unwrap a single-arg generic collection wrapper — `list[User]`,
|
|
140
|
+
* `set[User]`, `Iterable[User]`, `Sequence[User]`, `Iterator[User]`,
|
|
141
|
+
* `Generator[User, ...]` — to its element type.
|
|
142
|
+
*
|
|
143
|
+
* Point: for-loop and cross-file chain propagation need the element
|
|
144
|
+
* type, not the container. Multi-arg generics (`dict[str, User]`,
|
|
145
|
+
* `Callable[[int], User]`) are left alone — the element semantics
|
|
146
|
+
* aren't unambiguous and the scope-chain fallback handles them at
|
|
147
|
+
* resolution time.
|
|
148
|
+
*/
|
|
149
|
+
function stripGeneric(text) {
|
|
150
|
+
const single = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?(?:list|List|set|Set|tuple|Tuple|Iterable|Iterator|Sequence|Generator|AsyncIterable|AsyncIterator)\[([^,\]]+)\]$/);
|
|
151
|
+
if (single !== null)
|
|
152
|
+
return single[1].trim();
|
|
153
|
+
// dict[K, V] / Dict[K, V] / Mapping[K, V] — strip to value type V.
|
|
154
|
+
// For-loop destructuring of `for k, v in d.items()` binds `v` to
|
|
155
|
+
// `d`; the chain-follow then unwraps the dict annotation to V.
|
|
156
|
+
// Single-key dict `dict[K]` is not legal Python, so two args is the
|
|
157
|
+
// only shape worth handling. Match a top-level K up to the first
|
|
158
|
+
// comma and a V to the closing bracket; nested generics in V (e.g.
|
|
159
|
+
// `dict[str, list[User]]`) are left for a downstream strip pass.
|
|
160
|
+
const dict = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?(?:dict|Dict|Mapping|MutableMapping|OrderedDict|DefaultDict)\[[^,\]]+,\s*([^\]]+)\]$/);
|
|
161
|
+
if (dict !== null)
|
|
162
|
+
return dict[1].trim();
|
|
163
|
+
return text;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Unwrap nullable type annotations so downstream resolution treats
|
|
167
|
+
* `User | None`, `None | User`, and `Optional[User]` identically to
|
|
168
|
+
* `User`. A missing/unknown variant returns the input unchanged.
|
|
169
|
+
*
|
|
170
|
+
* This is a syntactic strip, not a semantic parse — it handles the
|
|
171
|
+
* canonical PEP-604 and `typing.Optional` shapes that cover the
|
|
172
|
+
* overwhelming majority of real-world Python annotations and punts on
|
|
173
|
+
* exotic unions (e.g. `User | Error`, which is ambiguous and should not
|
|
174
|
+
* auto-bind to one arm).
|
|
175
|
+
*/
|
|
176
|
+
function stripNullable(text) {
|
|
177
|
+
// `Optional[X]` / `typing.Optional[X]` / `t.Optional[X]`
|
|
178
|
+
const optMatch = text.match(/^(?:[A-Za-z_][A-Za-z0-9_]*\.)?Optional\[(.+)\]$/);
|
|
179
|
+
if (optMatch !== null)
|
|
180
|
+
return optMatch[1].trim();
|
|
181
|
+
// Binary union forms. A three-arm or larger union (`User | None | Error`)
|
|
182
|
+
// is ambiguous for single-receiver inference, so we leave it alone.
|
|
183
|
+
const parts = text.split('|').map((p) => p.trim());
|
|
184
|
+
if (parts.length !== 2)
|
|
185
|
+
return text;
|
|
186
|
+
if (parts[0] === 'None')
|
|
187
|
+
return parts[1];
|
|
188
|
+
if (parts[1] === 'None')
|
|
189
|
+
return parts[0];
|
|
190
|
+
return text;
|
|
191
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python LEGB precedence merge for the `mergeBindings` hook.
|
|
3
|
+
*
|
|
4
|
+
* Tier ranking (lower wins in shadowing):
|
|
5
|
+
*
|
|
6
|
+
* - 0: `local` — an `x = …` or `def x` or `class x` in this scope
|
|
7
|
+
* - 1: `import` / `namespace` / `reexport` — `from m import x`,
|
|
8
|
+
* `import m`, public re-exports
|
|
9
|
+
* - 2: `wildcard` — `from m import *`
|
|
10
|
+
*
|
|
11
|
+
* Within a surviving tier we de-dup by `DefId`, last-write-wins (Python
|
|
12
|
+
* semantics: a later assignment replaces an earlier one for lookup
|
|
13
|
+
* purposes).
|
|
14
|
+
*/
|
|
15
|
+
import type { BindingRef } from '../../../../_shared/index.js';
|
|
16
|
+
export declare function pythonMergeBindings(bindings: readonly BindingRef[]): readonly BindingRef[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python LEGB precedence merge for the `mergeBindings` hook.
|
|
3
|
+
*
|
|
4
|
+
* Tier ranking (lower wins in shadowing):
|
|
5
|
+
*
|
|
6
|
+
* - 0: `local` — an `x = …` or `def x` or `class x` in this scope
|
|
7
|
+
* - 1: `import` / `namespace` / `reexport` — `from m import x`,
|
|
8
|
+
* `import m`, public re-exports
|
|
9
|
+
* - 2: `wildcard` — `from m import *`
|
|
10
|
+
*
|
|
11
|
+
* Within a surviving tier we de-dup by `DefId`, last-write-wins (Python
|
|
12
|
+
* semantics: a later assignment replaces an earlier one for lookup
|
|
13
|
+
* purposes).
|
|
14
|
+
*/
|
|
15
|
+
const TIER_LOCAL = 0;
|
|
16
|
+
const TIER_IMPORT = 1;
|
|
17
|
+
const TIER_WILDCARD = 2;
|
|
18
|
+
const TIER_UNKNOWN = 3;
|
|
19
|
+
function tierOf(b) {
|
|
20
|
+
switch (b.origin) {
|
|
21
|
+
case 'local':
|
|
22
|
+
return TIER_LOCAL;
|
|
23
|
+
case 'reexport':
|
|
24
|
+
case 'import':
|
|
25
|
+
case 'namespace':
|
|
26
|
+
return TIER_IMPORT;
|
|
27
|
+
case 'wildcard':
|
|
28
|
+
return TIER_WILDCARD;
|
|
29
|
+
default:
|
|
30
|
+
return TIER_UNKNOWN;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function pythonMergeBindings(bindings) {
|
|
34
|
+
if (bindings.length === 0)
|
|
35
|
+
return bindings;
|
|
36
|
+
let bestTier = Number.POSITIVE_INFINITY;
|
|
37
|
+
for (const b of bindings)
|
|
38
|
+
bestTier = Math.min(bestTier, tierOf(b));
|
|
39
|
+
const survivors = bindings.filter((b) => tierOf(b) === bestTier);
|
|
40
|
+
const seen = new Map();
|
|
41
|
+
for (const b of survivors)
|
|
42
|
+
seen.set(b.def.nodeId, b);
|
|
43
|
+
return [...seen.values()];
|
|
44
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter query for Python scope captures (RFC §5.1).
|
|
3
|
+
*
|
|
4
|
+
* Exposes lazy `Parser` and `Query` singletons so callers don't
|
|
5
|
+
* pay tree-sitter init cost per file.
|
|
6
|
+
*/
|
|
7
|
+
import Parser from 'tree-sitter';
|
|
8
|
+
export declare function getPythonParser(): Parser;
|
|
9
|
+
export declare function getPythonScopeQuery(): Parser.Query;
|