Dispose in markdown tests (#153345)

Updates the markdown tests to dispose of disposables created during the test
This commit is contained in:
Matt Bierner 2022-06-27 12:52:59 -07:00 committed by GitHub
parent 464c3dc728
commit da0f64881a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 652 additions and 510 deletions

View file

@ -10,17 +10,19 @@ import { MdVsCodeDefinitionProvider } from '../languageFeatures/definitions';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { joinLines, workspacePath } from './util';
import { joinLines, withStore, workspacePath } from './util';
function getDefinition(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
function getDefinition(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
const engine = createNewMarkdownEngine();
const referencesProvider = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const provider = new MdVsCodeDefinitionProvider(referencesProvider);
return provider.provideDefinition(doc, pos, noopToken);
}
@ -46,31 +48,33 @@ function assertDefinitionsEqual(actualDef: vscode.Definition, ...expectedDefs: {
}
suite('markdown: Go to definition', () => {
test('Should not return definition when on link text', async () => {
test('Should not return definition when on link text', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[ref](#abc)`,
`[ref]: http://example.com`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(doc, new vscode.Position(0, 1), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(0, 1), workspace);
assert.deepStrictEqual(defs, undefined);
});
}));
test('Should find definition links within file from link', async () => {
test('Should find definition links within file from link', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`, // trigger here
``,
`[abc]: https://example.com`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
}));
test('Should find definition links using shorthand', async () => {
test('Should find definition links using shorthand', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[ref]`, // trigger 1
@ -79,59 +83,62 @@ suite('markdown: Go to definition', () => {
``,
`[ref]: /Hello.md` // trigger 3
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
{
const defs = await getDefinition(doc, new vscode.Position(0, 2), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(0, 2), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
{
const defs = await getDefinition(doc, new vscode.Position(2, 7), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(2, 7), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
{
const defs = await getDefinition(doc, new vscode.Position(4, 2), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(4, 2), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 4 },
);
}
});
}));
test('Should find definition links within file from definition', async () => {
test('Should find definition links within file from definition', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`, // trigger here
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(doc, new vscode.Position(2, 3), new InMemoryMdWorkspace([doc]));
const defs = await getDefinition(store, doc, new vscode.Position(2, 3), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
}));
test('Should not find definition links across files', async () => {
test('Should not find definition links across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`,
));
const defs = await getDefinition(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(workspacePath('other.md'), joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com?bad`,
`[abc]: https://example.com?bad`
))
]));
const defs = await getDefinition(store, doc, new vscode.Position(0, 12), workspace);
assertDefinitionsEqual(defs!,
{ uri: docUri, line: 2 },
);
});
}));
});

View file

