Skip to content

Commit

Permalink
Rebase against the upstream 33b1980
Browse files Browse the repository at this point in the history
vscode-upstream-sha1: 33b1980
  • Loading branch information
Eclipse Che Sync committed Jun 14, 2022
2 parents c7361de + 33b1980 commit 27f00c5
Show file tree
Hide file tree
Showing 45 changed files with 458 additions and 557 deletions.
17 changes: 15 additions & 2 deletions code/extensions/markdown-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,10 @@
"experimental"
]
},
"markdown.experimental.validate.headerLinks.enabled": {
"markdown.experimental.validate.fragmentLinks.enabled": {
"type": "string",
"scope": "resource",
"markdownDescription": "%configuration.markdown.experimental.validate.headerLinks.enabled.description%",
"markdownDescription": "%configuration.markdown.experimental.validate.fragmentLinks.enabled.description%",
"default": "warning",
"enum": [
"ignore",
Expand All @@ -475,6 +475,19 @@
"experimental"
]
},
"markdown.experimental.validate.fileLinks.markdownFragmentLinks": {
"type": "string",
"scope": "resource",
"markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%",
"enum": [
"ignore",
"warning",
"error"
],
"tags": [
"experimental"
]
},
"markdown.experimental.validate.ignoreLinks": {
"type": "array",
"scope": "resource",
Expand Down
3 changes: 2 additions & 1 deletion code/extensions/markdown-language-features/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@
"configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links.",
"configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.",
"configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.",
"configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.",
"configuration.markdown.experimental.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.",
"configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.",
"configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description": "Validate the fragment part of links to headers in other files in Markdown files, e.g. `[link](/path/to/file.md#header)`. Inherits the setting value from `#markdown.experimental.validate.fragmentLinks.enabled#` by default.",
"configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.",
"workspaceTrust": "Required for loading styles configured in the workspace."
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ export enum DiagnosticLevel {

export interface DiagnosticOptions {
readonly enabled: boolean;
readonly validateReferences: DiagnosticLevel;
readonly validateOwnHeaders: DiagnosticLevel;
readonly validateFilePaths: DiagnosticLevel;
readonly validateReferences: DiagnosticLevel | undefined;
readonly validateFragmentLinks: DiagnosticLevel | undefined;
readonly validateFileLinks: DiagnosticLevel | undefined;
readonly validateMarkdownFileLinkFragments: DiagnosticLevel | undefined;
readonly ignoreLinks: readonly string[];
}

function toSeverity(level: DiagnosticLevel): vscode.DiagnosticSeverity | undefined {
function toSeverity(level: DiagnosticLevel | undefined): vscode.DiagnosticSeverity | undefined {
switch (level) {
case DiagnosticLevel.error: return vscode.DiagnosticSeverity.Error;
case DiagnosticLevel.warning: return vscode.DiagnosticSeverity.Warning;
case DiagnosticLevel.ignore: return undefined;
case undefined: return undefined;
}
}

Expand All @@ -63,8 +65,9 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf
if (
e.affectsConfiguration('markdown.experimental.validate.enabled')
|| e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled')
|| e.affectsConfiguration('markdown.experimental.validate.headerLinks.enabled')
|| e.affectsConfiguration('markdown.experimental.validate.fragmentLinks.enabled')
|| e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled')
|| e.affectsConfiguration('markdown.experimental.validate.fileLinks.markdownFragmentLinks')
|| e.affectsConfiguration('markdown.experimental.validate.ignoreLinks')
) {
this._onDidChange.fire();
Expand All @@ -74,11 +77,13 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf

public getOptions(resource: vscode.Uri): DiagnosticOptions {
const config = vscode.workspace.getConfiguration('markdown', resource);
const validateFragmentLinks = config.get<DiagnosticLevel>('experimental.validate.fragmentLinks.enabled');
return {
enabled: config.get<boolean>('experimental.validate.enabled', false),
validateReferences: config.get<DiagnosticLevel>('experimental.validate.referenceLinks.enabled', DiagnosticLevel.ignore),
validateOwnHeaders: config.get<DiagnosticLevel>('experimental.validate.headerLinks.enabled', DiagnosticLevel.ignore),
validateFilePaths: config.get<DiagnosticLevel>('experimental.validate.fileLinks.enabled', DiagnosticLevel.ignore),
validateReferences: config.get<DiagnosticLevel>('experimental.validate.referenceLinks.enabled'),
validateFragmentLinks,
validateFileLinks: config.get<DiagnosticLevel>('experimental.validate.fileLinks.enabled'),
validateMarkdownFileLinkFragments: config.get<DiagnosticLevel | undefined>('markdown.experimental.validate.fileLinks.markdownFragmentLinks', validateFragmentLinks),
ignoreLinks: config.get('experimental.validate.ignoreLinks', []),
};
}
Expand Down Expand Up @@ -294,7 +299,7 @@ export class DiagnosticManager extends Disposable {
if (doc) {
this.inFlightDiagnostics.trigger(doc.uri, async (token) => {
const state = await this.recomputeDiagnosticState(doc, token);
this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFilePaths ? state.links : []);
this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []);
this.collection.set(doc.uri, state.diagnostics);
});
}
Expand Down Expand Up @@ -395,13 +400,13 @@ export class DiagnosticComputer {
diagnostics: (await Promise.all([
this.validateFileLinks(doc, options, links, token),
Array.from(this.validateReferenceLinks(options, links)),
this.validateOwnHeaderLinks(doc, options, links, token),
this.validateFragmentLinks(doc, options, links, token),
])).flat()
};
}

private async validateOwnHeaderLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
const severity = toSeverity(options.validateOwnHeaders);
private async validateFragmentLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
const severity = toSeverity(options.validateFragmentLinks);
if (typeof severity === 'undefined') {
return [];
}
Expand Down Expand Up @@ -449,10 +454,11 @@ export class DiagnosticComputer {
}

private async validateFileLinks(doc: SkinnyTextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
const severity = toSeverity(options.validateFilePaths);
if (typeof severity === 'undefined') {
const pathErrorSeverity = toSeverity(options.validateFileLinks);
if (typeof pathErrorSeverity === 'undefined') {
return [];
}
const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments);

const linkSet = new FileLinkMap(links);
if (linkSet.size === 0) {
Expand All @@ -479,18 +485,18 @@ export class DiagnosticComputer {
const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath);
for (const link of links) {
if (!this.isIgnoredLink(options, link.source.pathText)) {
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText));
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, pathErrorSeverity, link.source.pathText));
}
}
} else if (hrefDoc) {
} else if (hrefDoc && typeof fragmentErrorSeverity !== 'undefined') {
// Validate each of the links to headers in the file
const fragmentLinks = links.filter(x => x.fragment);
if (fragmentLinks.length) {
const toc = await TableOfContents.create(this.engine, hrefDoc);
for (const link of fragmentLinks) {
if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.text)) {
const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment);
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.text));
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, fragmentErrorSeverity, link.source.text));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents:
return (
await computer.getDiagnostics(doc, {
enabled: true,
validateFilePaths: DiagnosticLevel.warning,
validateOwnHeaders: DiagnosticLevel.warning,
validateFileLinks: DiagnosticLevel.warning,
validateFragmentLinks: DiagnosticLevel.warning,
validateMarkdownFileLinkFragments: DiagnosticLevel.warning,
validateReferences: DiagnosticLevel.warning,
ignoreLinks: [],
}, noopToken)
).diagnostics;
}

