Path: blob/main/extensions/copilot/src/extension/prompts/node/test/utils.ts
13405 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 * as fs from 'fs';6import type * as vscode from 'vscode';7import { VsCodeTextDocument } from '../../../../platform/editing/common/abstractText';8import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';9import { getStructureUsingIndentation } from '../../../../platform/parser/node/indentationStructure';10import { IParserService } from '../../../../platform/parser/node/parserService';11import { WASMLanguage } from '../../../../platform/parser/node/treeSitterLanguages';12import { IPlaygroundRunnerGlobals } from '../../../../util/common/debugValueEditorGlobals';13import { createTextDocumentData } from '../../../../util/common/test/shims/textDocument';14import * as path from '../../../../util/vs/base/common/path';15import { URI } from '../../../../util/vs/base/common/uri';16import { OffsetRange } from '../../../../util/vs/editor/common/core/ranges/offsetRange';17import { Range, Selection } from '../../../../vscodeTypes';18import { createExtensionUnitTestingServices } from '../../../test/node/services';19import { getAdjustedSelection } from '../inline/adjustSelection';20import { IProjectedDocumentDebugInfo } from '../inline/summarizedDocument/implementation';21import { IDocumentSummarizationItem, ISummarizedDocumentSettings, summarizeDocumentsSync } from '../inline/summarizedDocument/summarizeDocument';22import { summarizeDocumentSync } from '../inline/summarizedDocument/summarizeDocumentHelpers';23import { SummarizeDocumentPlayground } from './summarizeDocumentPlayground';2425export const DEFAULT_CHAR_LIMIT = 9557.333333333334;2627export function loadFile(data: FixtureData): Promise<ITestFile>;28export function loadFile(data: Omit<FixtureData, 'filePath'> & { fileName: string; fileContents: string }): Promise<'not_supported'>;29export async function loadFile(data: FixtureData | (Omit<FixtureData, 'filePath'> & { fileName: string; fileContents: string })): Promise<ITestFile | 'not_supported'> {30if ('fileName' in data) { return 'not_supported'; }31const contents = (await fs.promises.readFile(data.filePath)).toString();32return {33contents,34filePath: data.filePath,35languageId: data.languageId,36formattingOptions: undefined,37};38}3940interface FixtureData {41filePath: string;42languageId: 'typescript' | string;43}4445/** See https://github.com/microsoft/vscode-ts-file-path-support */46export type RelativeFilePath<T extends string> = string & { baseDir?: T };4748export function fixture(relativePath: RelativeFilePath<'$dir/fixtures'>): string {49const filePath = path.join(__dirname, 'fixtures', relativePath);50return filePath;51}52export function getSummarizedSnapshotPath(data: ITestFile, version?: string): string {53const secondaryExtension = (version ? `${version}.` : '') + 'summarized';54return addSecondaryExtension(data.filePath, secondaryExtension);55}5657function addSecondaryExtension(filePath: string, extension: string): string {58const parts = filePath.split('.');59parts.splice(parts.length - 1, 0, extension);60return parts.join('.');61}626364export async function fromFixtureOld(65pathWithinFixturesDir: string,66languageId: WASMLanguage | string,67formattingOptions?: vscode.FormattingOptions68): Promise<ITestFile> {69const filePath = path.join(__dirname, 'fixtures', pathWithinFixturesDir);70const contents = (await fs.promises.readFile(filePath)).toString();71return { filePath: filePath, contents, languageId, formattingOptions };72}7374export function docPathInFixture(pathWithinFixturesDir: string, type: 'summarized' | 'selection') {75const dirname = path.dirname(pathWithinFixturesDir);76const basename = path.basename(pathWithinFixturesDir);77const basenameByDots = basename.split('.');78basenameByDots.splice(basenameByDots.length - 1, 0, type);79const docBasename = basenameByDots.join('.');80const docPathWithinFixturesDir = path.join(dirname, docBasename);81return path.join(__dirname, 'fixtures', docPathWithinFixturesDir);82}8384export function summarizedDocPathInFixture(pathWithinFixturesDir: string) {85return docPathInFixture(pathWithinFixturesDir, 'summarized');86}8788export function selectionDocPathInFixture(pathWithinFixturesDir: string) {89return docPathInFixture(pathWithinFixturesDir, 'selection');90}9192interface ITestFile {93contents: string;94filePath: string;95languageId: WASMLanguage | string;96formattingOptions?: vscode.FormattingOptions;97}98export async function generateSummarizedDocument(99filePromise: ITestFile | Promise<ITestFile>,100selection: [lineNumber: number, columnNumber: number] | [number, number, number, number] | undefined,101charLimit: number = DEFAULT_CHAR_LIMIT,102settings: ISummarizedDocumentSettings = {},103): Promise<{ text: string; adjustedSelection: OffsetRange }> {104const file = await filePromise;105const doc = TextDocumentSnapshot.create(createTextDocumentData(106URI.from({ scheme: 'test', path: '/path/file.txt' }),107file.contents,108file.languageId109).document);110const accessor = createExtensionUnitTestingServices().createTestingAccessor();111const parserService = accessor.get(IParserService);112const currentDocAST = parserService.getTreeSitterAST(doc);113let structure = currentDocAST114? await currentDocAST.getStructure()115: undefined;116if (!structure) {117structure = getStructureUsingIndentation(118new VsCodeTextDocument(doc),119doc.languageId,120file.formattingOptions121);122}123const selections = selection ? getAdjustedSelection(structure, new VsCodeTextDocument(doc), toSelection(selection)) : undefined;124const summarizedDoc = summarizeDocumentSync(125charLimit,126doc,127selection ? toSelection(selection) : undefined,128structure,129settings,130) as IProjectedDocumentDebugInfo;131132const playgroundRunnerData = (globalThis as any as IPlaygroundRunnerGlobals).$$playgroundRunner_data;133if (playgroundRunnerData) {134function getDoc(text: string) {135const file = { contents: text, languageId: doc.languageId };136const data = createTextDocumentData(137URI.from({ scheme: 'test', path: '/path/file.ts' }),138file.contents,139file.languageId,140);141return data;142}143144globalThis.playground = new SummarizeDocumentPlayground(145summarizedDoc,146selection ? toSelection(selection) : new Range(0, 0, 0, 0),147charLimit,148(text) => parserService.getTreeSitterAST(getDoc(text).document)!.getStructure(),149(text, charLimit, selection, structure) => summarizeDocumentSync(150charLimit,151TextDocumentSnapshot.create(getDoc(text).document),152selection,153structure,154settings,155) as IProjectedDocumentDebugInfo156);157158globalThis.summarizedDoc = summarizedDoc;159const g = globalThis as any;160g.$$debugValueEditor_properties = [161{162label: `Active Test: "${playgroundRunnerData.currentPath.join(' > ')}"`,163},164{165label: 'Summarized Document',166expression: getExprText(() => globalThis.playground!.getSummarizedText()),167},168{169label: `Document Syntax Tree`,170expression: getExprText(() => globalThis.playground!.getAst()),171},172{173label: 'Input Document + Selection',174expression: getExprText(() => globalThis.playground!.inputDocument),175},176{177label: 'Input Options',178expression: getExprText(() => globalThis.playground!.inputOptions),179},180];181g.$$debugValueEditor_refresh?.('{}');182}183184return {185text: summarizedDoc.text,186adjustedSelection: selections ? summarizedDoc.projectOffsetRange(selections.adjusted) : new OffsetRange(0, 0),187};188}189export async function generateSummarizedDocuments(190input: {191filePromise: ITestFile | Promise<ITestFile>;192selection: [number, number] | [number, number, number, number] | undefined;193}[],194charLimit: number = DEFAULT_CHAR_LIMIT,195settings: ISummarizedDocumentSettings = {},196) {197198const items: IDocumentSummarizationItem[] = [];199200for (const { filePromise, selection } of input) {201202203const file = await filePromise;204const doc = TextDocumentSnapshot.create(createTextDocumentData(205URI.from({ scheme: 'test', path: file.filePath }),206file.contents,207file.languageId208).document);209const accessor = createExtensionUnitTestingServices().createTestingAccessor();210const parserService = accessor.get(IParserService);211const currentDocAST = parserService.getTreeSitterAST(doc);212let structure = currentDocAST213? await currentDocAST.getStructure()214: undefined;215if (!structure) {216structure = getStructureUsingIndentation(217new VsCodeTextDocument(doc),218doc.languageId,219file.formattingOptions220);221}222// const selections = selection ? getAdjustedSelection(structure, doc, toSelection(selection)) : undefined;223224items.push({225document: doc,226overlayNodeRoot: structure,227selection: selection && toSelection(selection)228});229230}231232return summarizeDocumentsSync(233charLimit,234settings,235items236);237}238239export function getExprText(arrowFn: () => any): string {240const src = arrowFn.toString();241const parts = src.split('=>');242const expr = parts[1];243return expr.trim();244}245246declare namespace globalThis {247export let playground: SummarizeDocumentPlayground | undefined;248export let summarizedDoc: IProjectedDocumentDebugInfo | undefined;249}250export async function generateSummarizedDocumentAndExtractGoodSelection(251filePromise: ITestFile | Promise<ITestFile>,252selection: [number, number] | [number, number, number, number],253charLimit: number = DEFAULT_CHAR_LIMIT254): Promise<[string | undefined, string | undefined]> {255const result = await generateSummarizedDocument(filePromise, selection, charLimit);256if (!result) {257return [undefined, undefined];258}259const adjustedSelection = result.adjustedSelection;260const codeAbove = result.text.substring(2610,262adjustedSelection.start263);264const adjustedSelectedCode = result.text.substring(265adjustedSelection.start,266adjustedSelection.endExclusive267);268const codeBelow = result.text.substring(269adjustedSelection.endExclusive270);271272return [`${codeAbove}__SELECTION_HERE__${codeBelow}`, adjustedSelectedCode];273}274function toSelection(275selection: [number, number] | [number, number, number, number]276): vscode.Selection {277if (selection.length === 2) {278return new Selection(279selection[0],280selection[1],281selection[0],282selection[1]283);284} else {285return new Selection(286selection[0],287selection[1],288selection[2],289selection[3]290);291}292}293294295