fix(markdown): avoid considering link references/sources in code sections (#146826)

This commit is contained in:
Akat 2022-04-08 05:50:37 +08:00 committed by GitHub
parent 108baab0c6
commit b499467f9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 55 deletions

View file

@ -207,9 +207,9 @@ async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): P
return { multiline, inline };
}
function isLinkInsideCode(code: CodeInDocument, link: MdLink) {
return code.multiline.some(interval => link.source.hrefRange.start.line >= interval[0] && link.source.hrefRange.start.line < interval[1]) ||
code.inline.some(position => position.intersection(link.source.hrefRange));
function isLinkInsideCode(code: CodeInDocument, linkHrefRange: vscode.Range) {
return code.multiline.some(interval => linkHrefRange.start.line >= interval[0] && linkHrefRange.start.line < interval[1]) ||
code.inline.some(position => position.intersection(linkHrefRange));
}
export class MdLinkProvider implements vscode.DocumentLinkProvider {
@ -257,33 +257,31 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
public async getAllLinks(document: SkinnyTextDocument): Promise<MdLink[]> {
const codeInDocument = await findCode(document, this.engine);
return Array.from([
...(await this.getInlineLinks(document)),
...this.getReferenceLinks(document),
...this.getLinkDefinitions(document),
...this.getAutoLinks(document),
...this.getInlineLinks(document, codeInDocument),
...this.getReferenceLinks(document, codeInDocument),
...this.getLinkDefinitions2(document, codeInDocument),
...this.getAutoLinks(document, codeInDocument),
]);
}
private async getInlineLinks(document: SkinnyTextDocument): Promise<MdLink[]> {
private *getInlineLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable<MdLink> {
const text = document.getText();
const results: MdLink[] = [];
const codeInDocument = await findCode(document, this.engine);
for (const match of text.matchAll(linkPattern)) {
const matchImageData = match[4] && extractDocumentLink(document, match[3].length + 1, match[4], match.index);
if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData)) {
results.push(matchImageData);
if (matchImageData && !isLinkInsideCode(codeInDocument, matchImageData.source.hrefRange)) {
yield matchImageData;
}
const matchLinkData = extractDocumentLink(document, match[1].length, match[5], match.index);
if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData)) {
results.push(matchLinkData);
if (matchLinkData && !isLinkInsideCode(codeInDocument, matchLinkData.source.hrefRange)) {
yield matchLinkData;
}
}
return results;
}
private *getAutoLinks(document: SkinnyTextDocument): Iterable<MdLink> {
private *getAutoLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable<MdLink> {
const text = document.getText();
for (const match of text.matchAll(autoLinkPattern)) {
@ -293,6 +291,10 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
const offset = (match.index ?? 0) + 1;
const linkStart = document.positionAt(offset);
const linkEnd = document.positionAt(offset + link.length);
const hrefRange = new vscode.Range(linkStart, linkEnd);
if (isLinkInsideCode(codeInDocument, hrefRange)) {
continue;
}
yield {
kind: 'link',
href: linkTarget,
@ -307,7 +309,7 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
}
private *getReferenceLinks(document: SkinnyTextDocument): Iterable<MdLink> {
private *getReferenceLinks(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable<MdLink> {
const text = document.getText();
for (const match of text.matchAll(referenceLinkPattern)) {
let linkStart: vscode.Position;
@ -327,12 +329,17 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
continue;
}
const hrefRange = new vscode.Range(linkStart, linkEnd);
if (isLinkInsideCode(codeInDocument, hrefRange)) {
continue;
}
yield {
kind: 'link',
source: {
text: reference,
resource: document.uri,
hrefRange: new vscode.Range(linkStart, linkEnd),
hrefRange,
fragmentRange: undefined,
},
href: {
@ -343,7 +350,12 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
}
}
public *getLinkDefinitions(document: SkinnyTextDocument): Iterable<MdLinkDefinition> {
public async getLinkDefinitions(document: SkinnyTextDocument): Promise<Iterable<MdLinkDefinition>> {
const codeInDocument = await findCode(document, this.engine);
return this.getLinkDefinitions2(document, codeInDocument);
}
private *getLinkDefinitions2(document: SkinnyTextDocument, codeInDocument: CodeInDocument): Iterable<MdLinkDefinition> {
const text = document.getText();
for (const match of text.matchAll(definitionPattern)) {
const pre = match[1];
@ -354,41 +366,35 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider {
const refStart = document.positionAt((match.index ?? 0) + 1);
const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length }));
let linkStart: vscode.Position;
let linkEnd: vscode.Position;
let text: string;
if (angleBracketLinkRe.test(link)) {
const linkStart = document.positionAt(offset + 1);
const linkEnd = document.positionAt(offset + link.length - 1);
const text = link.substring(1, link.length - 1);
const target = parseLink(document, text);
if (target) {
yield {
kind: 'definition',
source: {
text: link,
resource: document.uri,
hrefRange: new vscode.Range(linkStart, linkEnd),
fragmentRange: getFragmentRange(link, linkStart, linkEnd),
},
ref: { text: reference, range: refRange },
href: target,
};
}
linkStart = document.positionAt(offset + 1);
linkEnd = document.positionAt(offset + link.length - 1);
text = link.substring(1, link.length - 1);
} else {
const linkStart = document.positionAt(offset);
const linkEnd = document.positionAt(offset + link.length);
const target = parseLink(document, link);
if (target) {
yield {
kind: 'definition',
source: {
text: link,
resource: document.uri,
hrefRange: new vscode.Range(linkStart, linkEnd),
fragmentRange: getFragmentRange(link, linkStart, linkEnd)
},
ref: { text: reference, range: refRange },
href: target,
};
}
linkStart = document.positionAt(offset);
linkEnd = document.positionAt(offset + link.length);
text = link;
}
const hrefRange = new vscode.Range(linkStart, linkEnd);
if (isLinkInsideCode(codeInDocument, hrefRange)) {
continue;
}
const target = parseLink(document, text);
if (target) {
yield {
kind: 'definition',
source: {
text: link,
resource: document.uri,
hrefRange,
fragmentRange: getFragmentRange(link, linkStart, linkEnd),
},
ref: { text: reference, range: refRange },
href: target,
};
}
}
}

View file

@ -103,7 +103,11 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
switch (context.kind) {
case CompletionContextKind.ReferenceLink: {
return Array.from(this.provideReferenceSuggestions(document, position, context));
const items: vscode.CompletionItem[] = [];
for await (const item of this.provideReferenceSuggestions(document, position, context)) {
items.push(item);
}
return items;
}
case CompletionContextKind.LinkDefinition:
@ -232,11 +236,11 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider {
};
}
private *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): Iterable<vscode.CompletionItem> {
private async *provideReferenceSuggestions(document: SkinnyTextDocument, position: vscode.Position, context: CompletionContext): AsyncIterable<vscode.CompletionItem> {
const insertionRange = new vscode.Range(context.linkTextStartPosition, position);
const replacementRange = new vscode.Range(insertionRange.start, position.translate({ characterDelta: context.linkSuffix.length }));
const definitions = this.linkProvider.getLinkDefinitions(document);
const definitions = await this.linkProvider.getLinkDefinitions(document);
for (const def of definitions) {
yield {
kind: vscode.CompletionItemKind.Reference,

View file

@ -213,6 +213,26 @@ suite('markdown.DocumentLinkProvider', () => {
assert.strictEqual(links.length, 0);
});
test('Should not consider link references in code fenced with backticks (#146714)', async () => {
const text = joinLines(
'```',
'[a] [bb]',
'```');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider reference sources in code fenced with backticks (#146714)', async () => {
const text = joinLines(
'```',
'[a]: http://example.com;',
'[b]: <http://example.com>;',
'[c]: (http://example.com);',
'```');
const links = await getLinksForFile(text);
assert.strictEqual(links.length, 0);
});
test('Should not consider links in multiline inline code span between between text', async () => {
const text = joinLines(
'[b](https://1.com) `[b](https://2.com)',