@ -11,14 +11,14 @@ import { MdLinkProvider } from '../languageFeatures/documentLinks';
import { MdReferencesProvider } from '../languageFeatures/references';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { disposeAll } from '../util/dispose';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { ResourceMap } from '../util/resourceMap';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { assertRangeEqual, joinLines, workspacePath } from './util';
import { assertRangeEqual, joinLines, withStore, workspacePath } from './util';
const defaultDiagnosticsOptions = Object.freeze<DiagnosticOptions>({
enabled: true,
@ -29,10 +29,10 @@ const defaultDiagnosticsOptions = Object.freeze<DiagnosticOptions>({
ignoreLinks: [],
});
async function getComputedDiagnostics(doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial<DiagnosticOptions> = {}): Promise<vscode.Diagnostic[]> {
async function getComputedDiagnostics(store: DisposableStore, doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial<DiagnosticOptions> = {}): Promise<vscode.Diagnostic[]> {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine, workspace, nulLogger);
const tocProvider = new MdTableOfContentsProvider(engine, workspace, nulLogger);
const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger));
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const computer = new DiagnosticComputer(workspace, linkProvider, tocProvider);
return (
await computer.getDiagnostics(doc, { ...defaultDiagnosticsOptions, ...options, }, noopToken)
@ -108,31 +108,33 @@ class MemoryDiagnosticReporter extends DiagnosticReporter {
suite('markdown: Diagnostic Computer', () => {
test('Should not return any diagnostics for empty document', async () => {
test('Should not return any diagnostics for empty document', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`text`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assert.deepStrictEqual(diagnostics, []);
});
}));
test('Should generate diagnostic for link to file that does not exist', async () => {
test('Should generate diagnostic for link to file that does not exist', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[bad](/no/such/file.md)`,
`[good](/doc.md)`,
`[good-ref]: /doc.md`,
`[bad-ref]: /no/such/file.md`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(0, 6, 0, 22),
new vscode.Range(3, 11, 3, 27),
]);
});
}));
test('Should generate diagnostics for links to header that does not exist in current file', async () => {
test('Should generate diagnostics for links to header that does not exist in current file', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[good](#good-header)`,
`# Good Header`,
@ -141,15 +143,16 @@ suite('markdown: Diagnostic Computer', () => {
`[good-ref]: #good-header`,
`[bad-ref]: #no-such-header`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(2, 6, 2, 21),
new vscode.Range(5, 11, 5, 26),
]);
});
}));
test('Should generate diagnostics for links to non-existent headers in other files', async () => {
test('Should generate diagnostics for links to non-existent headers in other files', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`# My header`,
`[good](#my-header)`,
@ -163,13 +166,13 @@ suite('markdown: Diagnostic Computer', () => {
`# Other header`,
));
const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1, doc2]));
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1, doc2]));
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(5, 14, 5, 35),
]);
});
}));
test('Should support links both with and without .md file extension', async () => {
test('Should support links both with and without .md file extension', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# My header`,
`[good](#my-header)`,
@ -178,188 +181,182 @@ suite('markdown: Diagnostic Computer', () => {
`[good](/doc#my-header)`,
`[good](doc#my-header)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should generate diagnostics for non-existent link reference', async () => {
test('Should generate diagnostics for non-existent link reference', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[good link][good]`,
`[bad link][no-such]`,
``,
`[good]: http://example.com`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(1, 11, 1, 18),
]);
});
}));
test('Should not generate diagnostics when validate is disabled', async () => {
test('Should not generate diagnostics when validate is disabled', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](#no-such-header)`,
`[text][no-such-ref]`,
));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri));
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should not generate diagnostics for email autolink', async () => {
test('Should not generate diagnostics for email autolink', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`a <user@example.com> c`,
));
const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1]));
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1]));
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should not generate diagnostics for html tag that looks like an autolink', async () => {
test('Should not generate diagnostics for html tag that looks like an autolink', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`a <tag>b</tag> c`,
`a <scope:tag>b</scope:tag> c`,
));
const diagnostics = await getComputedDiagnostics(doc1, new InMemoryMdWorkspace([doc1]));
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1]));
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should allow ignoring invalid file link using glob', async () => {
test('Should allow ignoring invalid file link using glob', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](/no-such-file)`,
`![img](/no-such-file)`,
`[text]: /no-such-file`,
));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] });
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should be able to disable fragment validation for external files', async () => {
test('Should be able to disable fragment validation for external files', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](/doc2.md#no-such)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Disabling own fragment validation should also disable path fragment validation by default', async () => {
test('Disabling own fragment validation should also disable path fragment validation by default', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[b](#no-head)`,
`![i](/doc2.md#no-such)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
{
const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore });
assertDiagnosticsEqual(diagnostics, []);
}
{
// But we should be able to override the default
const diagnostics = await getComputedDiagnostics(doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning });
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(1, 13, 1, 21),
]);
}
});
}));
test('ignoreLinks should allow skipping link to non-existent file', async () => {
test('ignoreLinks should allow skipping link to non-existent file', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](/no-such-file#header)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('ignoreLinks should not consider link fragment', async () => {
test('ignoreLinks should not consider link fragment', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](/no-such-file#header)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/no-such-file'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('ignoreLinks should support globs', async () => {
test('ignoreLinks should support globs', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](/images/aaa.png)`,
`![i](/images/sub/bbb.png)`,
`![i](/images/sub/sub2/ccc.png)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/images/**/*.png'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/images/**/*.png'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('ignoreLinks should support ignoring header', async () => {
test('ignoreLinks should support ignoring header', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](#no-such)`,
));
const workspace = new InMemoryMdWorkspace([doc1]);
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['#no-such'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['#no-such'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('ignoreLinks should support ignoring header in file', async () => {
test('ignoreLinks should support ignoring header in file', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](/doc2.md#no-such)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
{
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] });
assertDiagnosticsEqual(diagnostics, []);
}
{
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md#*'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#*'] });
assertDiagnosticsEqual(diagnostics, []);
}
});
}));
test('ignoreLinks should support ignore header links if file is ignored', async () => {
test('ignoreLinks should support ignore header links if file is ignored', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](/doc2.md#no-such)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should not detect checkboxes as invalid links', async () => {
test('Should not detect checkboxes as invalid links', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`- [x]`,
`- [X]`,
`- [ ]`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, { ignoreLinks: ['/doc2.md'] });
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] });
assertDiagnosticsEqual(diagnostics, []);
});
}));
test('Should detect invalid links with titles', async () => {
test('Should detect invalid links with titles', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[link](<no such.md> "text")`,
`[link](<no such.md> 'text')`,
@ -368,7 +365,9 @@ suite('markdown: Diagnostic Computer', () => {
`[link](no-such.md 'text')`,
`[link](no-such.md (text))`,
));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(diagnostics, [
new vscode.Range(0, 8, 0, 18),
new vscode.Range(1, 8, 1, 18),
@ -377,36 +376,36 @@ suite('markdown: Diagnostic Computer', () => {
new vscode.Range(4, 7, 4, 17),
new vscode.Range(5, 7, 5, 17),
]);
});
}));
test('Should generate diagnostics for non-existent header using file link to own file', async () => {
test('Should generate diagnostics for non-existent header using file link to own file', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines(
`[bad](doc.md#no-such)`,
`[bad](doc#no-such)`,
`[bad](/sub/doc.md#no-such)`,
`[bad](/sub/doc#no-such)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(doc, new InMemoryMdWorkspace([doc]));
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [
new vscode.Range(0, 12, 0, 20),
new vscode.Range(1, 9, 1, 17),
new vscode.Range(2, 17, 2, 25),
new vscode.Range(3, 14, 3, 22),
]);
});
}));
test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', async () => {
test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', withStore(async (store) => {
const doc1 = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines(
`[bad](doc.md#no-such)`,
`[bad](doc#no-such)`,
`[bad](/sub/doc.md#no-such)`,
`[bad](/sub/doc#no-such)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
const workspace = new InMemoryMdWorkspace([doc1]);
const diagnostics = await getComputedDiagnostics(doc1, workspace, {
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, {
validateFragmentLinks: DiagnosticLevel.ignore,
validateMarkdownFileLinkFragments: DiagnosticLevel.warning,
});
@ -416,31 +415,22 @@ suite('markdown: Diagnostic Computer', () => {
new vscode.Range(2, 17, 2, 25),
new vscode.Range(3, 14, 3, 22),
]);
});
}));
});
suite('Markdown: Diagnostics manager', () => {
const _disposables: vscode.Disposable[] = [];
setup(() => {
disposeAll(_disposables);
});
teardown(() => {
disposeAll(_disposables);
});
function createDiagnosticsManager(
store: DisposableStore,
workspace: IMdWorkspace,
configuration = new MemoryDiagnosticConfiguration({}),
reporter: DiagnosticReporter = new DiagnosticCollectionReporter(),
) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine, workspace, nulLogger);
const tocProvider = new MdTableOfContentsProvider(engine, workspace, nulLogger);
const referencesProvider = new MdReferencesProvider(engine, workspace, tocProvider, nulLogger);
const manager = new DiagnosticManager(
const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger));
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const manager = store.add(new DiagnosticManager(
workspace,
new DiagnosticComputer(workspace, linkProvider, tocProvider),
configuration,
@ -448,27 +438,26 @@ suite('Markdown: Diagnostics manager', () => {
referencesProvider,
tocProvider,
nulLogger,
0);
_disposables.push(linkProvider, tocProvider, referencesProvider, manager);
0));
return manager;
}
test('Changing enable/disable should recompute diagnostics', async () => {
test('Changing enable/disable should recompute diagnostics', withStore(async (store) => {
const doc1Uri = workspacePath('doc1.md');
const doc2Uri = workspacePath('doc2.md');
const workspace = new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(doc1Uri, joinLines(
`[text](#no-such-1)`,
)),
new InMemoryDocument(doc2Uri, joinLines(
`[text](#no-such-2)`,
))
]);
]));
const reporter = new MemoryDiagnosticReporter();
const reporter = store.add(new MemoryDiagnosticReporter());
const config = new MemoryDiagnosticConfiguration({ enabled: true });
const manager = createDiagnosticsManager(workspace, config, reporter);
const manager = createDiagnosticsManager(store, workspace, config, reporter);
await manager.ready;
// Check initial state (Enabled)
@ -495,9 +484,9 @@ suite('Markdown: Diagnostics manager', () => {
assertDiagnosticsEqual(reporter.get(doc2Uri), [
new vscode.Range(0, 7, 0, 17),
]);
});
}));
test('Should revalidate linked files when header changes', async () => {
test('Should revalidate linked files when header changes', withStore(async (store) => {
const doc1Uri = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(doc1Uri, joinLines(
`[text](#no-such)`,
@ -509,11 +498,10 @@ suite('Markdown: Diagnostics manager', () => {
`[text](#header)`,
`[text](#no-such-2)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const reporter = store.add(new MemoryDiagnosticReporter());
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
const reporter = new MemoryDiagnosticReporter();
const manager = createDiagnosticsManager(workspace, new MemoryDiagnosticConfiguration({}), reporter);
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
await manager.ready;
// Check initial state
@ -553,9 +541,9 @@ suite('Markdown: Diagnostics manager', () => {
assertDiagnosticsEqual(reporter.get(doc2Uri), [
new vscode.Range(2, 7, 2, 17),
]);
});
}));
test('Should revalidate linked files when file is deleted/created', async () => {
test('Should revalidate linked files when file is deleted/created', withStore(async (store) => {
const doc1Uri = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(doc1Uri, joinLines(
`[text](/doc2.md)`,
@ -565,11 +553,10 @@ suite('Markdown: Diagnostics manager', () => {
const doc2 = new InMemoryDocument(doc2Uri, joinLines(
`# Header`
));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const reporter = store.add(new MemoryDiagnosticReporter());
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
const reporter = new MemoryDiagnosticReporter();
const manager = createDiagnosticsManager(workspace, new MemoryDiagnosticConfiguration({}), reporter);
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
await manager.ready;
// Check initial state
@ -589,5 +576,5 @@ suite('Markdown: Diagnostics manager', () => {
workspace.createDocument(doc2);
await reporter.waitPendingWork();
assertDiagnosticsEqual(reporter.get(doc1Uri), []);
});
}));
});

View file

@ -7,92 +7,115 @@ import * as assert from 'assert';
import 'mocha';
import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbols';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { workspacePath } from './util';
import { joinLines, withStore, workspacePath } from './util';
function getSymbolsForFile(fileContents: string) {
function getSymbolsForFile(store: DisposableStore, fileContents: string) {
const doc = new InMemoryDocument(workspacePath('test.md'), fileContents);
const workspace = new InMemoryMdWorkspace([doc]);
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const engine = createNewMarkdownEngine();
const provider = new MdDocumentSymbolProvider(new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const provider = new MdDocumentSymbolProvider(tocProvider, nulLogger);
return provider.provideDocumentSymbols(doc);
}
suite('markdown.DocumentSymbolProvider', () => {
test('Should not return anything for empty document', async () => {
const symbols = await getSymbolsForFile('');
suite('Markdown: DocumentSymbolProvider', () => {
test('Should not return anything for empty document', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, '');
assert.strictEqual(symbols.length, 0);
});
}));
test('Should not return anything for document with no headers', async () => {
const symbols = await getSymbolsForFile('a\na');
test('Should not return anything for document with no headers', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`a`,
`a`,
));
assert.strictEqual(symbols.length, 0);
});
}));
test('Should not return anything for document with # but no real headers', async () => {
const symbols = await getSymbolsForFile('a#a\na#');
test('Should not return anything for document with # but no real headers', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`a#a`,
`a#`,
));
assert.strictEqual(symbols.length, 0);
});
}));
test('Should return single symbol for single header', async () => {
const symbols = await getSymbolsForFile('# h');
test('Should return single symbol for single header', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, '# h');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
});
}));
test('Should not care about symbol level for single header', async () => {
const symbols = await getSymbolsForFile('### h');
test('Should not care about symbol level for single header', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, '### h');
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '### h');
});
}));
test('Should put symbols of same level in flat list', async () => {
const symbols = await getSymbolsForFile('## h\n## h2');
test('Should put symbols of same level in flat list', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`## h`,
`## h2`,
));
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '## h');
assert.strictEqual(symbols[1].name, '## h2');
});
}));
test('Should nest symbol of level - 1 under parent', async () => {
const symbols = await getSymbolsForFile('# h\n## h2\n## h3');
test('Should nest symbol of level - 1 under parent', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`# h`,
`## h2`,
`## h3`,
));
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 2);
assert.strictEqual(symbols[0].children[0].name, '## h2');
assert.strictEqual(symbols[0].children[1].name, '## h3');
});
}));
test('Should nest symbol of level - n under parent', async () => {
const symbols = await getSymbolsForFile('# h\n#### h2');
test('Should nest symbol of level - n under parent', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`# h`,
`#### h2`,
));
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 1);
assert.strictEqual(symbols[0].children[0].name, '#### h2');
});
}));
test('Should flatten children where lower level occurs first', async () => {
const symbols = await getSymbolsForFile('# h\n### h2\n## h3');
test('Should flatten children where lower level occurs first', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`# h`,
`### h2`,
`## h3`,
));
assert.strictEqual(symbols.length, 1);
assert.strictEqual(symbols[0].name, '# h');
assert.strictEqual(symbols[0].children.length, 2);
assert.strictEqual(symbols[0].children[0].name, '### h2');
assert.strictEqual(symbols[0].children[1].name, '## h3');
});
}));
test('Should handle line separator in file. Issue #63749', async () => {
const symbols = await getSymbolsForFile(`# A
- foo
# B
- bar`);
test('Should handle line separator in file. Issue #63749', withStore(async (store) => {
const symbols = await getSymbolsForFile(store, joinLines(
`# A`,
`- foo`,
``,
`# B`,
`- bar`,
));
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '# A');
assert.strictEqual(symbols[1].name, '# B');
});
}));
});

