Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions extensions/copilot/src/platform/parser/node/docGenParsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Node, TreeSitterOffsetRange } from './nodes';
import { _parse } from './parserWithCaching';
import { _getNodeMatchingSelection } from './selectionParsing';
import { WASMLanguage } from './treeSitterLanguages';
import { extractIdentifier, isDocumentableNode } from './util';
import { extractIdentifier, isDocumentableNode, unwrapPythonDecoratedDefinition } from './util';


export type NodeToDocumentContext = {
Expand Down Expand Up @@ -53,10 +53,11 @@ export async function _getNodeToDocument(
const selectionMatchedNode = isSelectionEmpty ? undefined : _getNodeMatchingSelection(treeRef.tree, selection, language);

if (selectionMatchedNode) {
const nodeIdentifier = extractIdentifier(selectionMatchedNode, language);
const unwrapped = unwrapPythonDecoratedDefinition(selectionMatchedNode, language);
const nodeIdentifier = extractIdentifier(unwrapped, language);
return {
nodeIdentifier,
nodeToDocument: Node.ofSyntaxNode(selectionMatchedNode),
nodeToDocument: Node.ofSyntaxNode(unwrapped),
nodeSelectionBy: 'matchingSelection'
};
}
Expand All @@ -72,6 +73,8 @@ export async function _getNodeToDocument(
++nNodesClimbedUp;
}

nodeToDocument = unwrapPythonDecoratedDefinition(nodeToDocument, language);

const nodeIdentifier = extractIdentifier(nodeToDocument, language);
return {
nodeIdentifier,
Expand All @@ -96,7 +99,9 @@ export async function _getDocumentableNodeIfOnIdentifier(
if (smallestNodeContainingRange.type.match(/identifier/) &&
(smallestNodeContainingRange.parent === null || isDocumentableNode(smallestNodeContainingRange.parent, language))
) {
const parent = smallestNodeContainingRange.parent;
const parent = smallestNodeContainingRange.parent === null
? null
: unwrapPythonDecoratedDefinition(smallestNodeContainingRange.parent, language);

const parentNodeRange = parent === null
? undefined
Expand Down
30 changes: 30 additions & 0 deletions extensions/copilot/src/platform/parser/node/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,37 @@ export function isDocumentableNode(node: SyntaxNode, language: WASMLanguage) {
return node.type.match(/definition|declaration|class_specifier/);
case WASMLanguage.Ruby:
return node.type.match(/module|class|method|assignment/);
case WASMLanguage.Python:
// Match `function_definition`/`class_definition`, plus the wrapping
// `decorated_definition` so that selections/cursors *on the decorator
// line itself* still resolve to the function/class (rather than
// climbing all the way up to `module`). Callers must unwrap a matched
// `decorated_definition` to its inner definition via
// {@link unwrapPythonDecoratedDefinition} so that downstream consumers
// (e.g. docstring generation) treat the `def`/`class` line — not the
// `@decorator` line — as the start of the definition. Otherwise
// docstrings end up *before* the decorator, which is a syntax error.
// See https://github.com/microsoft/vscode/issues/283165.
return node.type.match(/^(function_definition|class_definition|decorated_definition)$/);
default:
return node.type.match(/definition|declaration|declarator/);
}
}

/**
* In Python, a `decorated_definition` wraps a `function_definition` or
* `class_definition` whenever decorators are present. Its range starts at the
* `@decorator` line, which is *not* what callers want as "the node to
* document" — placing a docstring at that range would put it before the
* decorator (a syntax error). This helper unwraps a `decorated_definition` to
* its inner `function_definition`/`class_definition` so the range starts at
* the `def`/`class` keyword.
*
* See {@link isDocumentableNode} and https://github.com/microsoft/vscode/issues/283165.
*/
export function unwrapPythonDecoratedDefinition(node: SyntaxNode, language: WASMLanguage): SyntaxNode {
if (language !== WASMLanguage.Python || node.type !== 'decorated_definition') {
return node;
}
return node.children.find(c => c.type === 'function_definition' || c.type === 'class_definition') ?? node;
}
Loading
Loading