Path: blob/main/extensions/copilot/src/platform/parser/node/parserServiceImpl.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 { WorkerWithRpcProxy } from '../../../util/node/worker';6import { Lazy } from '../../../util/vs/base/common/lazy';7import * as path from '../../../util/vs/base/common/path';8import { TreeSitterOffsetRange, TreeSitterPointRange } from './nodes';9import * as parser from './parserImpl';10import { IParserService, ParserWorkerTimeoutError, TreeSitterAST } from './parserService';11import { WASMLanguage, getWasmLanguage } from './treeSitterLanguages';1213const workerPath = path.join(__dirname, 'worker2.js');14type ParserType = Omit<typeof parser, '_getNodeMatchingSelection'>;1516export class ParserServiceImpl implements IParserService {1718declare readonly _serviceBrand: undefined;1920private _parser: WorkerOrLocal<ParserType>;2122constructor(23useWorker: boolean24) {25this._parser = new WorkerOrLocal<ParserType>(parser, workerPath, useWorker);26}2728dispose(): void {29this._parser.dispose();30}3132getTreeSitterAST(textDocument: { readonly languageId: string; getText(): string }): TreeSitterAST | undefined {33const wasmLanguage = getWasmLanguage(textDocument.languageId);34if (!wasmLanguage) {35return undefined;36}37return this.getTreeSitterASTForWASMLanguage(wasmLanguage, textDocument.getText());38}3940getTreeSitterASTForWASMLanguage(wasmLanguage: WASMLanguage, source: string): TreeSitterAST {41const parserProxy = this._parser.proxy;42return {43getFunctionBodies: () => parserProxy._getFunctionBodies(wasmLanguage, source),44getCoarseParentScope: (range: TreeSitterPointRange) => parserProxy._getCoarseParentScope(wasmLanguage, source, range),45getFixSelectionOfInterest: (range: TreeSitterPointRange, maxNumberOfLines: number) => parserProxy._getFixSelectionOfInterest(wasmLanguage, source, range, maxNumberOfLines),46getCallExpressions: (selection: TreeSitterOffsetRange) => parserProxy._getCallExpressions(wasmLanguage, source, selection),47getFunctionDefinitions: () => parserProxy._getFunctionDefinitions(wasmLanguage, source),48getClassReferences: (selection: TreeSitterOffsetRange) => parserProxy._getClassReferences(wasmLanguage, source, selection),49getClassDeclarations: () => parserProxy._getClassDeclarations(wasmLanguage, source),50getTypeDeclarations: () => parserProxy._getTypeDeclarations(wasmLanguage, source),51getTypeReferences: (selection: TreeSitterOffsetRange) => parserProxy._getTypeReferences(wasmLanguage, source, selection),52getSymbols: (selection: TreeSitterOffsetRange) => parserProxy._getSymbols(wasmLanguage, source, selection),53getDocumentableNodeIfOnIdentifier: (range: TreeSitterOffsetRange) => parserProxy._getDocumentableNodeIfOnIdentifier(wasmLanguage, source, range),54getTestableNode: (range: TreeSitterOffsetRange) => parserProxy._getTestableNode(wasmLanguage, source, range),55getTestableNodes: () => parserProxy._getTestableNodes(wasmLanguage, source),56getNodeToExplain: (range: TreeSitterOffsetRange) => parserProxy._getNodeToExplain(wasmLanguage, source, range),57getNodeToDocument: (range: TreeSitterOffsetRange) => parserProxy._getNodeToDocument(wasmLanguage, source, range),58getFineScopes: (selection: TreeSitterOffsetRange) => parserProxy._getFineScopes(wasmLanguage, source, selection),59getStructure: () => parserProxy._getStructure(wasmLanguage, source),60findLastTest: () => parserProxy._findLastTest(wasmLanguage, source),61getParseErrorCount: () => parserProxy._getParseErrorCount(wasmLanguage, source),62};63}6465getSemanticChunkTree(wasmLanguage: WASMLanguage, source: string) {66return this._parser.proxy._getSemanticChunkTree(wasmLanguage, source);67}6869getSemanticChunkNames(language: WASMLanguage, source: string) {70return this._parser.proxy._getSemanticChunkNames(language, source);71}72}7374type Proxied<ProxyType> = {75[K in keyof ProxyType]: ProxyType[K] extends ((...args: infer Args) => infer R) ? (...args: Args) => Promise<Awaited<R>> : never;76};7778const _workerCallTimeout = 3_000;7980class WorkerOrLocal<T extends object> {8182private readonly _local: T;8384public get proxy(): Proxied<T> {85if (this._useWorker) {86return this._workerProxy;87}88return <any>this._local;89}9091private _worker: Lazy<WorkerWithRpcProxy<T>>;92private readonly _workerProxy: Proxied<T>;9394private _restart(): void {95if (this._worker.hasValue) {96this._worker.value.terminate();97}98this._worker = new Lazy(() => new WorkerWithRpcProxy<T>(this._workerPath, { name: 'Parser worker' }));99}100101constructor(102local: T,103private readonly _workerPath: string,104private readonly _useWorker: boolean,105) {106this._local = new Proxy(local, {107get: (target, prop, receiver) => {108const originalMethod = (target as any)[prop];109if (typeof originalMethod !== 'function') {110return originalMethod;111}112113return async (...args: any[]) => {114const result = await originalMethod.apply(target, viaJSON(args));115return viaJSON(result);116};117},118});119this._worker = new Lazy(() => new WorkerWithRpcProxy<T>(this._workerPath, { name: 'Parser worker' }));120this._workerProxy = this._createTimeoutProxy();121}122123private _createTimeoutProxy(): Proxied<T> {124const self = this;125return new Proxy({} as Proxied<T>, {126get(_target, prop) {127return async (...args: unknown[]) => {128const timedOut = Symbol();129const workerProxy = self._worker.value.proxy;130const call = (workerProxy as any)[prop](...args);131let timeoutHandle: ReturnType<typeof setTimeout> | undefined;132const timeoutPromise = new Promise<typeof timedOut>(resolve => {133timeoutHandle = setTimeout(() => resolve(timedOut), _workerCallTimeout);134});135try {136const result = await Promise.race([call, timeoutPromise]);137if (result === timedOut) {138self._restart();139throw new ParserWorkerTimeoutError();140}141return result;142} finally {143clearTimeout(timeoutHandle);144}145};146},147});148}149150dispose(): void {151if (this._worker.hasValue) {152this._worker.value.terminate();153}154}155}156157function viaJSON<T>(obj: T): T {158if (typeof obj === 'undefined') {159return obj;160}161return JSON.parse(JSON.stringify(obj));162}163164165