View file

@ -9,17 +9,19 @@ import * as vscode from 'vscode';
import { MdReference, MdReferencesProvider } from '../languageFeatures/references';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { joinLines, workspacePath } from './util';
import { joinLines, withStore, workspacePath } from './util';
function getFileReferences(resource: vscode.Uri, workspace: IMdWorkspace) {
function getFileReferences(store: DisposableStore, resource: vscode.Uri, workspace: IMdWorkspace) {
const engine = createNewMarkdownEngine();
const computer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
return computer.getAllReferencesToFile(resource, noopToken);
}
@ -37,82 +39,82 @@ function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRe
suite('markdown: find file references', () => {
test('Should find basic references', async () => {
test('Should find basic references', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md)`,
`[link 2](./other.md)`,
`[link 2](./other.md)`
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md)`,
`post`,
`post`
)),
]));
assertReferencesEqual(refs!,
const refs = await getFileReferences(store, otherUri, workspace);
assertReferencesEqual(refs,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
);
});
}));
test('Should find references with and without file extensions', async () => {
test('Should find references with and without file extensions', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md)`,
`[link 2](./other)`,
`[link 2](./other)`
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md)`,
`[link 4](./other)`,
`post`,
`post`
)),
]));
assertReferencesEqual(refs!,
const refs = await getFileReferences(store, otherUri, workspace);
assertReferencesEqual(refs,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
{ uri: otherUri, line: 3 },
);
});
}));
test('Should find references with headers on links', async () => {
test('Should find references with headers on links', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const refs = await getFileReferences(otherUri, new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(docUri, joinLines(
`# header`,
`[link 1](./other.md#sub-bla)`,
`[link 2](./other#sub-bla)`,
`[link 2](./other#sub-bla)`
)),
new InMemoryDocument(otherUri, joinLines(
`# header`,
`pre`,
`[link 3](./other.md#sub-bla)`,
`[link 4](./other#sub-bla)`,
`post`,
`post`
)),
]));
assertReferencesEqual(refs!,
const refs = await getFileReferences(store, otherUri, workspace);
assertReferencesEqual(refs,
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
{ uri: otherUri, line: 2 },
{ uri: otherUri, line: 3 },
);
});
}));
});

View file

