Path: blob/main/extensions/copilot/src/extension/linkify/test/node/filePathLinkifier.spec.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 { suite, test } from 'vitest';6import { isWindows } from '../../../../util/vs/base/common/platform';7import { URI } from '../../../../util/vs/base/common/uri';8import { PromptReference } from '../../../prompt/common/conversation';9import { LinkifyLocationAnchor } from '../../common/linkifiedText';10import { assertPartsEqual, createTestLinkifierService, linkify, workspaceFile } from './util';111213suite('File Path Linkifier', () => {1415test(`Should create file links from Markdown links`, async () => {16const linkifier = createTestLinkifierService(17'file.ts',18'src/file.ts'19);2021assertPartsEqual(22(await linkify(linkifier,23'[file.ts](file.ts) [src/file.ts](src/file.ts)',24)).parts,25[26new LinkifyLocationAnchor(workspaceFile('file.ts')),27` `,28new LinkifyLocationAnchor(workspaceFile('src/file.ts'))29],30);3132assertPartsEqual(33(await linkify(linkifier,34'[`file.ts`](file.ts) [`src/file.ts`](src/file.ts)',35)).parts,36[37new LinkifyLocationAnchor(workspaceFile('file.ts')),38` `,39new LinkifyLocationAnchor(workspaceFile('src/file.ts'))40]41);42});4344test(`Should create links for directories`, async () => {45{46const linkifier = createTestLinkifierService(47'dir/'48);49assertPartsEqual(50(await linkify(linkifier,51'[dir](dir) [dir/](dir/)',52)).parts,53[54new LinkifyLocationAnchor(workspaceFile('dir')),55` `,56new LinkifyLocationAnchor(workspaceFile('dir/'))57]58);59}60{61const linkifier = createTestLinkifierService(62'dir1/dir2/'63);64assertPartsEqual(65(await linkify(linkifier,66'[dir1/dir2](dir1/dir2) [dir1/dir2/](dir1/dir2/)',67)).parts,68[69new LinkifyLocationAnchor(workspaceFile('dir1/dir2')),70` `,71new LinkifyLocationAnchor(workspaceFile('dir1/dir2/'))72]73);74}75});7677test(`Should create file links for file paths as inline code`, async () => {78const linkifier = createTestLinkifierService(79'file.ts',80'src/file.ts',81);82assertPartsEqual(83(await linkify(linkifier,84'`file.ts` `src/file.ts`',85)).parts,86[87new LinkifyLocationAnchor(workspaceFile('file.ts')),88` `,89new LinkifyLocationAnchor(workspaceFile('src/file.ts'))90]91);92});9394test(`Should create file paths printed as plain text `, async () => {95const linkifier = createTestLinkifierService(96'file.ts',97'src/file.ts',98);99assertPartsEqual(100(await linkify(linkifier,101'file.ts src/file.ts'102)).parts,103[104new LinkifyLocationAnchor(workspaceFile('file.ts')),105` `,106new LinkifyLocationAnchor(workspaceFile('src/file.ts'))107]108);109});110111test(`Should de-linkify files that don't exist`, async () => {112const linkifier = createTestLinkifierService();113assertPartsEqual(114(await linkify(linkifier,115'[noSuchFile.ts](noSuchFile.ts) [src/noSuchFile.ts](src/noSuchFile.ts)',116)).parts,117[118'noSuchFile.ts src/noSuchFile.ts'119],120);121});122123test(`Should de-linkify bare file links that haven't been transformed`, async () => {124const linkifier = createTestLinkifierService(125'file.ts',126'src/file.ts',127);128assertPartsEqual(129(await linkify(linkifier,130'[text](file.ts) [`symbol` foo](src/file.ts)'131)).parts,132[133'text `symbol` foo',134]135);136});137138test(`Should not create links for https links`, async () => {139const linkifier = createTestLinkifierService();140assertPartsEqual(141(await linkify(linkifier,142'[http://example.com](http://example.com)',143)).parts,144[145'[http://example.com](http://example.com)',146],147);148});149150test(`Should handle file paths with spaces in the name`, async () => {151const linkifier = createTestLinkifierService(152`space file.ts`,153'sub space/space file.ts',154);155156const result = await linkify(linkifier, [157'[space file.ts](space%20file.ts)',158'[sub space/space file.ts](sub%20space/space%20file.ts)',159'[no such file.ts](no%20such%20file.ts)',160'[also not.ts](no%20such%20file.ts)',161].join('\n')162);163assertPartsEqual(164result.parts,165[166new LinkifyLocationAnchor(workspaceFile('space file.ts')),167`\n`,168new LinkifyLocationAnchor(workspaceFile('sub space/space file.ts')),169'\nno such file.ts\nalso not.ts',170]171);172});173174test(`Should handle posix style absolute paths`, async () => {175const isFile = URI.file(isWindows ? 'c:\\foo\\isfile.ts' : '/foo/isfile.ts');176const noFile = URI.file(isWindows ? 'c:\\foo\\nofile.ts' : '/foo/nofile.ts');177const linkifier = createTestLinkifierService(178isFile179);180181assertPartsEqual(182(await linkify(linkifier, [183`\`${isFile.fsPath}\``,184`\`${noFile.fsPath}\``,185].join('\n')186)).parts,187[188new LinkifyLocationAnchor(isFile),189`\n\`${noFile.fsPath}\``,190]191);192});193194test(`Should not linkify some common ambagious short paths`, async () => {195const linkifier = createTestLinkifierService();196assertPartsEqual(197(await linkify(linkifier, [198'- `.`',199'- `..`',200'- `/.`',201'- `\\.`',202'- `/..`',203'- `\\..`',204'- `/`',205'- `\\`',206'- `/`',207'- `//`',208'- `///`',209].join('\n')210)).parts,211[212[213'- `.`',214'- `..`',215'- `/.`',216'- `\\.`',217'- `/..`',218'- `\\..`',219'- `/`',220'- `\\`',221'- `/`',222'- `//`',223'- `///`',224].join('\n')225]226);227});228229test(`Should find file links in bold elements`, async () => {230const linkifier = createTestLinkifierService(231'file.ts',232'src/file.ts'233);234235assertPartsEqual(236(await linkify(linkifier,237'**file.ts**',238)).parts,239[240`**`,241new LinkifyLocationAnchor(workspaceFile('file.ts')),242`**`,243],244);245246assertPartsEqual(247(await linkify(linkifier,248'**`file.ts`**',249)).parts,250[251`**`,252new LinkifyLocationAnchor(workspaceFile('file.ts')),253`**`,254],255);256});257258test(`Should NOT use reference fallback for paths with directory components`, async () => {259// When text has a path like ./node_modules/playwright/cli.js, we should NOT260// match it to a reference just because the basename (cli.js) matches.261// This prevents linking to wrong files when the model mentions paths that don't exist.262const linkifier = createTestLinkifierService();263const references = [new PromptReference(URI.file('/workspace/src/cli.js'))];264265// Path with directories should NOT link to reference with matching basename266const result = await linkify(linkifier,267'./node_modules/playwright/cli.js',268references269);270assertPartsEqual(result.parts, [271'./node_modules/playwright/cli.js' // Should remain as plain text272]);273});274275test(`Should use reference fallback for simple filenames`, async () => {276// Simple filenames without directory components CAN use reference fallback277const linkifier = createTestLinkifierService();278const refUri = URI.file('/workspace/src/cli.js');279const references = [new PromptReference(refUri)];280281// Simple filename should link to reference with matching basename282const result = await linkify(linkifier,283'cli.js',284references285);286assertPartsEqual(result.parts, [287new LinkifyLocationAnchor(refUri)288]);289});290291test(`Should NOT use reference fallback for text with code-like characters`, async () => {292// Text containing $, {, }, (, ) are likely code snippets, not filenames293const linkifier = createTestLinkifierService();294const refUri = URI.file('/workspace/src/config.js');295const references = [new PromptReference(refUri)];296297// Code-like text should NOT link to reference even if basename matches298const result = await linkify(linkifier,299'config.${TerminalSettingId',300references301);302assertPartsEqual(result.parts, [303'config.${TerminalSettingId' // Should remain as plain text304]);305});306});307308309