Fix /doc placing Python docstrings before decorators#318571
Merged
Merged
Conversation
In Python's tree-sitter grammar, a `decorated_definition` node wraps `function_definition` / `class_definition`. The previous fall-through to the default regex `/definition|declaration|declarator/` matched the outer `decorated_definition`, so the documentable node's range started at the `@decorator` line. Downstream consumers (LLM prompt context, docstring insertion anchor) then placed docstrings *above* the a Pylance error and broken code.decorator Match only the inner `function_definition` / `class_definition` for Python. Add unit tests covering plain/decorated functions, classes, and methods (including stacked decorators and decorated classes). Fixes #283165 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes Python /doc docstring placement when functions/classes are decorated by ensuring the parser selects the inner function_definition/class_definition node rather than the wrapping decorated_definition.
Changes:
- Add a Python-specific
isDocumentableNoderule to match onlyfunction_definitionandclass_definition. - Add a new Python-focused
getNodeToDocumenttest suite covering decorated functions/classes/methods and the #283165 repro.
Show a summary per file
| File | Description |
|---|---|
| extensions/copilot/src/platform/parser/node/util.ts | Adds Python-specific documentable-node matching to avoid selecting decorated_definition. |
| extensions/copilot/src/platform/parser/test/node/getNodeToDocument.py.spec.ts | Adds regression and coverage tests for Python decorated definitions and selections. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 2
Address review feedback on #283165: - Match `decorated_definition` in `isDocumentableNode` for Python so cursors/selections on the `@decorator` line no longer escape past it to the `module` root. - Introduce `unwrapPythonDecoratedDefinition` and call it in both code paths of `_getNodeToDocument` so the returned range is always the inner `function_definition`/`class_ excluding the decorator definition` regardless of whether the node was found via selection match or via walk-up. - Fix misleading test title for whole-method decorated selection (now picks the inner function_definition as advertised). - Add 4 regression tests for cursor-on-decorator (incl. on `@` sign, selection of decorator-only, decorator on method inside class). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
roblourens
previously approved these changes
May 27, 2026
…r too Defensive consistency fix from subagent code review. While the Python tree-sitter grammar makes `function_definition`/`class_definition` (not `decorated_definition`) the direct parent of name so thisidentifiers branch can't trigger applying the unwrap maintains thetoday invariant: "the parser API never exposes a Python `decorated_definition` externally". This protects against future grammar changes or callers that pass ranges over decorator children. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dbaeumer
approved these changes
May 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #283165.
Problem
Invoking
/doc(or any docstring-generation flow that relies on the tree-sitter "node to document" identification) on a Python function/class with a decorator placed the docstring before the@decoratorline instead of inside the function body. Pylance rightly flagged this as an error, and any users relying on the docstring being inside the function got broken code.Repro from the issue:
Select the whole file →
/doc→ docstring lands above@pytest.fixture(...).Root cause
In
extensions/copilot/src/platform/parser/node/util.ts,isDocumentableNodehad no Python-specific case and fell through to the default regex/definition|declaration|declarator/.In Python's tree-sitter grammar, a
decorated_definitionnode wraps the innerfunction_definition/class_definition:Both
decorated_definitionandfunction_definitionmatch/definition/. When the user's selection covered the whole decorated unit, the weighting in_getNodeMatchingSelectionpreferred the outerdecorated_definition— whose range starts at the@…line — so downstream consumers (LLM prompt context, docstring insertion anchor) treated the decorator line as the start of the function.Fix
Add a Python case to
isDocumentableNodethat matches onlyfunction_definitionandclass_definition— not the wrappingdecorated_definition. Decorators are metadata, and the documentable/symbol-bearing unit is the function/class itself._getNodeToDocumentis also used by rename suggestions, the chat panel definition/references resolvers, test generation from source, and symbol-at-cursor context. All of these benefit from the parser identifying the inner declaration.Tests
New
extensions/copilot/src/platform/parser/test/node/getNodeToDocument.py.spec.tswith 14 tests:def/class, in body).def, in body, whole-file selection.@first/@second(arg=1)/@third).@dataclass).@staticmethod).@classmethod+@cached).Verification
tsc --noEmitclean.src/extension/{prompt,context,intents}sweep: 982 passed, 0 failed.