@ -8,32 +8,43 @@ import 'mocha';
import * as vscode from 'vscode';
import { MdFoldingProvider } from '../languageFeatures/folding';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { joinLines } from './util';
import { joinLines, withStore } from './util';
const testFileName = vscode.Uri.file('test.md');
suite('markdown.FoldingProvider', () => {
test('Should not return anything for empty document', async () => {
const folds = await getFoldsForDocument(``);
assert.strictEqual(folds.length, 0);
});
async function getFoldsForDocument(store: DisposableStore, contents: string) {
const doc = new InMemoryDocument(testFileName, contents);
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const engine = createNewMarkdownEngine();
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const provider = new MdFoldingProvider(engine, tocProvider);
return provider.provideFoldingRanges(doc, {}, noopToken);
}
test('Should not return anything for document without headers', async () => {
const folds = await getFoldsForDocument(joinLines(
suite('markdown.FoldingProvider', () => {
test('Should not return anything for empty document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, ``);
assert.strictEqual(folds.length, 0);
}));
test('Should not return anything for document without headers', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`**b** afas`,
`a#b`,
`a`,
));
assert.strictEqual(folds.length, 0);
});
}));
test('Should fold from header to end of document', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold from header to end of document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`# b`,
`c`,
@ -43,10 +54,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
}));
test('Should leave single newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should leave single newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
@ -58,10 +69,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
});
}));
test('Should collapse multiple newlines to single newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should collapse multiple newlines to single newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
@ -75,10 +86,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 4);
});
}));
test('Should not collapse if there is no newline before next header', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should not collapse if there is no newline before next header', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
``,
`# a`,
`x`,
@ -89,10 +100,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 2);
});
}));
test('Should fold nested <!-- #region --> markers', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold nested <!-- #region --> markers', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`<!-- #region -->`,
`b`,
@ -116,10 +127,10 @@ suite('markdown.FoldingProvider', () => {
assert.strictEqual(first.end, 5);
assert.strictEqual(second.start, 7);
assert.strictEqual(second.end, 9);
});
}));
test('Should fold from list to end of document', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold from list to end of document', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`- b`,
`c`,
@ -129,10 +140,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
}));
test('lists folds should span multiple lines of content', async () => {
const folds = await getFoldsForDocument(joinLines(
test('lists folds should span multiple lines of content', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`a`,
`- This list item\n spans multiple\n lines.`,
));
@ -140,10 +151,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
}));
test('List should leave single blankline before new element', async () => {
const folds = await getFoldsForDocument(joinLines(
test('List should leave single blankline before new element', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`- a`,
`a`,
``,
@ -154,10 +165,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
});
}));
test('Should fold fenced code blocks', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold fenced code blocks', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`~~~ts`,
`a`,
`~~~`,
@ -167,10 +178,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 0);
assert.strictEqual(firstFold.end, 2);
});
}));
test('Should fold fenced code blocks with yaml front matter', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold fenced code blocks with yaml front matter', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`---`,
`title: bla`,
`---`,
@ -188,10 +199,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 4);
assert.strictEqual(firstFold.end, 6);
});
}));
test('Should fold html blocks', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold html blocks', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`x`,
`<div>`,
` fa`,
@ -201,10 +212,10 @@ suite('markdown.FoldingProvider', () => {
const firstFold = folds[0];
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
});
}));
test('Should fold html block comments', async () => {
const folds = await getFoldsForDocument(joinLines(
test('Should fold html block comments', withStore(async (store) => {
const folds = await getFoldsForDocument(store, joinLines(
`x`,
`<!--`,
`fa`,
@ -215,14 +226,5 @@ suite('markdown.FoldingProvider', () => {
assert.strictEqual(firstFold.start, 1);
assert.strictEqual(firstFold.end, 3);
assert.strictEqual(firstFold.kind, vscode.FoldingRangeKind.Comment);
});
}));
});
async function getFoldsForDocument(contents: string) {
const doc = new InMemoryDocument(testFileName, contents);
const workspace = new InMemoryMdWorkspace([doc]);
const engine = createNewMarkdownEngine();
const provider = new MdFoldingProvider(engine, new MdTableOfContentsProvider(engine, workspace, nulLogger));
return await provider.provideFoldingRanges(doc, {}, new vscode.CancellationTokenSource().token);
}

View file

@ -7,14 +7,16 @@ import * as assert from 'assert';
import * as path from 'path';
import * as vscode from 'vscode';
import { ITextDocument } from '../types/textDocument';
import { Disposable } from '../util/dispose';
import { ResourceMap } from '../util/resourceMap';
import { IMdWorkspace } from '../workspace';
export class InMemoryMdWorkspace implements IMdWorkspace {
export class InMemoryMdWorkspace extends Disposable implements IMdWorkspace {
private readonly _documents = new ResourceMap<ITextDocument>(uri => uri.fsPath);
constructor(documents: ITextDocument[]) {
super();
for (const doc of documents) {
this._documents.set(doc.uri, doc);
}
@ -49,13 +51,13 @@ export class InMemoryMdWorkspace implements IMdWorkspace {
return Array.from(files.entries());
}
private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter<ITextDocument>();
private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<ITextDocument>());
public onDidChangeMarkdownDocument = this._onDidChangeMarkdownDocumentEmitter.event;
private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter<ITextDocument>();
private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<ITextDocument>());
public onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocumentEmitter.event;
private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter<vscode.Uri>();
private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter<vscode.Uri>());
public onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocumentEmitter.event;
public updateDocument(document: ITextDocument) {

View file

@ -9,17 +9,19 @@ import * as vscode from 'vscode';
import { MdReferencesProvider, MdVsCodeReferencesProvider } from '../languageFeatures/references';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { joinLines, workspacePath } from './util';
import { joinLines, withStore, workspacePath } from './util';
function getReferences(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
function getReferences(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace) {
const engine = createNewMarkdownEngine();
const computer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const computer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const provider = new MdVsCodeReferencesProvider(computer);
return provider.provideReferences(doc, pos, { includeDeclaration: true }, noopToken);
}
@ -42,26 +44,27 @@ function assertReferencesEqual(actualRefs: readonly vscode.Location[], ...expect
}
}
suite('markdown: find all references', () => {
test('Should not return references when not on header or link', async () => {
suite('Markdown: Find all references', () => {
test('Should not return references when not on header or link', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# abc`,
``,
`[link 1](#abc)`,
`text`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
{
const refs = await getReferences(doc, new vscode.Position(1, 0), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(1, 0), workspace);
assert.deepStrictEqual(refs, []);
}
{
const refs = await getReferences(doc, new vscode.Position(3, 2), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(3, 2), workspace);
assert.deepStrictEqual(refs, []);
}
});
}));
test('Should find references from header within same file', async () => {
test('Should find references from header within same file', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
@ -70,55 +73,61 @@ suite('markdown: find all references', () => {
`[not link](#noabc)`,
`[link 2](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 2 },
{ uri, line: 4 },
);
});
}));
test('Should not return references when on link text', async () => {
test('Should not return references when on link text', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`[ref](#abc)`,
`[ref]: http://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 1), new InMemoryMdWorkspace([doc]));
assert.deepStrictEqual(refs, []);
});
const workspace = store.add(new InMemoryMdWorkspace([doc]));
test('Should find references using normalized slug', async () => {
const refs = await getReferences(store, doc, new vscode.Position(0, 1), workspace);
assert.deepStrictEqual(refs, []);
}));
test('Should find references using normalized slug', withStore(async (store) => {
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
`# a B c`,
`[simple](#a-b-c)`,
`[start underscore](#_a-b-c)`,
`[different case](#a-B-C)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
{
// Trigger header
const refs = await getReferences(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 0), workspace);
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 1
const refs = await getReferences(doc, new vscode.Position(1, 12), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(1, 12), workspace);
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 2
const refs = await getReferences(doc, new vscode.Position(2, 24), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(2, 24), workspace);
assert.deepStrictEqual(refs!.length, 4);
}
{
// Trigger on line 3
const refs = await getReferences(doc, new vscode.Position(3, 20), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(3, 20), workspace);
assert.deepStrictEqual(refs!.length, 4);
}
});
}));
test('Should find references from header across files', async () => {
test('Should find references from header across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
@ -128,59 +137,64 @@ suite('markdown: find all references', () => {
``,
`[link 1](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#abc)`,
`[not link](/doc.md#abz)`,
`[link](/doc.md#abc)`,
`[link](/doc.md#abc)`
)),
new InMemoryDocument(other2Uri, joinLines(
`[not link](#abc)`,
`[not link](./doc.md#abz)`,
`[link](./doc.md#abc)`,
`[link](./doc.md#abc)`
))
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
{ uri: other1Uri, line: 2 },
{ uri: other2Uri, line: 2 },
);
});
}));
test('Should find references from header to link definitions ', async () => {
test('Should find references from header to link definitions ', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
``,
`[bla]: #abc`
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 3), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 3), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
);
});
}));
test('Should find header references from link definition', async () => {
test('Should find header references from link definition', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# A b C`,
`[text][bla]`,
`[bla]: #a-b-c`, // trigger here
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(2, 9), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(2, 9), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
);
});
}));
test('Should find references from link within same file', async () => {
test('Should find references from link within same file', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`,
@ -189,16 +203,17 @@ suite('markdown: find all references', () => {
`[not link](#noabc)`,
`[link 2](#abc)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 }, // Header definition
{ uri, line: 2 },
{ uri, line: 4 },
);
});
}));
test('Should find references from link across files', async () => {
test('Should find references from link across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
const other2Uri = workspacePath('other2.md');
@ -208,21 +223,22 @@ suite('markdown: find all references', () => {
``,
`[link 1](#abc)`,
));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#abc)`,
`[not link](/doc.md#abz)`,
`[with ext](/doc.md#abc)`,
`[without ext](/doc#abc)`,
`[without ext](/doc#abc)`
)),
new InMemoryDocument(other2Uri, joinLines(
`[not link](#abc)`,
`[not link](./doc.md#abz)`,
`[link](./doc.md#abc)`,
`[link](./doc.md#abc)`
))
]));
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
@ -230,9 +246,9 @@ suite('markdown: find all references', () => {
{ uri: other1Uri, line: 3 }, // Other without ext
{ uri: other2Uri, line: 2 }, // Other2
);
});
}));
test('Should find references without requiring file extensions', async () => {
test('Should find references without requiring file extensions', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('other.md');
@ -241,7 +257,7 @@ suite('markdown: find all references', () => {
``,
`[link 1](#a-b-c)`,
));
const refs = await getReferences(doc, new vscode.Position(2, 10), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(other1Uri, joinLines(
`[not link](#a-b-c)`,
@ -249,10 +265,11 @@ suite('markdown: find all references', () => {
`[with ext](/doc.md#a-b-c)`,
`[without ext](/doc#a-b-c)`,
`[rel with ext](./doc.md#a-b-c)`,
`[rel without ext](./doc#a-b-c)`,
`[rel without ext](./doc#a-b-c)`
)),
]));
const refs = await getReferences(store, doc, new vscode.Position(2, 10), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
@ -261,9 +278,9 @@ suite('markdown: find all references', () => {
{ uri: other1Uri, line: 4 }, // Other relative link with ext
{ uri: other1Uri, line: 5 }, // Other relative link without ext
);
});
}));
test('Should find references from link across files when triggered on link without file extension', async () => {
test('Should find references from link across files when triggered on link without file extension', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
@ -272,23 +289,24 @@ suite('markdown: find all references', () => {
`[without ext](./sub/other.md#header)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 23), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(other1Uri, joinLines(
`pre`,
`# header`,
`post`,
`post`
)),
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 23), workspace);
assertReferencesEqual(refs!,
{ uri: other1Uri, line: 1 }, // Header definition
{ uri: docUri, line: 0 },
{ uri: docUri, line: 1 },
);
});
}));
test('Should include header references when triggered on file link', async () => {
test('Should include header references when triggered on file link', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('sub', 'other.md');
@ -297,24 +315,24 @@ suite('markdown: find all references', () => {
`[with ext](./sub/other#header)`,
`[without ext](./sub/other.md#no-such-header)`,
));
const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(otherUri, joinLines(
`pre`,
`# header`, // Definition should not be included since we triggered on a file link
`post`,
`# header`,
`post`
)),
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 1 },
{ uri: docUri, line: 2 },
);
});
}));
test('Should not include refs from other file to own header', async () => {
test('Should not include refs from other file to own header', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const otherUri = workspacePath('sub', 'other.md');
@ -322,7 +340,7 @@ suite('markdown: find all references', () => {
`[other](./sub/other)`, // trigger here
));
const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(otherUri, joinLines(
`# header`, // Definition should not be included since we triggered on a file link
@ -330,28 +348,30 @@ suite('markdown: find all references', () => {
)),
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 15), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
);
});
}));
test('Should find explicit references to own file ', async () => {
test('Should find explicit references to own file ', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[bare](doc.md)`, // trigger here
`[rel](./doc.md)`,
`[abs](/doc.md)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 1 },
{ uri, line: 2 },
);
});
}));
test('Should support finding references to http uri', async () => {
test('Should support finding references to http uri', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com)`,
@ -359,16 +379,17 @@ suite('markdown: find all references', () => {
`[2](http://example.com)`,
`[3]: http://example.com`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 2 },
{ uri, line: 3 },
);
});
}));
test('Should consider authority, scheme and paths when finding references to http uri', async () => {
test('Should consider authority, scheme and paths when finding references to http uri', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com/cat)`,
@ -379,50 +400,53 @@ suite('markdown: find all references', () => {
`[6](http://other.com/cat)`,
`[7](https://example.com/cat)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 4 },
);
});
}));
test('Should support finding references to http uri across files', async () => {
test('Should support finding references to http uri across files', withStore(async (store) => {
const uri1 = workspacePath('doc.md');
const uri2 = workspacePath('doc2.md');
const doc = new InMemoryDocument(uri1, joinLines(
`[1](http://example.com)`,
`[3]: http://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(uri2, joinLines(
`[other](http://example.com)`,
`[other](http://example.com)`
))
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
assertReferencesEqual(refs!,
{ uri: uri1, line: 0 },
{ uri: uri1, line: 1 },
{ uri: uri2, line: 0 },
);
});
}));
test('Should support finding references to autolinked http links', async () => {
test('Should support finding references to autolinked http links', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[1](http://example.com)`,
`<http://example.com>`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 13), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 13), workspace);
assertReferencesEqual(refs!,
{ uri, line: 0 },
{ uri, line: 1 },
);
});
}));
test('Should distinguish between references to file and to header within file', async () => {
test('Should distinguish between references to file and to header within file', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const other1Uri = workspacePath('sub', 'other.md');
@ -435,13 +459,14 @@ suite('markdown: find all references', () => {
`[link](/doc.md#abc)`,
`[link no text](/doc#abc)`,
));
const workspace = new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
otherDoc,
]);
]));
{
// Check refs to header fragment
const headerRefs = await getReferences(otherDoc, new vscode.Position(0, 16), workspace);
const headerRefs = await getReferences(store, otherDoc, new vscode.Position(0, 16), workspace);
assertReferencesEqual(headerRefs!,
{ uri: docUri, line: 0 }, // Header definition
{ uri: docUri, line: 2 },
@ -451,7 +476,7 @@ suite('markdown: find all references', () => {
}
{
// Check refs to file itself from link with ext
const fileRefs = await getReferences(otherDoc, new vscode.Position(0, 9), workspace);
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(0, 9), workspace);
assertReferencesEqual(fileRefs!,
{ uri: other1Uri, line: 0, endCharacter: 14 },
{ uri: other1Uri, line: 1, endCharacter: 19 },
@ -459,15 +484,15 @@ suite('markdown: find all references', () => {
}
{
// Check refs to file itself from link without ext
const fileRefs = await getReferences(otherDoc, new vscode.Position(1, 17), workspace);
const fileRefs = await getReferences(store, otherDoc, new vscode.Position(1, 17), workspace);
assertReferencesEqual(fileRefs!,
{ uri: other1Uri, line: 0 },
{ uri: other1Uri, line: 1 },
);
}
});
}));
test('Should support finding references to unknown file', async () => {
test('Should support finding references to unknown file', withStore(async (store) => {
const uri1 = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![img](/images/more/image.png)`,
@ -480,17 +505,18 @@ suite('markdown: find all references', () => {
`![img](/images/more/image.png)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const refs = await getReferences(doc1, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc1, doc2]));
const refs = await getReferences(store, doc1, new vscode.Position(0, 10), workspace);
assertReferencesEqual(refs!,
{ uri: uri1, line: 0 },
{ uri: uri1, line: 2 },
{ uri: uri2, line: 0 },
);
});
}));
suite('Reference links', () => {
test('Should find reference links within file from link', async () => {
test('Should find reference links within file from link', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`, // trigger here
@ -498,14 +524,16 @@ suite('markdown: find all references', () => {
`[abc]: https://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
}));
test('Should find reference links using shorthand', async () => {
test('Should find reference links using shorthand', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[ref]`, // trigger 1
@ -514,9 +542,10 @@ suite('markdown: find all references', () => {
``,
`[ref]: /Hello.md` // trigger 3
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
{
const refs = await getReferences(doc, new vscode.Position(0, 2), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 2), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
@ -524,7 +553,7 @@ suite('markdown: find all references', () => {
);
}
{
const refs = await getReferences(doc, new vscode.Position(2, 7), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(2, 7), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
@ -532,31 +561,32 @@ suite('markdown: find all references', () => {
);
}
{
const refs = await getReferences(doc, new vscode.Position(4, 2), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(4, 2), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
{ uri: docUri, line: 4 },
);
}
});
}));
test('Should find reference links within file from definition', async () => {
test('Should find reference links within file from definition', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com`, // trigger here
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(2, 3), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(2, 3), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
}));
test('Should not find reference links across files', async () => {
test('Should not find reference links across files', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`[link 1][abc]`,
@ -564,21 +594,23 @@ suite('markdown: find all references', () => {
`[abc]: https://example.com`,
));
const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(workspacePath('other.md'), joinLines(
`[link 1][abc]`,
``,
`[abc]: https://example.com?bad`,
`[abc]: https://example.com?bad`
))
]));
const refs = await getReferences(store, doc, new vscode.Position(0, 12), workspace);
assertReferencesEqual(refs!,
{ uri: docUri, line: 0 },
{ uri: docUri, line: 2 },
);
});
}));
test('Should not consider checkboxes as reference links', async () => {
test('Should not consider checkboxes as reference links', withStore(async (store) => {
const docUri = workspacePath('doc.md');
const doc = new InMemoryDocument(docUri, joinLines(
`- [x]`,
@ -587,9 +619,10 @@ suite('markdown: find all references', () => {
``,
`[x]: https://example.com`
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const refs = await getReferences(doc, new vscode.Position(0, 4), new InMemoryMdWorkspace([doc]));
const refs = await getReferences(store, doc, new vscode.Position(0, 4), workspace);
assert.strictEqual(refs?.length!, 0);
});
}));
});
});