function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration()) {
function createDiagnosticsManager(workspaceContents: MdWorkspaceContents, configuration = new MemoryDiagnosticConfiguration({})) {
const engine = createNewMarkdownEngine();
const linkProvider = new MdLinkProvider(engine);
return new DiagnosticManager(new DiagnosticComputer(engine, workspaceContents, linkProvider), configuration);
Expand All @@ -45,32 +46,28 @@ function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRa
}
}

const defaultDiagnosticsOptions = Object.freeze<DiagnosticOptions>({
enabled: true,
validateFileLinks: DiagnosticLevel.warning,
validateMarkdownFileLinkFragments: undefined,
validateFragmentLinks: DiagnosticLevel.warning,
validateReferences: DiagnosticLevel.warning,
ignoreLinks: [],
});

class MemoryDiagnosticConfiguration implements DiagnosticConfiguration {

private readonly _onDidChange = new vscode.EventEmitter<void>();
public readonly onDidChange = this._onDidChange.event;

constructor(
private readonly enabled: boolean = true,
private readonly ignoreLinks: string[] = [],
private readonly _options: Partial<DiagnosticOptions>,
) { }

getOptions(_resource: vscode.Uri): DiagnosticOptions {
if (!this.enabled) {
return {
enabled: false,
validateFilePaths: DiagnosticLevel.ignore,
validateOwnHeaders: DiagnosticLevel.ignore,
validateReferences: DiagnosticLevel.ignore,
ignoreLinks: this.ignoreLinks,
};
}
return {
enabled: true,
validateFilePaths: DiagnosticLevel.warning,
validateOwnHeaders: DiagnosticLevel.warning,
validateReferences: DiagnosticLevel.warning,
ignoreLinks: this.ignoreLinks,
...defaultDiagnosticsOptions,
...this._options,
};
}
}
Expand Down Expand Up @@ -172,7 +169,7 @@ suite('markdown: Diagnostics', () => {
`[text][no-such-ref]`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(false));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ enabled: false }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand Down Expand Up @@ -203,17 +200,52 @@ suite('markdown: Diagnostics', () => {
`[text]: /no-such-file`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file']));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});

test('Should be able to disable fragment validation for external files', async () => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`![i](/doc2.md#no-such)`,
));
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));

