Don't try parsing non-markdown files (#152661)

This fixes our references and rename provider to not try parsing non-markdown files as if they were markdown
This commit is contained in:
Matt Bierner 2022-06-20 10:44:50 -07:00 committed by GitHub
parent b3dc3301dd
commit 0bc3109761
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 46 deletions

View file

@ -13,12 +13,12 @@ import { MdTableOfContentsWatcher } from '../test/tableOfContentsWatcher';
import { Delayer } from '../util/async';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { isMarkdownFile } from '../util/file';
import { isMarkdownFile, looksLikeMarkdownPath } from '../util/file';
import { Limiter } from '../util/limiter';
import { ResourceMap } from '../util/resourceMap';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinkProvider';
import { MdReferencesProvider, tryFindMdDocumentForLink } from './references';
import { MdReferencesProvider, tryResolveLinkPath } from './references';
const localize = nls.loadMessageBundle();
@ -374,7 +374,7 @@ export class DiagnosticManager extends Disposable {
this.pendingDiagnostics.clear();
await Promise.all(pending.map(async resource => {
const doc = await this.workspaceContents.getMarkdownDocument(resource);
const doc = await this.workspaceContents.getOrLoadMarkdownDocument(resource);
if (doc) {
await this.inFlightDiagnostics.trigger(doc.uri, async (token) => {
const state = await this.recomputeDiagnosticState(doc, token);
@ -540,19 +540,19 @@ export class DiagnosticComputer {
return;
}
const hrefDoc = await tryFindMdDocumentForLink({ kind: 'internal', path: path, fragment: '' }, this.workspaceContents);
if (!hrefDoc && !await this.workspaceContents.pathExists(path)) {
const resolvedHrefPath = await tryResolveLinkPath(path, this.workspaceContents);
if (!resolvedHrefPath) {
const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath);
for (const link of links) {
if (!this.isIgnoredLink(options, link.source.pathText)) {
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, pathErrorSeverity, link.source.pathText));
}
}
} else if (hrefDoc && typeof fragmentErrorSeverity !== 'undefined') {
} else if (typeof fragmentErrorSeverity !== 'undefined' && this.isMarkdownPath(resolvedHrefPath)) {
// Validate each of the links to headers in the file
const fragmentLinks = links.filter(x => x.fragment);
if (fragmentLinks.length) {
const toc = await this.tocProvider.get(hrefDoc.uri);
const toc = await this.tocProvider.get(resolvedHrefPath);
for (const link of fragmentLinks) {
if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) {
const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment);
@ -567,6 +567,10 @@ export class DiagnosticComputer {
return diagnostics;
}
private isMarkdownPath(resolvedHrefPath: vscode.Uri) {
return this.workspaceContents.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath);
}
private isIgnoredLink(options: DiagnosticOptions, link: string): boolean {
return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob));
}

View file

@ -8,6 +8,7 @@ import { MarkdownEngine } from '../markdownEngine';
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { Disposable } from '../util/dispose';
import { looksLikeMarkdownPath } from '../util/file';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref, MdLink, MdLinkComputer } from './documentLinkProvider';
import { MdWorkspaceInfoCache } from './workspaceCache';
@ -177,15 +178,15 @@ export class MdReferencesProvider extends Disposable {
return references;
}
const targetDoc = await tryFindMdDocumentForLink(sourceLink.href, this.workspaceContents);
const resolvedResource = await tryResolveLinkPath(sourceLink.href.path, this.workspaceContents);
if (token.isCancellationRequested) {
return [];
}
const references: MdReference[] = [];
if (targetDoc && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
const toc = await this.tocProvider.get(targetDoc.uri);
if (resolvedResource && this.isMarkdownPath(resolvedResource) && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
const toc = await this.tocProvider.get(resolvedResource);
const entry = toc.lookup(sourceLink.href.fragment);
if (entry) {
references.push({
@ -199,7 +200,7 @@ export class MdReferencesProvider extends Disposable {
}
for (const link of allLinksInWorkspace) {
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, targetDoc.uri)) {
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resolvedResource)) {
continue;
}
@ -215,12 +216,16 @@ export class MdReferencesProvider extends Disposable {
}
}
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
references.push(...this.findAllLinksToFile(targetDoc?.uri ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
references.push(...this.findAllLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
}
return references;
}
private isMarkdownPath(resolvedHrefPath: vscode.Uri) {
return this.workspaceContents.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath);
}
private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) {
return href.path.fsPath === targetDoc.fsPath
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath;
@ -310,16 +315,17 @@ export function registerReferencesSupport(
return vscode.languages.registerReferenceProvider(selector, new MdVsCodeReferencesProvider(referencesProvider));
}
export async function tryFindMdDocumentForLink(href: InternalHref, workspaceContents: MdWorkspaceContents): Promise<SkinnyTextDocument | undefined> {
const targetDoc = await workspaceContents.getMarkdownDocument(href.path);
if (targetDoc) {
return targetDoc;
export async function tryResolveLinkPath(originalUri: vscode.Uri, workspaceContents: MdWorkspaceContents): Promise<vscode.Uri | undefined> {
if (await workspaceContents.pathExists(originalUri)) {
return originalUri;
}
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
if (uri.Utils.extname(href.path) === '') {
const dotMdResource = href.path.with({ path: href.path.path + '.md' });
return workspaceContents.getMarkdownDocument(dotMdResource);
if (uri.Utils.extname(originalUri) === '') {
const dotMdResource = originalUri.with({ path: originalUri.path + '.md' });
if (await workspaceContents.pathExists(dotMdResource)) {
return dotMdResource;
}
}
return undefined;

View file

@ -11,7 +11,7 @@ import { Disposable } from '../util/dispose';
import { resolveDocumentLink } from '../util/openDocumentLink';
import { MdWorkspaceContents, SkinnyTextDocument } from '../workspaceContents';
import { InternalHref } from './documentLinkProvider';
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryFindMdDocumentForLink } from './references';
import { MdHeaderReference, MdLinkReference, MdReference, MdReferencesProvider, tryResolveLinkPath } from './references';
const localize = nls.loadMessageBundle();
@ -153,8 +153,7 @@ export class MdVsCodeRenameProvider extends Disposable implements vscode.RenameP
const edit = new vscode.WorkspaceEdit();
const fileRenames: MdFileRenameEdit[] = [];
const targetDoc = await tryFindMdDocumentForLink(triggerHref, this.workspaceContents);
const targetUri = targetDoc?.uri ?? triggerHref.path;
const targetUri = await tryResolveLinkPath(triggerHref.path, this.workspaceContents) ?? triggerHref.path;
const rawNewFilePath = resolveDocumentLink(newName, triggerDocument);
let resolvedNewFilePath = rawNewFilePath;

View file

@ -61,7 +61,7 @@ export class MdDocumentInfoCache<T> extends Disposable {
return existing;
}
const doc = await this.workspaceContents.getMarkdownDocument(resource);
const doc = await this.workspaceContents.getOrLoadMarkdownDocument(resource);
return doc && this.onDidChangeDocument(doc, true)?.value;
}

View file

@ -22,10 +22,14 @@ export class InMemoryWorkspaceMarkdownDocuments implements MdWorkspaceContents {
return Array.from(this._documents.values());
}
public async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
return this._documents.get(resource);
}
public hasMarkdownDocument(resolvedHrefPath: vscode.Uri): boolean {
return this._documents.has(resolvedHrefPath);
}
public async pathExists(resource: vscode.Uri): Promise<boolean> {
return this._documents.has(resource);
}

View file

@ -4,7 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as URI from 'vscode-uri';
const markdownFileExtensions = Object.freeze<string[]>([
'.md',
'.mkd',
'.mdwn',
'.mdown',
'.markdown',
'.markdn',
'.mdtxt',
'.mdtext',
'.workbook',
]);
export function isMarkdownFile(document: vscode.TextDocument) {
return document.languageId === 'markdown';
}
}
export function looksLikeMarkdownPath(resolvedHrefPath: vscode.Uri) {
return markdownFileExtensions.includes(URI.Utils.extname(resolvedHrefPath).toLowerCase());
}

View file

@ -32,15 +32,3 @@ export function getUriForLinkWithKnownExternalScheme(link: string): vscode.Uri |
export function isOfScheme(scheme: string, link: string): boolean {
return link.toLowerCase().startsWith(scheme);
}
export const MarkdownFileExtensions: readonly string[] = [
'.md',
'.mkd',
'.mdwn',
'.mdown',
'.markdown',
'.markdn',
'.mdtxt',
'.mdtext',
'.workbook',
];

View file

@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as URI from 'vscode-uri';
import { coalesce } from './util/arrays';
import { Disposable } from './util/dispose';
import { isMarkdownFile } from './util/file';
import { isMarkdownFile, looksLikeMarkdownPath } from './util/file';
import { InMemoryDocument } from './util/inMemoryDocument';
import { Limiter } from './util/limiter';
import { ResourceMap } from './util/resourceMap';
@ -42,7 +41,12 @@ export interface MdWorkspaceContents {
*/
getAllMarkdownDocuments(): Promise<Iterable<SkinnyTextDocument>>;
getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined>;
/**
* Check if a document already exists in the workspace contents.
*/
hasMarkdownDocument(resource: vscode.Uri): boolean;
getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined>;
pathExists(resource: vscode.Uri): Promise<boolean>;
@ -84,7 +88,7 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**');
const onDiskResults = await Promise.all(resources.map(resource => {
return limiter.queue(async () => {
const doc = await this.getMarkdownDocument(resource);
const doc = await this.getOrLoadMarkdownDocument(resource);
if (doc) {
foundFiles.add(doc.uri.toString());
}
@ -123,14 +127,14 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
this._register(this._watcher.onDidChange(async resource => {
this._documentCache.delete(resource);
const document = await this.getMarkdownDocument(resource);
const document = await this.getOrLoadMarkdownDocument(resource);
if (document) {
this._onDidChangeMarkdownDocumentEmitter.fire(document);
}
}));
this._register(this._watcher.onDidCreate(async resource => {
const document = await this.getMarkdownDocument(resource);
const document = await this.getOrLoadMarkdownDocument(resource);
if (document) {
this._onDidCreateMarkdownDocumentEmitter.fire(document);
}
@ -160,7 +164,7 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
return isMarkdownFile(doc) && doc.uri.scheme !== 'vscode-bulkeditpreview';
}
public async getMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise<SkinnyTextDocument | undefined> {
const existing = this._documentCache.get(resource);
if (existing) {
return existing;
@ -172,8 +176,7 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
return matchingDocument;
}
const ext = URI.Utils.extname(resource).toLowerCase();
if (ext !== '.md') {
if (!looksLikeMarkdownPath(resource)) {
return undefined;
}
@ -190,6 +193,10 @@ export class VsCodeMdWorkspaceContents extends Disposable implements MdWorkspace
}
}
public hasMarkdownDocument(resolvedHrefPath: vscode.Uri): boolean {
return this._documentCache.has(resolvedHrefPath);
}
public async pathExists(target: vscode.Uri): Promise<boolean> {
let targetResourceStat: vscode.FileStat | undefined;
try {