View file

@ -11,31 +11,34 @@ import { MdVsCodeRenameProvider, MdWorkspaceEdit } from '../languageFeatures/ren
import { githubSlugifier } from '../slugify';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { noopToken } from '../util/cancellation';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { assertRangeEqual, joinLines, workspacePath } from './util';
import { assertRangeEqual, joinLines, withStore, workspacePath } from './util';
/**
* Get prepare rename info.
*/
function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
function prepareRename(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, workspace: IMdWorkspace): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
const engine = createNewMarkdownEngine();
const referenceComputer = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const renameProvider = new MdVsCodeRenameProvider(workspace, referenceComputer, githubSlugifier);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const referenceComputer = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referenceComputer, githubSlugifier));
return renameProvider.prepareRename(doc, pos, noopToken);
}
/**
* Get all the edits for the rename.
*/
function getRenameEdits(doc: InMemoryDocument, pos: vscode.Position, newName: string, workspace: IMdWorkspace): Promise<MdWorkspaceEdit | undefined> {
function getRenameEdits(store: DisposableStore, doc: InMemoryDocument, pos: vscode.Position, newName: string, workspace: IMdWorkspace): Promise<MdWorkspaceEdit | undefined> {
const engine = createNewMarkdownEngine();
const referencesProvider = new MdReferencesProvider(engine, workspace, new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
const renameProvider = new MdVsCodeRenameProvider(workspace, referencesProvider, githubSlugifier);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
const renameProvider = store.add(new MdVsCodeRenameProvider(workspace, referencesProvider, githubSlugifier));
return renameProvider.provideRenameEditsImpl(doc, pos, newName, noopToken);
}
@ -91,73 +94,78 @@ suite('markdown: rename', () => {
await vscode.extensions.getExtension('vscode.markdown-language-features')!.activate();
});
test('Rename on header should not include leading #', async () => {
test('Rename on header should not include leading #', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# abc`
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace);
assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 2, 0, 5), 'New Header')
]
});
});
}));
test('Rename on header should include leading or trailing #s', async () => {
test('Rename on header should include leading or trailing #s', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### abc ###`
));
const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(0, 0), workspace);
assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 7), 'New Header')
]
});
});
}));
test('Rename on header should pick up links in doc', async () => {
test('Rename on header should pick up links in doc', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`, // rename here
`[text](#a-b-c)`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
});
});
}));
test('Rename on link should use slug for link', async () => {
test('Rename on link should use slug for link', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
`[text](#a-b-c)`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'),
]
});
});
}));
test('Rename on link definition should work', async () => {
test('Rename on link definition should work', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### A b C`,
@ -165,7 +173,8 @@ suite('markdown: rename', () => {
`[ref]: #a-b-c`// rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "New Header", new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'),
@ -173,9 +182,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(2, 8, 2, 13), 'new-header'),
]
});
});
}));
test('Rename on header should pick up links across files', async () => {
test('Rename on header should pick up links across files', withStore(async (store) => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
@ -183,7 +192,7 @@ suite('markdown: rename', () => {
`[text](#a-b-c)`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 0), "New Header", new InMemoryMdWorkspace([
doc,
new InMemoryDocument(otherUri, joinLines(
`[text](#a-b-c)`, // Should not find this
@ -202,9 +211,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
]
});
});
}));
test('Rename on link should pick up links across files', async () => {
test('Rename on link should pick up links across files', withStore(async (store) => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
@ -212,7 +221,7 @@ suite('markdown: rename', () => {
`[text](#a-b-c)`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "New Header", new InMemoryMdWorkspace([
doc,
new InMemoryDocument(otherUri, joinLines(
`[text](#a-b-c)`, // Should not find this
@ -231,9 +240,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'),
]
});
});
}));
test('Rename on link in other file should pick up all refs', async () => {
test('Rename on link in other file should pick up all refs', withStore(async (store) => {
const uri = workspacePath('doc.md');
const otherUri = workspacePath('other.md');
const doc = new InMemoryDocument(uri, joinLines(
@ -263,7 +272,7 @@ suite('markdown: rename', () => {
{
// Rename on header with file extension
const edit = await getRenameEdits(otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryMdWorkspace([
const edit = await getRenameEdits(store, otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryMdWorkspace([
doc,
otherDoc
]));
@ -271,15 +280,15 @@ suite('markdown: rename', () => {
}
{
// Rename on header without extension
const edit = await getRenameEdits(otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryMdWorkspace([
const edit = await getRenameEdits(store, otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryMdWorkspace([
doc,
otherDoc
]));
assertEditsEqual(edit!, ...expectedEdits);
}
});
}));
test('Rename on reference should rename references and definition', async () => {
test('Rename on reference should rename references and definition', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text][ref]`, // rename here
@ -288,7 +297,8 @@ suite('markdown: rename', () => {
`[ref]: https://example.com`,
));
const edit = await getRenameEdits(doc, new vscode.Position(0, 8), "new ref", new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 8), "new ref", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
@ -296,9 +306,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
]
});
});
}));
test('Rename on definition should rename references and definitions', async () => {
test('Rename on definition should rename references and definitions', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text][ref]`,
@ -307,7 +317,8 @@ suite('markdown: rename', () => {
`[ref]: https://example.com`, // rename here
));
const edit = await getRenameEdits(doc, new vscode.Position(3, 3), "new ref", new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(3, 3), "new ref", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 7, 0, 10), 'new ref'),
@ -315,9 +326,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(3, 1, 3, 4), 'new ref'),
]
});
});
}));
test('Rename on definition entry should rename header and references', async () => {
test('Rename on definition entry should rename header and references', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# a B c`,
@ -325,12 +336,13 @@ suite('markdown: rename', () => {
`[direct](#a-b-c)`,
`[ref]: #a-b-c`, // rename here
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const preparedInfo = await prepareRename(doc, new vscode.Position(3, 10), new InMemoryMdWorkspace([doc]));
const preparedInfo = await prepareRename(store, doc, new vscode.Position(3, 10), workspace);
assert.strictEqual(preparedInfo!.placeholder, 'a B c');
assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13));
const edit = await getRenameEdits(doc, new vscode.Position(3, 10), "x Y z", new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(3, 10), "x Y z", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'),
@ -338,50 +350,54 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'),
]
});
});
}));
test('Rename should not be supported on link text', async () => {
test('Rename should not be supported on link text', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`# Header`,
`[text](#header)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryMdWorkspace([doc])));
});
await assert.rejects(prepareRename(store, doc, new vscode.Position(1, 2), workspace));
}));
test('Path rename should use file path as range', async () => {
test('Path rename should use file path as range', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md)`,
`[ref]: ./doc.md`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
assert.strictEqual(info!.placeholder, './doc.md');
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
});
}));
test('Path rename\'s range should excludes fragment', async () => {
test('Path rename\'s range should excludes fragment', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md#some-header)`,
`[ref]: ./doc.md#some-header`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
assert.strictEqual(info!.placeholder, './doc.md');
assertRangeEqual(info!.range, new vscode.Range(0, 7, 0, 15));
});
}));
test('Path rename should update file and all refs', async () => {
test('Path rename should update file and all refs', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](./doc.md)`,
`[ref]: ./doc.md`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), './sub/newDoc.md', new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), './sub/newDoc.md', workspace);
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('sub', 'newDoc.md'),
@ -391,16 +407,17 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(1, 7, 1, 15), './sub/newDoc.md'),
]
});
});
}));
test('Path rename using absolute file path should anchor to workspace root', async () => {
test('Path rename using absolute file path should anchor to workspace root', withStore(async (store) => {
const uri = workspacePath('sub', 'doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc.md)`,
`[ref]: /sub/doc.md`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/newSub/newDoc.md', new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/newSub/newDoc.md', workspace);
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('newSub', 'newDoc.md'),
@ -410,26 +427,28 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/newSub/newDoc.md'),
]
});
});
}));
test('Path rename should use un-encoded paths as placeholder', async () => {
test('Path rename should use un-encoded paths as placeholder', withStore(async (store) => {
const uri = workspacePath('sub', 'doc with spaces.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc%20with%20spaces.md)`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(doc, new vscode.Position(0, 10), new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(0, 10), workspace);
assert.strictEqual(info!.placeholder, '/sub/doc with spaces.md');
});
}));
test('Path rename should encode paths', async () => {
test('Path rename should encode paths', withStore(async (store) => {
const uri = workspacePath('sub', 'doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/sub/doc.md)`,
`[ref]: /sub/doc.md`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/NEW sub/new DOC.md', workspace);
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('NEW sub', 'new DOC.md'),
@ -439,9 +458,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/NEW%20sub/new%20DOC.md'),
]
});
});
}));
test('Path rename should work with unknown files', async () => {
test('Path rename should work with unknown files', withStore(async (store) => {
const uri1 = workspacePath('doc1.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![img](/images/more/image.png)`,
@ -454,10 +473,12 @@ suite('markdown: rename', () => {
`![img](/images/more/image.png)`,
));
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), '/img/test/new.png', new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc1,
doc2
]));
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), '/img/test/new.png', workspace);
assertEditsEqual(edit!,
// Should not have file edits since the files don't exist here
{
@ -471,16 +492,17 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(0, 7, 0, 29), '/img/test/new.png'),
]
});
});
}));
test('Path rename should use .md extension on extension-less link', async () => {
test('Path rename should use .md extension on extension-less link', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[text](/doc#header)`,
`[ref]: /doc#other`,
));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(doc, new vscode.Position(0, 10), '/new File', new InMemoryMdWorkspace([doc]));
const edit = await getRenameEdits(store, doc, new vscode.Position(0, 10), '/new File', workspace);
assertEditsEqual(edit!, {
originalUri: uri,
newUri: workspacePath('new File.md'), // Rename on disk should use file extension
@ -490,10 +512,10 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(1, 7, 1, 11), '/new%20File'),
]
});
});
}));
// TODO: fails on windows
test.skip('Path rename should use correctly resolved paths across files', async () => {
test.skip('Path rename should use correctly resolved paths across files', withStore(async (store) => {
const uri1 = workspacePath('sub', 'doc.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`[text](./doc.md)`,
@ -518,9 +540,11 @@ suite('markdown: rename', () => {
`[ref]: /sub/doc.md`,
));
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), './new/new-doc.md', new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc1, doc2, doc3, doc4,
]));
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), './new/new-doc.md', workspace);
assertEditsEqual(edit!, {
originalUri: uri1,
newUri: workspacePath('sub', 'new', 'new-doc.md'),
@ -545,9 +569,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(1, 7, 1, 18), '/sub/new/new-doc.md'),
]
});
});
}));
test('Path rename should resolve on links without prefix', async () => {
test('Path rename should resolve on links without prefix', withStore(async (store) => {
const uri1 = workspacePath('sub', 'doc.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`![text](sub2/doc3.md)`,
@ -561,9 +585,11 @@ suite('markdown: rename', () => {
const uri3 = workspacePath('sub', 'sub2', 'doc3.md');
const doc3 = new InMemoryDocument(uri3, joinLines());
const edit = await getRenameEdits(doc1, new vscode.Position(0, 10), 'sub2/cat.md', new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc1, doc2, doc3
]));
const edit = await getRenameEdits(store, doc1, new vscode.Position(0, 10), 'sub2/cat.md', workspace);
assertEditsEqual(edit!, {
originalUri: workspacePath('sub', 'sub2', 'doc3.md'),
newUri: workspacePath('sub', 'sub2', 'cat.md'),
@ -572,21 +598,22 @@ suite('markdown: rename', () => {
}, {
uri: uri2, edits: [new vscode.TextEdit(new vscode.Range(0, 8, 0, 24), 'sub/sub2/cat.md')]
});
});
}));
test('Rename on link should use header text as placeholder', async () => {
test('Rename on link should use header text as placeholder', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`### a B c ###`,
`[text](#a-b-c)`,
));
const info = await prepareRename(doc, new vscode.Position(1, 10), new InMemoryMdWorkspace([doc]));
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const info = await prepareRename(store, doc, new vscode.Position(1, 10), workspace);
assert.strictEqual(info!.placeholder, 'a B c');
assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13));
});
}));
test('Rename on http uri should work', async () => {
test('Rename on http uri should work', withStore(async (store) => {
const uri1 = workspacePath('doc.md');
const uri2 = workspacePath('doc2.md');
const doc = new InMemoryDocument(uri1, joinLines(
@ -595,12 +622,14 @@ suite('markdown: rename', () => {
`<http://example.com>`,
));
const edit = await getRenameEdits(doc, new vscode.Position(1, 10), "https://example.com/sub", new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
doc,
new InMemoryDocument(uri2, joinLines(
`[4](http://example.com)`,
`[4](http://example.com)`
))
]));
const edit = await getRenameEdits(store, doc, new vscode.Position(1, 10), "https://example.com/sub", workspace);
assertEditsEqual(edit!, {
uri: uri1, edits: [
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
@ -612,9 +641,9 @@ suite('markdown: rename', () => {
new vscode.TextEdit(new vscode.Range(0, 4, 0, 22), 'https://example.com/sub'),
]
});
});
}));
test('Rename on definition path should update all references to path', async () => {
test('Rename on definition path should update all references to path', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[ref text][ref]`,
@ -622,22 +651,22 @@ suite('markdown: rename', () => {
`[ref]: /file`, // rename here
));
const workspace = new InMemoryMdWorkspace([doc]);
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const preparedInfo = await prepareRename(doc, new vscode.Position(2, 10), workspace);
const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 10), workspace);
assert.strictEqual(preparedInfo!.placeholder, '/file');
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12));
const edit = await getRenameEdits(doc, new vscode.Position(2, 10), "/newFile", workspace);
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 10), "/newFile", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/newFile'),
new vscode.TextEdit(new vscode.Range(2, 7, 2, 12), '/newFile'),
]
});
});
}));
test('Rename on definition path where file exists should also update file', async () => {
test('Rename on definition path where file exists should also update file', withStore(async (store) => {
const uri1 = workspacePath('doc.md');
const doc1 = new InMemoryDocument(uri1, joinLines(
`[ref text][ref]`,
@ -648,13 +677,13 @@ suite('markdown: rename', () => {
const uri2 = workspacePath('doc2.md');
const doc2 = new InMemoryDocument(uri2, joinLines());
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
const preparedInfo = await prepareRename(doc1, new vscode.Position(2, 10), workspace);
const preparedInfo = await prepareRename(store, doc1, new vscode.Position(2, 10), workspace);
assert.strictEqual(preparedInfo!.placeholder, '/doc2');
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 7, 2, 12));
const edit = await getRenameEdits(doc1, new vscode.Position(2, 10), "/new-doc", workspace);
const edit = await getRenameEdits(store, doc1, new vscode.Position(2, 10), "/new-doc", workspace);
assertEditsEqual(edit!, {
uri: uri1, edits: [
new vscode.TextEdit(new vscode.Range(1, 9, 1, 14), '/new-doc'),
@ -664,9 +693,9 @@ suite('markdown: rename', () => {
originalUri: uri2,
newUri: workspacePath('new-doc.md')
});
});
}));
test('Rename on definition path header should update all references to header', async () => {
test('Rename on definition path header should update all references to header', withStore(async (store) => {
const uri = workspacePath('doc.md');
const doc = new InMemoryDocument(uri, joinLines(
`[ref text][ref]`,
@ -674,18 +703,18 @@ suite('markdown: rename', () => {
`[ref]: /file#header`, // rename here
));
const workspace = new InMemoryMdWorkspace([doc]);
const workspace = store.add(new InMemoryMdWorkspace([doc]));
const preparedInfo = await prepareRename(doc, new vscode.Position(2, 16), workspace);
const preparedInfo = await prepareRename(store, doc, new vscode.Position(2, 16), workspace);
assert.strictEqual(preparedInfo!.placeholder, 'header');
assertRangeEqual(preparedInfo!.range, new vscode.Range(2, 13, 2, 19));
const edit = await getRenameEdits(doc, new vscode.Position(2, 16), "New Header", workspace);
const edit = await getRenameEdits(store, doc, new vscode.Position(2, 16), "New Header", workspace);
assertEditsEqual(edit!, {
uri, edits: [
new vscode.TextEdit(new vscode.Range(1, 15, 1, 21), 'new-header'),
new vscode.TextEdit(new vscode.Range(2, 13, 2, 19), 'new-header'),
]
});
});
}));
});

