From ab7bc9fb0b508d81ef1bb30a3d3cc4229e0800a7 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Thu, 23 Jun 2022 17:53:56 -0700 Subject: [PATCH] Split up MD link tests (#153048) This splits the markdown link tests in two: - Tests for detecting links in md files (`MdLinkComputer`) - Tests for the actual vs code editor link provider Also fixes a few cases splitting these tests up caught --- .../src/languageFeatures/documentLinks.ts | 13 +- .../src/test/documentLinkProvider.test.ts | 121 ++++++++++++------ 2 files changed, 92 insertions(+), 42 deletions(-) diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts index 057197b4dee..13e8fcd97f5 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinks.ts @@ -220,7 +220,7 @@ const linkPattern = new RegExp( /** * Matches `[text][ref]` or `[shorthand]` */ -const referenceLinkPattern = /(^|[^\]\\])(?:(?:(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]|\[\s*?([^\s\]]*?)\])(?![\:\(]))/gm; +const referenceLinkPattern = /(^|[^\]\\])(?:(?:(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]|\[\s*?([^\s\\\]]*?)\])(?![\:\(]))/gm; /** * Matches `` @@ -352,12 +352,17 @@ export class MdLinkComputer { let linkStart: vscode.Position; let linkEnd: vscode.Position; let reference = match[4]; - if (reference) { // [text][ref] + if (reference === '') { // [ref][], + reference = match[3]; + const offset = ((match.index ?? 0) + match[1].length) + 1; + linkStart = document.positionAt(offset); + linkEnd = document.positionAt(offset + reference.length); + } else if (reference) { // [text][ref] const pre = match[2]; const offset = ((match.index ?? 0) + match[1].length) + pre.length; linkStart = document.positionAt(offset); linkEnd = document.positionAt(offset + reference.length); - } else if (match[5]) { // [ref][], [ref] + } else if (match[5]) { // [ref] reference = match[5]; const offset = ((match.index ?? 0) + match[1].length) + 1; linkStart = document.positionAt(offset); @@ -526,6 +531,8 @@ export class MdVsCodeLinkProvider implements vscode.DocumentLinkProvider { return documentLink; } case 'reference': { + // We only render reference links in the editor if they are actually defined. + // This matches how reference links are rendered by markdown-it. const def = definitionSet.lookup(link.href.ref); if (def) { const documentLink = new vscode.DocumentLink( diff --git a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts index dcc31c997da..1a53e409362 100644 --- a/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts +++ b/extensions/markdown-language-features/src/test/documentLinkProvider.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import 'mocha'; import * as vscode from 'vscode'; -import { MdLinkProvider, MdVsCodeLinkProvider } from '../languageFeatures/documentLinks'; +import { MdLink, MdLinkComputer, MdLinkProvider, MdVsCodeLinkProvider } from '../languageFeatures/documentLinks'; import { noopToken } from '../util/cancellation'; import { InMemoryDocument } from '../util/inMemoryDocument'; import { createNewMarkdownEngine } from './engine'; @@ -15,25 +15,23 @@ import { nulLogger } from './nulLogging'; import { assertRangeEqual, joinLines, workspacePath } from './util'; -function getLinksForFile(fileContents: string) { - const doc = new InMemoryDocument(workspacePath('x.md'), fileContents); - const workspace = new InMemoryMdWorkspace([doc]); +suite('Markdown: MdLinkComputer', () => { - const engine = createNewMarkdownEngine(); - const linkProvider = new MdLinkProvider(engine, workspace, nulLogger); - const provider = new MdVsCodeLinkProvider(linkProvider); - return provider.provideDocumentLinks(doc, noopToken); -} - -function assertLinksEqual(actualLinks: readonly vscode.DocumentLink[], expectedRanges: readonly vscode.Range[]) { - assert.strictEqual(actualLinks.length, expectedRanges.length); - - for (let i = 0; i < actualLinks.length; ++i) { - assertRangeEqual(actualLinks[i].range, expectedRanges[i], `Range ${i} to be equal`); + function getLinksForFile(fileContents: string): Promise { + const doc = new InMemoryDocument(workspacePath('x.md'), fileContents); + const engine = createNewMarkdownEngine(); + const linkProvider = new MdLinkComputer(engine); + return linkProvider.getAllLinks(doc, noopToken); + } + + function assertLinksEqual(actualLinks: readonly MdLink[], expectedRanges: readonly vscode.Range[]) { + assert.strictEqual(actualLinks.length, expectedRanges.length); + + for (let i = 0; i < actualLinks.length; ++i) { + assertRangeEqual(actualLinks[i].source.hrefRange, expectedRanges[i], `Range ${i} to be equal`); + } } -} -suite('Markdown: DocumentLinkProvider', () => { test('Should not return anything for empty document', async () => { const links = await getLinksForFile(''); assertLinksEqual(links, []); @@ -182,30 +180,32 @@ suite('Markdown: DocumentLinkProvider', () => { }); test('Should find reference link shorthand (#141285)', async () => { - { - const links = await getLinksForFile(joinLines( - '[ref]', - '[ref]: https://example.com', - )); - assertLinksEqual(links, [ - new vscode.Range(0, 1, 0, 4), - new vscode.Range(1, 7, 1, 26), - ]); - } - { - const links = await getLinksForFile(joinLines( - '[Does Not Work]', - '[def]: https://example.com', - )); - assertLinksEqual(links, [ - new vscode.Range(1, 7, 1, 26), - ]); - } + const links = await getLinksForFile(joinLines( + '[ref]', + '[ref]: https://example.com', + )); + assertLinksEqual(links, [ + new vscode.Range(0, 1, 0, 4), + new vscode.Range(1, 7, 1, 26), + ]); }); - test('Should not include reference link shorthand when source does not exist (#141285)', async () => { - const links = await getLinksForFile('[Works]'); - assertLinksEqual(links, []); + test('Should find reference link shorthand using empty closing brackets (#141285)', async () => { + const links = await getLinksForFile(joinLines( + '[ref][]', + )); + assertLinksEqual(links, [ + new vscode.Range(0, 1, 0, 4), + ]); + }); + + test.skip('Should find reference link shorthand for link with space in label (#141285)', async () => { + const links = await getLinksForFile(joinLines( + '[ref with space]', + )); + assertLinksEqual(links, [ + new vscode.Range(0, 7, 0, 26), + ]); }); test('Should not include reference links with escaped leading brackets', async () => { @@ -462,3 +462,46 @@ suite('Markdown: DocumentLinkProvider', () => { ]); }); }); + + +suite('Markdown: VS Code DocumentLinkProvider', () => { + + function getLinksForFile(fileContents: string) { + const doc = new InMemoryDocument(workspacePath('x.md'), fileContents); + const workspace = new InMemoryMdWorkspace([doc]); + + const engine = createNewMarkdownEngine(); + const linkProvider = new MdLinkProvider(engine, workspace, nulLogger); + const provider = new MdVsCodeLinkProvider(linkProvider); + return provider.provideDocumentLinks(doc, noopToken); + } + + function assertLinksEqual(actualLinks: readonly vscode.DocumentLink[], expectedRanges: readonly vscode.Range[]) { + assert.strictEqual(actualLinks.length, expectedRanges.length); + + for (let i = 0; i < actualLinks.length; ++i) { + assertRangeEqual(actualLinks[i].range, expectedRanges[i], `Range ${i} to be equal`); + } + } + + test('Should include defined reference links (#141285)', async () => { + const links = await getLinksForFile(joinLines( + '[ref]', + '[ref][]', + '[ref][ref]', + '', + '[ref]: http://example.com' + )); + assertLinksEqual(links, [ + new vscode.Range(0, 1, 0, 4), + new vscode.Range(1, 1, 1, 4), + new vscode.Range(2, 6, 2, 9), + new vscode.Range(4, 7, 4, 25), + ]); + }); + + test('Should not include reference link shorthand when definition does not exist (#141285)', async () => { + const links = await getLinksForFile('[ref]'); + assertLinksEqual(links, []); + }); +});