const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]);

const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateMarkdownFileLinkFragments: DiagnosticLevel.ignore }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});

test('Disabling own fragment validation should also disable path fragment validation by default', async () => {
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 contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]);

{
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateFragmentLinks: DiagnosticLevel.ignore }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
}
{
// But we should be able to override the default
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 1);
}
});

test('ignoreLinks should allow skipping link to non-existent file', async () => {
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
`[text](/no-such-file#header)`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file']));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand All @@ -223,7 +255,7 @@ suite('markdown: Diagnostics', () => {
`[text](/no-such-file#header)`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/no-such-file']));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/no-such-file'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand All @@ -235,7 +267,7 @@ suite('markdown: Diagnostics', () => {
`![i](/images/sub/sub2/ccc.png)`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['/images/**/*.png']));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['/images/**/*.png'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand All @@ -245,7 +277,7 @@ suite('markdown: Diagnostics', () => {
`![i](#no-such)`,
));

const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['#no-such']));
const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration({ ignoreLinks: ['#no-such'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand All @@ -258,12 +290,12 @@ suite('markdown: Diagnostics', () => {

const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]);
{
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#no-such']));
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md#no-such'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
}
{
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#*']));
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md#*'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
}
Expand All @@ -276,7 +308,7 @@ suite('markdown: Diagnostics', () => {
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));

const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]);
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md']));
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand All @@ -289,7 +321,7 @@ suite('markdown: Diagnostics', () => {
));

const contents = new InMemoryWorkspaceMarkdownDocuments([doc1]);
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md']));
const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration({ ignoreLinks: ['/doc2.md'] }));
const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken);
assert.deepStrictEqual(diagnostics.length, 0);
});
Expand Down
1 change: 0 additions & 1 deletion code/extensions/vscode-api-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"notebookControllerKind",
"notebookDebugOptions",
"notebookDeprecated",
"notebookEditorDecorationType",
"notebookEditorEdit",
"notebookLiveShare",
"notebookMessaging",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,6 @@ suite('vscode', function () {
assertNoRpcFromEntry([item, 'TreeView']);
});

test('no rpc, createNotebookEditorDecorationType(...)', function () {
const item = vscode.notebooks.createNotebookEditorDecorationType({ top: {} });
dispo.push(item);
assertNoRpcFromEntry([item, 'NotebookEditorDecorationType']);
});

test('no rpc, createNotebookController(...)', function () {
const ctrl = vscode.notebooks.createNotebookController('foo', 'bar', '');
Expand Down
1 change: 0 additions & 1 deletion code/extensions/vscode-notebook-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"notebookControllerKind",
"notebookDebugOptions",
"notebookDeprecated",
"notebookEditorDecorationType",
"notebookLiveShare",
"notebookMessaging",
"notebookMime"
Expand Down
Loading

0 comments on commit 27f00c5

Please sign in to comment.