View file

@ -5,6 +5,7 @@
import * as assert from 'assert';
import * as os from 'os';
import * as vscode from 'vscode';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
export const joinLines = (...args: string[]) =>
@ -37,3 +38,14 @@ export function assertRangeEqual(expected: vscode.Range, actual: vscode.Range, m
assert.strictEqual(expected.end.line, actual.end.line, message);
assert.strictEqual(expected.end.character, actual.end.character, message);
}
export function withStore<R>(fn: (this: Mocha.Context, store: DisposableStore) => Promise<R>) {
return async function (this: Mocha.Context): Promise<R> {
const store = new DisposableStore();
try {
return await fn.call(this, store);
} finally {
store.dispose();
}
};
}

View file

@ -10,37 +10,40 @@ import { MdDocumentSymbolProvider } from '../languageFeatures/documentSymbols';
import { MdWorkspaceSymbolProvider } from '../languageFeatures/workspaceSymbols';
import { MdTableOfContentsProvider } from '../tableOfContents';
import { ITextDocument } from '../types/textDocument';
import { DisposableStore } from '../util/dispose';
import { InMemoryDocument } from '../util/inMemoryDocument';
import { IMdWorkspace } from '../workspace';
import { createNewMarkdownEngine } from './engine';
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
import { nulLogger } from './nulLogging';
import { workspacePath } from './util';
import { withStore, workspacePath } from './util';
function getWorkspaceSymbols(workspace: IMdWorkspace, query = ''): Promise<vscode.SymbolInformation[]> {
function getWorkspaceSymbols(store: DisposableStore, workspace: IMdWorkspace, query = ''): Promise<vscode.SymbolInformation[]> {
const engine = createNewMarkdownEngine();
const symbolProvider = new MdDocumentSymbolProvider(new MdTableOfContentsProvider(engine, workspace, nulLogger), nulLogger);
return new MdWorkspaceSymbolProvider(symbolProvider, workspace).provideWorkspaceSymbols(query);
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
const symbolProvider = new MdDocumentSymbolProvider(tocProvider, nulLogger);
const workspaceSymbolProvider = store.add(new MdWorkspaceSymbolProvider(symbolProvider, workspace));
return workspaceSymbolProvider.provideWorkspaceSymbols(query);
}
suite('markdown.WorkspaceSymbolProvider', () => {
test('Should not return anything for empty workspace', async () => {
const workspace = new InMemoryMdWorkspace([]);
assert.deepStrictEqual(await getWorkspaceSymbols(workspace, ''), []);
});
test('Should not return anything for empty workspace', withStore(async (store) => {
const workspace = store.add(new InMemoryMdWorkspace([]));
assert.deepStrictEqual(await getWorkspaceSymbols(store, workspace, ''), []);
}));
test('Should return symbols from workspace with one markdown file', async () => {
const workspace = new InMemoryMdWorkspace([
test('Should return symbols from workspace with one markdown file', withStore(async (store) => {
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(workspacePath('test.md'), `# header1\nabc\n## header2`)
]);
]));
const symbols = await getWorkspaceSymbols(workspace, '');
const symbols = await getWorkspaceSymbols(store, workspace, '');
assert.strictEqual(symbols.length, 2);
assert.strictEqual(symbols[0].name, '# header1');
assert.strictEqual(symbols[1].name, '## header2');
});
}));
test('Should return all content basic workspace', async () => {
test('Should return all content basic workspace', withStore(async (store) => {
const fileNameCount = 10;
const files: ITextDocument[] = [];
for (let i = 0; i < fileNameCount; ++i) {
@ -48,55 +51,55 @@ suite('markdown.WorkspaceSymbolProvider', () => {
files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`));
}
const workspace = new InMemoryMdWorkspace(files);
const workspace = store.add(new InMemoryMdWorkspace(files));
const symbols = await getWorkspaceSymbols(workspace, '');
const symbols = await getWorkspaceSymbols(store, workspace, '');
assert.strictEqual(symbols.length, fileNameCount * 2);
});
}));
test('Should update results when markdown file changes symbols', async () => {
test('Should update results when markdown file changes symbols', withStore(async (store) => {
const testFileName = workspacePath('test.md');
const workspace = new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(testFileName, `# header1`, 1 /* version */)
]);
]));
assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1);
assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1);
// Update file
workspace.updateDocument(new InMemoryDocument(testFileName, `# new header\nabc\n## header2`, 2 /* version */));
const newSymbols = await getWorkspaceSymbols(workspace, '');
const newSymbols = await getWorkspaceSymbols(store, workspace, '');
assert.strictEqual(newSymbols.length, 2);
assert.strictEqual(newSymbols[0].name, '# new header');
assert.strictEqual(newSymbols[1].name, '## header2');
});
}));
test('Should remove results when file is deleted', async () => {
test('Should remove results when file is deleted', withStore(async (store) => {
const testFileName = workspacePath('test.md');
const workspace = new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(testFileName, `# header1`)
]);
]));
assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1);
assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1);
// delete file
workspace.deleteDocument(testFileName);
const newSymbols = await getWorkspaceSymbols(workspace, '');
const newSymbols = await getWorkspaceSymbols(store, workspace, '');
assert.strictEqual(newSymbols.length, 0);
});
}));
test('Should update results when markdown file is created', async () => {
test('Should update results when markdown file is created', withStore(async (store) => {
const testFileName = workspacePath('test.md');
const workspace = new InMemoryMdWorkspace([
const workspace = store.add(new InMemoryMdWorkspace([
new InMemoryDocument(testFileName, `# header1`)
]);
]));
assert.strictEqual((await getWorkspaceSymbols(workspace, '')).length, 1);
assert.strictEqual((await getWorkspaceSymbols(store, workspace, '')).length, 1);
// Create file
workspace.createDocument(new InMemoryDocument(workspacePath('test2.md'), `# new header\nabc\n## header2`));
const newSymbols = await getWorkspaceSymbols(workspace, '');
const newSymbols = await getWorkspaceSymbols(store, workspace, '');
assert.strictEqual(newSymbols.length, 3);
});
}));
});

