mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-24 00:51:53 +00:00
chore(scope-resolution): drop unused python/scopes.scm sibling
The file was documentation-only — the authoritative scope query is the embedded `PYTHON_SCOPE_QUERY` constant in `python/query.ts`. Nothing loaded the `.scm` at runtime, so it drifted from the code. Remove it and update the four doc comments that pointed at it: - language-provider.ts: "scopes.scm query" → "scope query (embedded in each language's query.ts)". - languages/python.ts: capture-vocabulary pointer → query.ts. - python/query.ts header: drop the "edit both together" note. - python/receiver-binding.ts: "keeps the .scm declarative" → "keeps the embedded scope query declarative". - scope/walkers.ts: "Python's scopes.scm" → "Python's scope query". Historical plan docs under docs/plans/ still reference scopes.scm but are frozen artifacts, not living documentation. C# never had a .scm sibling, so no action needed there.
This commit is contained in:
parent
8f5f7cf228
commit
a58a478c06
6 changed files with 9 additions and 279 deletions
|
|
@ -303,8 +303,9 @@ interface LanguageProviderConfig {
|
|||
|
||||
/**
|
||||
* Emit scope captures from raw source, **pre-grouped per tree-sitter
|
||||
* query match**. Tree-sitter-based providers run a `scopes.scm` query
|
||||
* and emit one `CaptureMatch` per query match; standalone providers
|
||||
* query match**. Tree-sitter-based providers run a scope query
|
||||
* (embedded as a string constant in each language's `query.ts`) and
|
||||
* emit one `CaptureMatch` per query match; standalone providers
|
||||
* (COBOL) emit matches from a regex tagger. The return shape is
|
||||
* parser-agnostic: the central `ScopeExtractor` consumes
|
||||
* `CaptureMatch[]` without knowing which parser produced them.
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export const pythonProvider = defineLanguage({
|
|||
// ── RFC #909 Ring 3: scope-based resolution hooks (RFC §5) ──────────
|
||||
// Python is the first migration. See ./python/index.ts for the
|
||||
// full per-hook rationale and the canonical capture vocabulary in
|
||||
// ./python/scopes.scm.
|
||||
// ./python/query.ts (PYTHON_SCOPE_QUERY constant).
|
||||
emitScopeCaptures: emitPythonScopeCaptures,
|
||||
interpretImport: interpretPythonImport,
|
||||
interpretTypeBinding: interpretPythonTypeBinding,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
/**
|
||||
* Tree-sitter query for Python scope captures (RFC §5.1).
|
||||
*
|
||||
* The `.scm` sibling file is the human-readable spec; this module
|
||||
* mirrors it at runtime. **Edit both together** — the unit/integration
|
||||
* tests reference the embedded constant, and the file documents the
|
||||
* contract.
|
||||
*
|
||||
* Also exposes lazy `Parser` and `Query` singletons so callers don't
|
||||
* Exposes lazy `Parser` and `Query` singletons so callers don't
|
||||
* pay tree-sitter init cost per file.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
*
|
||||
* Tree-sitter can't easily express "the first parameter of a function
|
||||
* defined directly inside a class body" via a single static query.
|
||||
* Doing this in code keeps the `.scm` file declarative and lets us
|
||||
* encode the `@classmethod` / `@staticmethod` decorator awareness that
|
||||
* Python's runtime depends on.
|
||||
* Doing this in code keeps the embedded scope query declarative and
|
||||
* lets us encode the `@classmethod` / `@staticmethod` decorator
|
||||
* awareness that Python's runtime depends on.
|
||||
*/
|
||||
|
||||
import type { CaptureMatch } from 'gitnexus-shared';
|
||||
|
|
|
|||
|
|
@ -1,266 +0,0 @@
|
|||
; Tree-sitter Python query — RFC §5.1 captures for scope-based resolution
|
||||
; (RFC #909 Ring 3, language: Python — first migration).
|
||||
;
|
||||
; Capture vocabulary consumed by the central `ScopeExtractor`:
|
||||
;
|
||||
; @scope.module — file root
|
||||
; @scope.class — class body
|
||||
; @scope.function — def / async def body (functions, methods, lambdas)
|
||||
;
|
||||
; @declaration.class + @declaration.name
|
||||
; @declaration.function + @declaration.name
|
||||
; @declaration.method + @declaration.name (functions inside class bodies)
|
||||
; @declaration.variable + @declaration.name (module/class/function-level assignments)
|
||||
;
|
||||
; @import.statement — anchor for `interpretImport`. The hook reads
|
||||
; the captured source text and tokenizes it into
|
||||
; a `ParsedImport`. We do NOT decompose
|
||||
; `import X, Y` here at query time — `interpretImport`
|
||||
; splits multi-target statements into N matches.
|
||||
;
|
||||
; @type-binding.parameter + @type-binding.name + @type-binding.type
|
||||
;
|
||||
; @reference.call.free + @reference.name (e.g. `print(x)`)
|
||||
; @reference.call.member + @reference.name + @reference.receiver
|
||||
; (e.g. `obj.save()`)
|
||||
;
|
||||
; Python has NO block scope: `if`, `for`, `while`, `try`, `with`, `match`
|
||||
; bodies do NOT introduce a new lexical scope (PEP 8 / language reference).
|
||||
; We therefore do NOT emit `@scope.block` captures for those constructs;
|
||||
; their contained declarations land in the enclosing function/class/module
|
||||
; scope automatically (RFC §5.1 "transparent block" behavior).
|
||||
;
|
||||
; `@reference.call.constructor` is intentionally absent: Python has no
|
||||
; `new` keyword. A call to a class is syntactically identical to a call to
|
||||
; a free function; the registry decides constructor-vs-call by inspecting
|
||||
; the resolved `def.type`. Stays out of the parser to avoid duplicating
|
||||
; that logic in tree-sitter.
|
||||
|
||||
; ─── Scopes ────────────────────────────────────────────────────────────────
|
||||
|
||||
(module) @scope.module
|
||||
|
||||
(class_definition) @scope.class
|
||||
|
||||
(function_definition) @scope.function
|
||||
|
||||
; ─── Declarations: class / function ────────────────────────────────────────
|
||||
|
||||
(class_definition
|
||||
name: (identifier) @declaration.name) @declaration.class
|
||||
|
||||
(function_definition
|
||||
name: (identifier) @declaration.name) @declaration.function
|
||||
|
||||
; ─── Declarations: assignments (module-, class-, function-level variables)
|
||||
;
|
||||
; Note: tree-sitter-python parses both annotated and plain assignments as
|
||||
; `(assignment left: ...)` — typed and untyped both surface here. The
|
||||
; central extractor de-dupes by `nodeId` (file#line:col:type:name).
|
||||
|
||||
(assignment
|
||||
left: (identifier) @declaration.name) @declaration.variable
|
||||
|
||||
; for-loop target — Python `for` does NOT introduce a new scope; the
|
||||
; loop variable binds in the enclosing function/module scope.
|
||||
(for_statement
|
||||
left: (identifier) @declaration.name) @declaration.variable
|
||||
|
||||
; ─── Imports ───────────────────────────────────────────────────────────────
|
||||
;
|
||||
; The whole statement is the anchor — `interpretImport` decomposes it.
|
||||
; We tag both shapes so the hook sees a single capture name (`@import.statement`).
|
||||
|
||||
(import_statement) @import.statement
|
||||
|
||||
(import_from_statement) @import.statement
|
||||
|
||||
; ─── Type bindings: parameter annotations ──────────────────────────────────
|
||||
;
|
||||
; `def f(x: User)` — `x` is bound to `User` in `f`'s scope.
|
||||
;
|
||||
; `interpretTypeBinding` reads `@type-binding.name` (the parameter name)
|
||||
; and `@type-binding.type` (the annotation source text) to produce a
|
||||
; `ParsedTypeBinding { boundName, rawTypeName, source: 'parameter-annotation' }`.
|
||||
|
||||
(typed_parameter
|
||||
(identifier) @type-binding.name
|
||||
type: (type) @type-binding.type) @type-binding.parameter
|
||||
|
||||
(typed_default_parameter
|
||||
name: (identifier) @type-binding.name
|
||||
type: (type) @type-binding.type) @type-binding.parameter
|
||||
|
||||
; ─── Type bindings: constructor-inferred assignments ───────────────────────
|
||||
;
|
||||
; `u = User("alice")` — `u`'s type is inferred from the RHS call's target.
|
||||
; Python has no `new` keyword, so the pattern matches any `assignment`
|
||||
; whose RHS is a `call` with a bare-identifier function (constructor-
|
||||
; shaped). The registry resolves the raw name through the scope chain at
|
||||
; lookup time, so imported classes, local classes, and aliased imports
|
||||
; all work without query-time knowledge.
|
||||
;
|
||||
; Emits `source: 'constructor-inferred'`.
|
||||
;
|
||||
; Listed BEFORE the annotation pattern so `u: User = find()` — which
|
||||
; matches both patterns — has the annotation (later-processed match)
|
||||
; overwrite the constructor-inferred guess. Explicit user intent wins.
|
||||
|
||||
(assignment
|
||||
left: (identifier) @type-binding.name
|
||||
right: (call
|
||||
function: (identifier) @type-binding.type)) @type-binding.constructor
|
||||
|
||||
; ─── Type bindings: variable annotations ───────────────────────────────────
|
||||
;
|
||||
; `u: User` or `u: User = some_value` — `u` is explicitly annotated. Both
|
||||
; forms parse under tree-sitter-python as `(assignment left: type:)` with
|
||||
; an optional `right:`. Module-, class-, and function-scope annotations
|
||||
; all land here; scope attachment is handled by the central extractor
|
||||
; via the anchor's innermost-containing scope.
|
||||
;
|
||||
; Emits `source: 'annotation'`.
|
||||
|
||||
(assignment
|
||||
left: (identifier) @type-binding.name
|
||||
type: (type) @type-binding.type) @type-binding.annotation
|
||||
|
||||
; For-loop iterable of a free-call result: `for u in get_users()` —
|
||||
; binds `u → get_users` so the chain post-pass follows it through
|
||||
; `get_users`'s return-type annotation (cross-file via
|
||||
; `propagateImportedReturnTypes`).
|
||||
(for_statement
|
||||
left: (identifier) @type-binding.name
|
||||
right: (call
|
||||
function: (identifier) @type-binding.type)) @type-binding.alias
|
||||
|
||||
; for (i, u) in enumerate(X) and for i, u in enumerate(X) — bind the
|
||||
; second tuple element to X. enumerate yields (int, X-element); the
|
||||
; chain-follow unwraps X via generic-strip when X is an annotated
|
||||
; collection.
|
||||
(for_statement
|
||||
left: (tuple_pattern
|
||||
(identifier)
|
||||
(identifier) @type-binding.name)
|
||||
right: (call
|
||||
function: (identifier) @_enum
|
||||
arguments: (argument_list
|
||||
(identifier) @type-binding.type))
|
||||
(#eq? @_enum "enumerate")) @type-binding.alias
|
||||
|
||||
(for_statement
|
||||
left: (pattern_list
|
||||
(identifier)
|
||||
(identifier) @type-binding.name)
|
||||
right: (call
|
||||
function: (identifier) @_enum
|
||||
arguments: (argument_list
|
||||
(identifier) @type-binding.type))
|
||||
(#eq? @_enum "enumerate")) @type-binding.alias
|
||||
|
||||
; for k, v in d.items() — bind v to d. The chain-follow unwraps d's
|
||||
; dict[K, V] annotation to V via the dict-aware stripGeneric.
|
||||
(for_statement
|
||||
left: (pattern_list
|
||||
(identifier)
|
||||
(identifier) @type-binding.name)
|
||||
right: (call
|
||||
function: (attribute
|
||||
object: (identifier) @type-binding.type
|
||||
attribute: (identifier) @_items))
|
||||
(#eq? @_items "items")) @type-binding.alias
|
||||
|
||||
(for_statement
|
||||
left: (tuple_pattern
|
||||
(identifier)
|
||||
(identifier) @type-binding.name)
|
||||
right: (call
|
||||
function: (attribute
|
||||
object: (identifier) @type-binding.type
|
||||
attribute: (identifier) @_items))
|
||||
(#eq? @_items "items")) @type-binding.alias
|
||||
|
||||
; for i, (k, v) in enumerate(d.items()) — nested tuple destructuring.
|
||||
(for_statement
|
||||
left: (pattern_list
|
||||
(identifier)
|
||||
(tuple_pattern
|
||||
(identifier)
|
||||
(identifier) @type-binding.name))
|
||||
right: (call
|
||||
function: (identifier) @_enum
|
||||
arguments: (argument_list
|
||||
(call
|
||||
function: (attribute
|
||||
object: (identifier) @type-binding.type
|
||||
attribute: (identifier) @_items))))
|
||||
(#eq? @_enum "enumerate")
|
||||
(#eq? @_items "items")) @type-binding.alias
|
||||
|
||||
; for i, k, v in enumerate(d.items()) — 3-var flat destructuring.
|
||||
(for_statement
|
||||
left: (pattern_list
|
||||
(identifier)
|
||||
(identifier)
|
||||
(identifier) @type-binding.name)
|
||||
right: (call
|
||||
function: (identifier) @_enum
|
||||
arguments: (argument_list
|
||||
(call
|
||||
function: (attribute
|
||||
object: (identifier) @type-binding.type
|
||||
attribute: (identifier) @_items))))
|
||||
(#eq? @_enum "enumerate")
|
||||
(#eq? @_items "items")) @type-binding.alias
|
||||
|
||||
; for u in self.X — heuristic: bind u to X (the attribute name).
|
||||
; The chain-follow resolves X via the enclosing method's parameter
|
||||
; typeBinding. Supports fixtures that reference `self.X` as a stand-in
|
||||
; for a parameter X (matches legacy DAG fallback behavior).
|
||||
(for_statement
|
||||
left: (identifier) @type-binding.name
|
||||
right: (attribute
|
||||
object: (identifier) @_self
|
||||
attribute: (identifier) @type-binding.type)
|
||||
(#eq? @_self "self")) @type-binding.alias
|
||||
|
||||
; for v in d.values() — bind v to d (dict-strip yields value type).
|
||||
(for_statement
|
||||
left: (identifier) @type-binding.name
|
||||
right: (call
|
||||
function: (attribute
|
||||
object: (identifier) @type-binding.type
|
||||
attribute: (identifier) @_values))
|
||||
(#eq? @_values "values")) @type-binding.alias
|
||||
|
||||
; ─── Type bindings: function return-type annotations ─────────────────────
|
||||
;
|
||||
; `def get_user() -> User:` — binds the function's NAME to its return
|
||||
; type in the enclosing scope. Combined with the constructor-inferred +
|
||||
; chain-follow path, `u = get_user()` then resolves `u: User` cross-
|
||||
; call. Python provider hoists the binding via `pythonBindingScopeFor`
|
||||
; to the function's parent scope so callers in module/class scope see it.
|
||||
|
||||
(function_definition
|
||||
name: (identifier) @type-binding.name
|
||||
return_type: (type) @type-binding.type) @type-binding.return
|
||||
|
||||
; ─── References: calls ─────────────────────────────────────────────────────
|
||||
;
|
||||
; Free call: `print(x)` — function is a bare identifier
|
||||
; Member call: `obj.save()` — function is an attribute access
|
||||
|
||||
(call
|
||||
function: (identifier) @reference.name) @reference.call.free
|
||||
|
||||
(call
|
||||
function: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.call.member
|
||||
|
||||
; Attribute write: `obj.name = "x"` — emits ACCESSES (write) edge from
|
||||
; the enclosing function to the field on obj's class.
|
||||
(assignment
|
||||
left: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.write.member
|
||||
|
|
@ -159,7 +159,7 @@ export function populateClassOwnedMembers(parsed: ParsedFile): void {
|
|||
// when the def sits inside a class. Without this, two classes in the
|
||||
// same file that share a method name collide at the graph-bridge lookup
|
||||
// (`node-lookup.ts` keys by (filePath, qualifiedName) and falls back to
|
||||
// simple name only). Python's `scopes.scm` doesn't emit
|
||||
// simple name only). Python's scope query doesn't emit
|
||||
// `@declaration.qualified_name` for nested methods, so the finalized
|
||||
// defs arrive here with simple names — we stamp the qualifier while
|
||||
// we're already walking class scopes for ownerId.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue