Path: blob/main/extensions/copilot/src/platform/parser/node/testGenParsing.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { QueryCapture } from 'web-tree-sitter';6import { uniqueFilter } from '../../../util/vs/base/common/arrays';7import { assertType } from '../../../util/vs/base/common/types';8import { Node, TreeSitterOffsetRange } from './nodes';9import { _parse } from './parserWithCaching';10import { runQueries } from './querying';11import { WASMLanguage } from './treeSitterLanguages';12import { testableNodeQueries, testInSuiteQueries } from './treeSitterQueries';1314export type TestableNode = {15identifier: {16name: string;17range: TreeSitterOffsetRange;18};19node: Node;20};212223export async function _getTestableNode(24language: WASMLanguage,25source: string,26range: TreeSitterOffsetRange27): Promise<TestableNode | null> {28const treeRef = await _parse(language, source);2930try {31const queryCaptures = runQueries(32testableNodeQueries[language],33treeRef.tree.rootNode34).flatMap(({ captures }) => captures); // @ulugbekna: keep in mind: there's duplication of captures3536const symbolKindToIdents = new Map<string, QueryCapture[]>();3738for (const capture of queryCaptures) {39const [symbolKind, name] = capture.name.split('.');40if (name !== 'identifier') {41continue;42}4344const idents = symbolKindToIdents.get(symbolKind) || [];45idents.push(capture);46symbolKindToIdents.set(symbolKind, idents);47}4849let minimalTestableNode: TestableNode | null = null;5051for (const capture of queryCaptures) {52const [symbolKind, name] = capture.name.split('.');5354if (name !== undefined || // ensure we traverse only declarations (and child nodes such as `method.identifier` or `method.accessibility_modifier`)55!TreeSitterOffsetRange.doesContain(capture.node, range) // ensure this declaration contains our range of interest56) {57continue;58}5960// ensure we pick range-wise minimal testable node61if (minimalTestableNode !== null &&62TreeSitterOffsetRange.len(minimalTestableNode.node) < TreeSitterOffsetRange.len(capture.node)63) {64continue;65}6667const idents = symbolKindToIdents.get(symbolKind);6869assertType(idents !== undefined, `must have seen identifier for symbol kind '${symbolKind}' (lang: ${language})`);7071const nodeIdent = idents.find(ident => TreeSitterOffsetRange.doesContain(capture.node, ident.node));7273assertType(nodeIdent !== undefined, `must have seen identifier for symbol '${symbolKind}' (lang: ${language})`);7475minimalTestableNode = {76identifier: {77name: nodeIdent.node.text,78range: TreeSitterOffsetRange.ofSyntaxNode(nodeIdent.node),79},80node: Node.ofSyntaxNode(capture.node),81};82}8384return minimalTestableNode;8586} catch (e) {87console.error('getTestableNode: Unexpected error', e);88return null;89} finally {90treeRef.dispose();91}92}9394export async function _getTestableNodes(95language: WASMLanguage,96source: string,97): Promise<TestableNode[] | null> {98const treeRef = await _parse(language, source);99100try {101const queryCaptures = runQueries(102testableNodeQueries[language],103treeRef.tree.rootNode104)105.flatMap(({ captures }) => captures)106.filter(uniqueFilter((c: QueryCapture) => [c.node.startIndex, c.node.endIndex].toString()));107108const symbolKindToIdents = new Map<string, QueryCapture[]>();109110for (const capture of queryCaptures) {111const [symbolKind, name] = capture.name.split('.');112if (name !== 'identifier') {113continue;114}115116const idents = symbolKindToIdents.get(symbolKind) || [];117idents.push(capture);118symbolKindToIdents.set(symbolKind, idents);119}120121const testableNodes: TestableNode[] = [];122123for (const capture of queryCaptures) {124if (capture.name.includes('.')) {125continue;126}127128const symbolKind = capture.name;129130const idents = symbolKindToIdents.get(symbolKind);131132assertType(idents !== undefined, `must have seen identifier for symbol kind '${symbolKind}' (lang: ${language})`);133134const nodeIdent = idents.find(ident => TreeSitterOffsetRange.doesContain(capture.node, ident.node));135136assertType(nodeIdent !== undefined, `must have seen identifier for symbol '${symbolKind}' (lang: ${language})`);137138testableNodes.push({139identifier: {140name: nodeIdent.node.text,141range: TreeSitterOffsetRange.ofSyntaxNode(nodeIdent.node),142},143node: Node.ofSyntaxNode(capture.node),144});145}146147return testableNodes;148149} catch (e) {150console.error('getTestableNodes: Unexpected error', e);151return null;152} finally {153treeRef.dispose();154}155}156157export async function _findLastTest(lang: WASMLanguage, src: string): Promise<TreeSitterOffsetRange | null> {158159const treeRef = await _parse(lang, src);160161try {162const queryResults = runQueries(testInSuiteQueries[lang], treeRef.tree.rootNode);163164const captures = queryResults165.flatMap(e => e.captures).sort((a, b) => a.node.endIndex - b.node.endIndex)166.filter(c => c.name === 'test');167168if (captures.length === 0) {169return null;170}171172const lastTest = captures[captures.length - 1].node;173174return {175startIndex: lastTest.startIndex,176endIndex: lastTest.endIndex177};178} finally {179treeRef.dispose();180}181}182183184