View file

@ -5,13 +5,36 @@
import * as vscode from 'vscode';
export function disposeAll(disposables: vscode.Disposable[]) {
while (disposables.length) {
const item = disposables.pop();
item?.dispose();
export class MultiDisposeError extends Error {
constructor(
public readonly errors: any[]
) {
super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`);
}
}
export function disposeAll(disposables: Iterable<vscode.Disposable>) {
const errors: any[] = [];
for (const disposable of disposables) {
try {
disposable.dispose();
} catch (e) {
errors.push(e);
}
}
if (errors.length === 1) {
throw errors[0];
} else if (errors.length > 1) {
throw new MultiDisposeError(errors);
}
}
export interface IDisposable {
dispose(): void;
}
export abstract class Disposable {
private _isDisposed = false;
@ -25,7 +48,7 @@ export abstract class Disposable {
disposeAll(this._disposables);
}
protected _register<T extends vscode.Disposable>(value: T): T {
protected _register<T extends IDisposable>(value: T): T {
if (this._isDisposed) {
value.dispose();
} else {
@ -38,3 +61,22 @@ export abstract class Disposable {
return this._isDisposed;
}
}
export class DisposableStore extends Disposable {
private readonly items = new Set<IDisposable>();
public override dispose() {
super.dispose();
disposeAll(this.items);
this.items.clear();
}
public add<T extends IDisposable>(item: T): T {
if (this.isDisposed) {
console.warn('Adding to disposed store. Item will be leaked');
}
this.items.add(item);
return item;
}
}