Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'
This commit is contained in:
@ -0,0 +1,319 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import * as path from 'path';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { getLanguageModes, WorkspaceFolder, TextDocument, CompletionList, CompletionItemKind, ClientCapabilities, TextEdit } from '../modes/languageModes';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
import { getDocumentContext } from '../utils/documentContext';
|
||||
export interface ItemDescription {
|
||||
label: string;
|
||||
documentation?: string;
|
||||
kind?: CompletionItemKind;
|
||||
resultText?: string;
|
||||
command?: { title: string, command: string };
|
||||
notAvailable?: boolean;
|
||||
}
|
||||
|
||||
export function assertCompletion(completions: CompletionList, expected: ItemDescription, document: TextDocument) {
|
||||
let matches = completions.items.filter(completion => {
|
||||
return completion.label === expected.label;
|
||||
});
|
||||
if (expected.notAvailable) {
|
||||
assert.equal(matches.length, 0, `${expected.label} should not existing is results`);
|
||||
return;
|
||||
}
|
||||
|
||||
assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`);
|
||||
let match = matches[0];
|
||||
if (expected.documentation) {
|
||||
assert.equal(match.documentation, expected.documentation);
|
||||
}
|
||||
if (expected.kind) {
|
||||
assert.equal(match.kind, expected.kind);
|
||||
}
|
||||
if (expected.resultText && match.textEdit) {
|
||||
const edit = TextEdit.is(match.textEdit) ? match.textEdit : TextEdit.replace(match.textEdit.replace, match.textEdit.newText);
|
||||
assert.equal(TextDocument.applyEdits(document, [edit]), expected.resultText);
|
||||
}
|
||||
if (expected.command) {
|
||||
assert.deepEqual(match.command, expected.command);
|
||||
}
|
||||
}
|
||||
|
||||
const testUri = 'test://test/test.html';
|
||||
|
||||
export async function testCompletionFor(value: string, expected: { count?: number, items?: ItemDescription[] }, uri = testUri, workspaceFolders?: WorkspaceFolder[]): Promise<void> {
|
||||
let offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
let workspace = {
|
||||
settings: {},
|
||||
folders: workspaceFolders || [{ name: 'x', uri: uri.substr(0, uri.lastIndexOf('/')) }]
|
||||
};
|
||||
|
||||
let document = TextDocument.create(uri, 'html', 0, value);
|
||||
let position = document.positionAt(offset);
|
||||
const context = getDocumentContext(uri, workspace.folders);
|
||||
|
||||
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
|
||||
const mode = languageModes.getModeAtPosition(document, position)!;
|
||||
|
||||
let list = await mode.doComplete!(document, position, context);
|
||||
|
||||
if (expected.count) {
|
||||
assert.equal(list.items.length, expected.count);
|
||||
}
|
||||
if (expected.items) {
|
||||
for (let item of expected.items) {
|
||||
assertCompletion(list, item, document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suite('HTML Completion', () => {
|
||||
test('HTML JavaScript Completions', async () => {
|
||||
await testCompletionFor('<html><script>window.|</script></html>', {
|
||||
items: [
|
||||
{ label: 'location', resultText: '<html><script>window.location</script></html>' },
|
||||
]
|
||||
});
|
||||
await testCompletionFor('<html><script>$.|</script></html>', {
|
||||
items: [
|
||||
{ label: 'getJSON', resultText: '<html><script>$.getJSON</script></html>' },
|
||||
]
|
||||
});
|
||||
await testCompletionFor('<html><script>const x = { a: 1 };</script><script>x.|</script></html>', {
|
||||
items: [
|
||||
{ label: 'a', resultText: '<html><script>const x = { a: 1 };</script><script>x.a</script></html>' },
|
||||
]
|
||||
}, 'test://test/test2.html');
|
||||
});
|
||||
});
|
||||
|
||||
suite('HTML Path Completion', () => {
|
||||
const triggerSuggestCommand = {
|
||||
title: 'Suggest',
|
||||
command: 'editor.action.triggerSuggest'
|
||||
};
|
||||
|
||||
const fixtureRoot = path.resolve(__dirname, '../../src/test/pathCompletionFixtures');
|
||||
const fixtureWorkspace = { name: 'fixture', uri: URI.file(fixtureRoot).toString() };
|
||||
const indexHtmlUri = URI.file(path.resolve(fixtureRoot, 'index.html')).toString();
|
||||
const aboutHtmlUri = URI.file(path.resolve(fixtureRoot, 'about/about.html')).toString();
|
||||
|
||||
test('Basics - Correct label/kind/result/command', async () => {
|
||||
await testCompletionFor('<script src="./|">', {
|
||||
items: [
|
||||
{ label: 'about/', kind: CompletionItemKind.Folder, resultText: '<script src="./about/">', command: triggerSuggestCommand },
|
||||
{ label: 'index.html', kind: CompletionItemKind.File, resultText: '<script src="./index.html">' },
|
||||
{ label: 'src/', kind: CompletionItemKind.Folder, resultText: '<script src="./src/">', command: triggerSuggestCommand }
|
||||
]
|
||||
}, indexHtmlUri);
|
||||
});
|
||||
|
||||
test('Basics - Single Quote', async () => {
|
||||
await testCompletionFor(`<script src='./|'>`, {
|
||||
items: [
|
||||
{ label: 'about/', kind: CompletionItemKind.Folder, resultText: `<script src='./about/'>`, command: triggerSuggestCommand },
|
||||
{ label: 'index.html', kind: CompletionItemKind.File, resultText: `<script src='./index.html'>` },
|
||||
{ label: 'src/', kind: CompletionItemKind.Folder, resultText: `<script src='./src/'>`, command: triggerSuggestCommand }
|
||||
]
|
||||
}, indexHtmlUri);
|
||||
});
|
||||
|
||||
test('No completion for remote paths', async () => {
|
||||
await testCompletionFor('<script src="http:">', { items: [] });
|
||||
await testCompletionFor('<script src="http:/|">', { items: [] });
|
||||
await testCompletionFor('<script src="http://|">', { items: [] });
|
||||
await testCompletionFor('<script src="https:|">', { items: [] });
|
||||
await testCompletionFor('<script src="https:/|">', { items: [] });
|
||||
await testCompletionFor('<script src="https://|">', { items: [] });
|
||||
await testCompletionFor('<script src="//|">', { items: [] });
|
||||
});
|
||||
|
||||
test('Relative Path', async () => {
|
||||
await testCompletionFor('<script src="../|">', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="../about/">' },
|
||||
{ label: 'index.html', resultText: '<script src="../index.html">' },
|
||||
{ label: 'src/', resultText: '<script src="../src/">' }
|
||||
]
|
||||
}, aboutHtmlUri);
|
||||
|
||||
await testCompletionFor('<script src="../src/|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="../src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="../src/test.js">' },
|
||||
]
|
||||
}, aboutHtmlUri);
|
||||
});
|
||||
|
||||
test('Absolute Path', async () => {
|
||||
await testCompletionFor('<script src="/|">', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="/about/">' },
|
||||
{ label: 'index.html', resultText: '<script src="/index.html">' },
|
||||
{ label: 'src/', resultText: '<script src="/src/">' },
|
||||
]
|
||||
}, indexHtmlUri);
|
||||
|
||||
await testCompletionFor('<script src="/src/|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="/src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="/src/test.js">' },
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
test('Empty Path Value', async () => {
|
||||
// document: index.html
|
||||
await testCompletionFor('<script src="|">', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="about/">' },
|
||||
{ label: 'index.html', resultText: '<script src="index.html">' },
|
||||
{ label: 'src/', resultText: '<script src="src/">' },
|
||||
]
|
||||
}, indexHtmlUri);
|
||||
// document: about.html
|
||||
await testCompletionFor('<script src="|">', {
|
||||
items: [
|
||||
{ label: 'about.css', resultText: '<script src="about.css">' },
|
||||
{ label: 'about.html', resultText: '<script src="about.html">' },
|
||||
{ label: 'media/', resultText: '<script src="media/">' },
|
||||
]
|
||||
}, aboutHtmlUri);
|
||||
});
|
||||
test('Incomplete Path', async () => {
|
||||
await testCompletionFor('<script src="/src/f|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="/src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="/src/test.js">' },
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="../src/f|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="../src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="../src/test.js">' },
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
test('No leading dot or slash', async () => {
|
||||
// document: index.html
|
||||
await testCompletionFor('<script src="s|">', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="about/">' },
|
||||
{ label: 'index.html', resultText: '<script src="index.html">' },
|
||||
{ label: 'src/', resultText: '<script src="src/">' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="src/|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="src/test.js">' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="src/f|">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="src/test.js">' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
// document: about.html
|
||||
await testCompletionFor('<script src="s|">', {
|
||||
items: [
|
||||
{ label: 'about.css', resultText: '<script src="about.css">' },
|
||||
{ label: 'about.html', resultText: '<script src="about.html">' },
|
||||
{ label: 'media/', resultText: '<script src="media/">' },
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="media/|">', {
|
||||
items: [
|
||||
{ label: 'icon.pic', resultText: '<script src="media/icon.pic">' }
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="media/f|">', {
|
||||
items: [
|
||||
{ label: 'icon.pic', resultText: '<script src="media/icon.pic">' }
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
test('Trigger completion in middle of path', async () => {
|
||||
// document: index.html
|
||||
await testCompletionFor('<script src="src/f|eature.js">', {
|
||||
items: [
|
||||
{ label: 'feature.js', resultText: '<script src="src/feature.js">' },
|
||||
{ label: 'test.js', resultText: '<script src="src/test.js">' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="s|rc/feature.js">', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="about/">' },
|
||||
{ label: 'index.html', resultText: '<script src="index.html">' },
|
||||
{ label: 'src/', resultText: '<script src="src/">' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
// document: about.html
|
||||
await testCompletionFor('<script src="media/f|eature.js">', {
|
||||
items: [
|
||||
{ label: 'icon.pic', resultText: '<script src="media/icon.pic">' }
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="m|edia/feature.js">', {
|
||||
items: [
|
||||
{ label: 'about.css', resultText: '<script src="about.css">' },
|
||||
{ label: 'about.html', resultText: '<script src="about.html">' },
|
||||
{ label: 'media/', resultText: '<script src="media/">' },
|
||||
]
|
||||
}, aboutHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
|
||||
test('Trigger completion in middle of path and with whitespaces', async () => {
|
||||
await testCompletionFor('<script src="./| about/about.html>', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="./about/ about/about.html>' },
|
||||
{ label: 'index.html', resultText: '<script src="./index.html about/about.html>' },
|
||||
{ label: 'src/', resultText: '<script src="./src/ about/about.html>' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
|
||||
await testCompletionFor('<script src="./a|bout /about.html>', {
|
||||
items: [
|
||||
{ label: 'about/', resultText: '<script src="./about/ /about.html>' },
|
||||
{ label: 'index.html', resultText: '<script src="./index.html /about.html>' },
|
||||
{ label: 'src/', resultText: '<script src="./src/ /about.html>' },
|
||||
]
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
test('Completion should ignore files/folders starting with dot', async () => {
|
||||
await testCompletionFor('<script src="./|"', {
|
||||
count: 3
|
||||
}, indexHtmlUri, [fixtureWorkspace]);
|
||||
});
|
||||
|
||||
test('Unquoted Path', async () => {
|
||||
/* Unquoted value is not supported in html language service yet
|
||||
testCompletionFor(`<div><a href=about/|>`, {
|
||||
items: [
|
||||
{ label: 'about.html', resultText: `<div><a href=about/about.html>` }
|
||||
]
|
||||
}, testUri);
|
||||
*/
|
||||
});
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import { getDocumentContext } from '../utils/documentContext';
|
||||
|
||||
suite('HTML Document Context', () => {
|
||||
|
||||
test('Context', function (): any {
|
||||
const docURI = 'file:///users/test/folder/test.html';
|
||||
const rootFolders = [{ name: '', uri: 'file:///users/test/' }];
|
||||
|
||||
let context = getDocumentContext(docURI, rootFolders);
|
||||
assert.equal(context.resolveReference('/', docURI), 'file:///users/test/');
|
||||
assert.equal(context.resolveReference('/message.html', docURI), 'file:///users/test/message.html');
|
||||
assert.equal(context.resolveReference('message.html', docURI), 'file:///users/test/folder/message.html');
|
||||
assert.equal(context.resolveReference('message.html', 'file:///users/test/'), 'file:///users/test/message.html');
|
||||
});
|
||||
});
|
@ -0,0 +1,126 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import * as embeddedSupport from '../modes/embeddedSupport';
|
||||
import { getLanguageService } from 'vscode-html-languageservice';
|
||||
import { TextDocument } from '../modes/languageModes';
|
||||
|
||||
suite('HTML Embedded Support', () => {
|
||||
|
||||
const htmlLanguageService = getLanguageService();
|
||||
|
||||
function assertLanguageId(value: string, expectedLanguageId: string | undefined): void {
|
||||
const offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
const document = TextDocument.create('test://test/test.html', 'html', 0, value);
|
||||
|
||||
const position = document.positionAt(offset);
|
||||
|
||||
const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
|
||||
const languageId = docRegions.getLanguageAtPosition(position);
|
||||
|
||||
assert.equal(languageId, expectedLanguageId);
|
||||
}
|
||||
|
||||
function assertEmbeddedLanguageContent(value: string, languageId: string, expectedContent: string): void {
|
||||
const document = TextDocument.create('test://test/test.html', 'html', 0, value);
|
||||
|
||||
const docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
|
||||
const content = docRegions.getEmbeddedDocument(languageId);
|
||||
assert.equal(content.getText(), expectedContent);
|
||||
}
|
||||
|
||||
test('Styles', function (): any {
|
||||
assertLanguageId('|<html><style>foo { }</style></html>', 'html');
|
||||
assertLanguageId('<html|><style>foo { }</style></html>', 'html');
|
||||
assertLanguageId('<html><st|yle>foo { }</style></html>', 'html');
|
||||
assertLanguageId('<html><style>|foo { }</style></html>', 'css');
|
||||
assertLanguageId('<html><style>foo| { }</style></html>', 'css');
|
||||
assertLanguageId('<html><style>foo { }|</style></html>', 'css');
|
||||
assertLanguageId('<html><style>foo { }</sty|le></html>', 'html');
|
||||
});
|
||||
|
||||
test('Styles - Incomplete HTML', function (): any {
|
||||
assertLanguageId('|<html><style>foo { }', 'html');
|
||||
assertLanguageId('<html><style>fo|o { }', 'css');
|
||||
assertLanguageId('<html><style>foo { }|', 'css');
|
||||
});
|
||||
|
||||
test('Style in attribute', function (): any {
|
||||
assertLanguageId('<div id="xy" |style="color: red"/>', 'html');
|
||||
assertLanguageId('<div id="xy" styl|e="color: red"/>', 'html');
|
||||
assertLanguageId('<div id="xy" style=|"color: red"/>', 'html');
|
||||
assertLanguageId('<div id="xy" style="|color: red"/>', 'css');
|
||||
assertLanguageId('<div id="xy" style="color|: red"/>', 'css');
|
||||
assertLanguageId('<div id="xy" style="color: red|"/>', 'css');
|
||||
assertLanguageId('<div id="xy" style="color: red"|/>', 'html');
|
||||
assertLanguageId('<div id="xy" style=\'color: r|ed\'/>', 'css');
|
||||
assertLanguageId('<div id="xy" style|=color:red/>', 'html');
|
||||
assertLanguageId('<div id="xy" style=|color:red/>', 'css');
|
||||
assertLanguageId('<div id="xy" style=color:r|ed/>', 'css');
|
||||
assertLanguageId('<div id="xy" style=color:red|/>', 'css');
|
||||
assertLanguageId('<div id="xy" style=color:red/|>', 'html');
|
||||
});
|
||||
|
||||
test('Style content', function (): any {
|
||||
assertEmbeddedLanguageContent('<html><style>foo { }</style></html>', 'css', ' foo { } ');
|
||||
assertEmbeddedLanguageContent('<html><script>var i = 0;</script></html>', 'css', ' ');
|
||||
assertEmbeddedLanguageContent('<html><style>foo { }</style>Hello<style>foo { }</style></html>', 'css', ' foo { } foo { } ');
|
||||
assertEmbeddedLanguageContent('<html>\n <style>\n foo { } \n </style>\n</html>\n', 'css', '\n \n foo { } \n \n\n');
|
||||
|
||||
assertEmbeddedLanguageContent('<div style="color: red"></div>', 'css', ' __{color: red} ');
|
||||
assertEmbeddedLanguageContent('<div style=color:red></div>', 'css', ' __{color:red} ');
|
||||
});
|
||||
|
||||
test('Scripts', function (): any {
|
||||
assertLanguageId('|<html><script>var i = 0;</script></html>', 'html');
|
||||
assertLanguageId('<html|><script>var i = 0;</script></html>', 'html');
|
||||
assertLanguageId('<html><scr|ipt>var i = 0;</script></html>', 'html');
|
||||
assertLanguageId('<html><script>|var i = 0;</script></html>', 'javascript');
|
||||
assertLanguageId('<html><script>var| i = 0;</script></html>', 'javascript');
|
||||
assertLanguageId('<html><script>var i = 0;|</script></html>', 'javascript');
|
||||
assertLanguageId('<html><script>var i = 0;</scr|ipt></html>', 'html');
|
||||
|
||||
assertLanguageId('<script type="text/javascript">var| i = 0;</script>', 'javascript');
|
||||
assertLanguageId('<script type="text/ecmascript">var| i = 0;</script>', 'javascript');
|
||||
assertLanguageId('<script type="application/javascript">var| i = 0;</script>', 'javascript');
|
||||
assertLanguageId('<script type="application/ecmascript">var| i = 0;</script>', 'javascript');
|
||||
assertLanguageId('<script type="application/typescript">var| i = 0;</script>', undefined);
|
||||
assertLanguageId('<script type=\'text/javascript\'>var| i = 0;</script>', 'javascript');
|
||||
});
|
||||
|
||||
test('Scripts in attribute', function (): any {
|
||||
assertLanguageId('<div |onKeyUp="foo()" onkeydown=\'bar()\'/>', 'html');
|
||||
assertLanguageId('<div onKeyUp=|"foo()" onkeydown=\'bar()\'/>', 'html');
|
||||
assertLanguageId('<div onKeyUp="|foo()" onkeydown=\'bar()\'/>', 'javascript');
|
||||
assertLanguageId('<div onKeyUp="foo(|)" onkeydown=\'bar()\'/>', 'javascript');
|
||||
assertLanguageId('<div onKeyUp="foo()|" onkeydown=\'bar()\'/>', 'javascript');
|
||||
assertLanguageId('<div onKeyUp="foo()"| onkeydown=\'bar()\'/>', 'html');
|
||||
assertLanguageId('<div onKeyUp="foo()" onkeydown=|\'bar()\'/>', 'html');
|
||||
assertLanguageId('<div onKeyUp="foo()" onkeydown=\'|bar()\'/>', 'javascript');
|
||||
assertLanguageId('<div onKeyUp="foo()" onkeydown=\'bar()|\'/>', 'javascript');
|
||||
assertLanguageId('<div onKeyUp="foo()" onkeydown=\'bar()\'|/>', 'html');
|
||||
|
||||
assertLanguageId('<DIV ONKEYUP|=foo()</DIV>', 'html');
|
||||
assertLanguageId('<DIV ONKEYUP=|foo()</DIV>', 'javascript');
|
||||
assertLanguageId('<DIV ONKEYUP=f|oo()</DIV>', 'javascript');
|
||||
assertLanguageId('<DIV ONKEYUP=foo(|)</DIV>', 'javascript');
|
||||
assertLanguageId('<DIV ONKEYUP=foo()|</DIV>', 'javascript');
|
||||
assertLanguageId('<DIV ONKEYUP=foo()<|/DIV>', 'html');
|
||||
|
||||
assertLanguageId('<label data-content="|Checkbox"/>', 'html');
|
||||
assertLanguageId('<label on="|Checkbox"/>', 'html');
|
||||
});
|
||||
|
||||
test('Script content', function (): any {
|
||||
assertEmbeddedLanguageContent('<html><script>var i = 0;</script></html>', 'javascript', ' var i = 0; ');
|
||||
assertEmbeddedLanguageContent('<script type="text/javascript">var i = 0;</script>', 'javascript', ' var i = 0; ');
|
||||
|
||||
assertEmbeddedLanguageContent('<div onKeyUp="foo()" onkeydown="bar()"/>', 'javascript', ' foo(); bar(); ');
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
Polymer({
|
||||
is: "chat-messages",
|
||||
properties: {
|
||||
user: {},
|
||||
friend: {
|
||||
observer: "_friendChanged"
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
22
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/19813-tab.html
vendored
Normal file
22
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/19813-tab.html
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
Polymer({
|
||||
is: "chat-messages",
|
||||
properties: {
|
||||
user: {},
|
||||
friend: {
|
||||
observer: "_friendChanged"
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
22
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/19813.html
vendored
Normal file
22
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/19813.html
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
Polymer({
|
||||
is: "chat-messages",
|
||||
properties: {
|
||||
user: {},
|
||||
friend: {
|
||||
observer: "_friendChanged"
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
6
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/21634.html
vendored
Normal file
6
lib/vscode/extensions/html-language-features/server/src/test/fixtures/expected/21634.html
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<app-route path="/module" element="page-module" bindRouter onUrlChange="updateModel"></app-route>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
});
|
||||
</script>
|
19
lib/vscode/extensions/html-language-features/server/src/test/fixtures/inputs/19813.html
vendored
Normal file
19
lib/vscode/extensions/html-language-features/server/src/test/fixtures/inputs/19813.html
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script>
|
||||
Polymer({
|
||||
is: "chat-messages",
|
||||
properties: {
|
||||
user: {},
|
||||
friend: {
|
||||
observer: "_friendChanged"
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
6
lib/vscode/extensions/html-language-features/server/src/test/fixtures/inputs/21634.html
vendored
Normal file
6
lib/vscode/extensions/html-language-features/server/src/test/fixtures/inputs/21634.html
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
<app-route path="/module" element="page-module" bindRouter onUrlChange="updateModel"></app-route>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
});
|
||||
</script>
|
@ -0,0 +1,215 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import { getFoldingRanges } from '../modes/htmlFolding';
|
||||
import { TextDocument, getLanguageModes } from '../modes/languageModes';
|
||||
import { ClientCapabilities } from 'vscode-css-languageservice';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
|
||||
interface ExpectedIndentRange {
|
||||
startLine: number;
|
||||
endLine: number;
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
async function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): Promise<void> {
|
||||
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
|
||||
const workspace = {
|
||||
settings: {},
|
||||
folders: [{ name: 'foo', uri: 'test://foo' }]
|
||||
};
|
||||
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
|
||||
const actual = await getFoldingRanges(languageModes, document, nRanges, null);
|
||||
|
||||
let actualRanges = [];
|
||||
for (let i = 0; i < actual.length; i++) {
|
||||
actualRanges[i] = r(actual[i].startLine, actual[i].endLine, actual[i].kind);
|
||||
}
|
||||
actualRanges = actualRanges.sort((r1, r2) => r1.startLine - r2.startLine);
|
||||
assert.deepEqual(actualRanges, expected, message);
|
||||
}
|
||||
|
||||
function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRange {
|
||||
return { startLine, endLine, kind };
|
||||
}
|
||||
|
||||
suite('HTML Folding', async () => {
|
||||
|
||||
test('Embedded JavaScript', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script>',
|
||||
/*3*/'function f() {',
|
||||
/*4*/'}',
|
||||
/*5*/'</script>',
|
||||
/*6*/'</head>',
|
||||
/*7*/'</html>',
|
||||
];
|
||||
await await assertRanges(input, [r(0, 6), r(1, 5), r(2, 4), r(3, 4)]);
|
||||
});
|
||||
|
||||
test('Embedded JavaScript - multiple areas', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head>',
|
||||
/* 2*/'<script>',
|
||||
/* 3*/' var x = {',
|
||||
/* 4*/' foo: true,',
|
||||
/* 5*/' bar: {}',
|
||||
/* 6*/' };',
|
||||
/* 7*/'</script>',
|
||||
/* 8*/'<script>',
|
||||
/* 9*/' test(() => { // hello',
|
||||
/*10*/' f();',
|
||||
/*11*/' });',
|
||||
/*12*/'</script>',
|
||||
/*13*/'</head>',
|
||||
/*14*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6), r(8, 11), r(9, 11), r(9, 11)]);
|
||||
});
|
||||
|
||||
test('Embedded JavaScript - incomplete', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head>',
|
||||
/* 2*/'<script>',
|
||||
/* 3*/' var x = {',
|
||||
/* 4*/'</script>',
|
||||
/* 5*/'<script>',
|
||||
/* 6*/' });',
|
||||
/* 7*/'</script>',
|
||||
/* 8*/'</head>',
|
||||
/* 9*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 8), r(1, 7), r(2, 3), r(5, 6)]);
|
||||
});
|
||||
|
||||
test('Embedded JavaScript - regions', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head>',
|
||||
/* 2*/'<script>',
|
||||
/* 3*/' // #region Lalala',
|
||||
/* 4*/' // #region',
|
||||
/* 5*/' x = 9;',
|
||||
/* 6*/' // #endregion',
|
||||
/* 7*/' // #endregion Lalala',
|
||||
/* 8*/'</script>',
|
||||
/* 9*/'</head>',
|
||||
/*10*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
|
||||
});
|
||||
|
||||
test('Embedded CSS', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head>',
|
||||
/* 2*/'<style>',
|
||||
/* 3*/' foo {',
|
||||
/* 4*/' display: block;',
|
||||
/* 5*/' color: black;',
|
||||
/* 6*/' }',
|
||||
/* 7*/'</style>',
|
||||
/* 8*/'</head>',
|
||||
/* 9*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 8), r(1, 7), r(2, 6), r(3, 5)]);
|
||||
});
|
||||
|
||||
test('Embedded CSS - multiple areas', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head style="color:red">',
|
||||
/* 2*/'<style>',
|
||||
/* 3*/' /*',
|
||||
/* 4*/' foo: true,',
|
||||
/* 5*/' bar: {}',
|
||||
/* 6*/' */',
|
||||
/* 7*/'</style>',
|
||||
/* 8*/'<style>',
|
||||
/* 9*/' @keyframes mymove {',
|
||||
/*10*/' from {top: 0px;}',
|
||||
/*11*/' }',
|
||||
/*12*/'</style>',
|
||||
/*13*/'</head>',
|
||||
/*14*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6, 'comment'), r(8, 11), r(9, 10)]);
|
||||
});
|
||||
|
||||
test('Embedded CSS - regions', async () => {
|
||||
const input = [
|
||||
/* 0*/'<html>',
|
||||
/* 1*/'<head>',
|
||||
/* 2*/'<style>',
|
||||
/* 3*/' /* #region Lalala */',
|
||||
/* 4*/' /* #region*/',
|
||||
/* 5*/' x = 9;',
|
||||
/* 6*/' /* #endregion*/',
|
||||
/* 7*/' /* #endregion Lalala*/',
|
||||
/* 8*/'</style>',
|
||||
/* 9*/'</head>',
|
||||
/*10*/'</html>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
|
||||
});
|
||||
|
||||
|
||||
// test('Embedded JavaScript - multi line comment', async () => {
|
||||
// const input = [
|
||||
// /* 0*/'<html>',
|
||||
// /* 1*/'<head>',
|
||||
// /* 2*/'<script>',
|
||||
// /* 3*/' /*',
|
||||
// /* 4*/' * Hello',
|
||||
// /* 5*/' */',
|
||||
// /* 6*/'</script>',
|
||||
// /* 7*/'</head>',
|
||||
// /* 8*/'</html>',
|
||||
// ];
|
||||
// await assertRanges(input, [r(0, 7), r(1, 6), r(2, 5), r(3, 5, 'comment')]);
|
||||
// });
|
||||
|
||||
test('Test limit', async () => {
|
||||
const input = [
|
||||
/* 0*/'<div>',
|
||||
/* 1*/' <span>',
|
||||
/* 2*/' <b>',
|
||||
/* 3*/' ',
|
||||
/* 4*/' </b>,',
|
||||
/* 5*/' <b>',
|
||||
/* 6*/' <pre>',
|
||||
/* 7*/' ',
|
||||
/* 8*/' </pre>,',
|
||||
/* 9*/' <pre>',
|
||||
/*10*/' ',
|
||||
/*11*/' </pre>,',
|
||||
/*12*/' </b>,',
|
||||
/*13*/' <b>',
|
||||
/*14*/' ',
|
||||
/*15*/' </b>,',
|
||||
/*16*/' <b>',
|
||||
/*17*/' ',
|
||||
/*18*/' </b>',
|
||||
/*19*/' </span>',
|
||||
/*20*/'</div>',
|
||||
];
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'no limit', undefined);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(9, 10), r(13, 14), r(16, 17)], 'limit 8', 8);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(6, 7), r(13, 14), r(16, 17)], 'limit 7', 7);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14)], 'limit 5', 5);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11)], 'limit 4', 4);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18), r(2, 3)], 'limit 3', 3);
|
||||
await assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2);
|
||||
await assertRanges(input, [r(0, 19)], 'limit 1', 1);
|
||||
});
|
||||
|
||||
});
|
@ -0,0 +1,210 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { getLanguageModes, TextDocument, Range, FormattingOptions, ClientCapabilities } from '../modes/languageModes';
|
||||
|
||||
import { format } from '../modes/formatting';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
|
||||
suite('HTML Embedded Formatting', () => {
|
||||
|
||||
async function assertFormat(value: string, expected: string, options?: any, formatOptions?: FormattingOptions, message?: string): Promise<void> {
|
||||
let workspace = {
|
||||
settings: options,
|
||||
folders: [{ name: 'foo', uri: 'test://foo' }]
|
||||
};
|
||||
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
|
||||
|
||||
let rangeStartOffset = value.indexOf('|');
|
||||
let rangeEndOffset;
|
||||
if (rangeStartOffset !== -1) {
|
||||
value = value.substr(0, rangeStartOffset) + value.substr(rangeStartOffset + 1);
|
||||
|
||||
rangeEndOffset = value.indexOf('|');
|
||||
value = value.substr(0, rangeEndOffset) + value.substr(rangeEndOffset + 1);
|
||||
} else {
|
||||
rangeStartOffset = 0;
|
||||
rangeEndOffset = value.length;
|
||||
}
|
||||
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
|
||||
let range = Range.create(document.positionAt(rangeStartOffset), document.positionAt(rangeEndOffset));
|
||||
if (!formatOptions) {
|
||||
formatOptions = FormattingOptions.create(2, true);
|
||||
}
|
||||
|
||||
let result = await format(languageModes, document, range, formatOptions, undefined, { css: true, javascript: true });
|
||||
|
||||
let actual = TextDocument.applyEdits(document, result);
|
||||
assert.equal(actual, expected, message);
|
||||
}
|
||||
|
||||
async function assertFormatWithFixture(fixtureName: string, expectedPath: string, options?: any, formatOptions?: FormattingOptions): Promise<void> {
|
||||
let input = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'test', 'fixtures', 'inputs', fixtureName)).toString().replace(/\r\n/mg, '\n');
|
||||
let expected = fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'test', 'fixtures', 'expected', expectedPath)).toString().replace(/\r\n/mg, '\n');
|
||||
await assertFormat(input, expected, options, formatOptions, expectedPath);
|
||||
}
|
||||
|
||||
test('HTML only', async () => {
|
||||
await assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
|
||||
await assertFormat('|<html><body><p>Hello</p></body></html>|', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
|
||||
await assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>');
|
||||
});
|
||||
|
||||
test('HTML & Scripts', async () => {
|
||||
await assertFormat('<html><head><script></script></head></html>', '<html>\n\n<head>\n <script></script>\n</head>\n\n</html>');
|
||||
await assertFormat('<html><head><script>var x=1;</script></head></html>', '<html>\n\n<head>\n <script>var x = 1;</script>\n</head>\n\n</html>');
|
||||
await assertFormat('<html><head><script>\nvar x=2;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 2;\n </script>\n</head>\n\n</html>');
|
||||
await assertFormat('<html><head>\n <script>\nvar x=3;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 3;\n </script>\n</head>\n\n</html>');
|
||||
await assertFormat('<html><head>\n <script>\nvar x=4;\nconsole.log("Hi");\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 4;\n console.log("Hi");\n </script>\n</head>\n\n</html>');
|
||||
await assertFormat('<html><head>\n |<script>\nvar x=5;\n</script>|</head></html>', '<html><head>\n <script>\n var x = 5;\n </script></head></html>');
|
||||
});
|
||||
|
||||
test('HTLM & Scripts - Fixtures', async () => {
|
||||
assertFormatWithFixture('19813.html', '19813.html');
|
||||
assertFormatWithFixture('19813.html', '19813-4spaces.html', undefined, FormattingOptions.create(4, true));
|
||||
assertFormatWithFixture('19813.html', '19813-tab.html', undefined, FormattingOptions.create(1, false));
|
||||
assertFormatWithFixture('21634.html', '21634.html');
|
||||
});
|
||||
|
||||
test('Script end tag', async () => {
|
||||
await assertFormat('<html>\n<head>\n <script>\nvar x = 0;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 0;\n </script>\n</head>\n\n</html>');
|
||||
});
|
||||
|
||||
test('HTML & Multiple Scripts', async () => {
|
||||
await assertFormat('<html><head>\n<script>\nif(x){\nbar(); }\n</script><script>\nfunction(x){ }\n</script></head></html>', '<html>\n\n<head>\n <script>\n if (x) {\n bar();\n }\n </script>\n <script>\n function(x) {}\n </script>\n</head>\n\n</html>');
|
||||
});
|
||||
|
||||
test('HTML & Styles', async () => {
|
||||
await assertFormat('<html><head>\n<style>\n.foo{display:none;}\n</style></head></html>', '<html>\n\n<head>\n <style>\n .foo {\n display: none;\n }\n </style>\n</head>\n\n</html>');
|
||||
});
|
||||
|
||||
test('EndWithNewline', async () => {
|
||||
let options = {
|
||||
html: {
|
||||
format: {
|
||||
endWithNewline: true
|
||||
}
|
||||
}
|
||||
};
|
||||
await assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>\n', options);
|
||||
await assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>', options);
|
||||
await assertFormat('<html><head><script>\nvar x=1;\n</script></head></html>', '<html>\n\n<head>\n <script>\n var x = 1;\n </script>\n</head>\n\n</html>\n', options);
|
||||
});
|
||||
|
||||
test('Inside script', async () => {
|
||||
await assertFormat('<html><head>\n <script>\n|var x=6;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n</script></head></html>');
|
||||
await assertFormat('<html><head>\n <script>\n|var x=6;\nvar y= 9;|\n</script></head></html>', '<html><head>\n <script>\n var x = 6;\n var y = 9;\n</script></head></html>');
|
||||
});
|
||||
|
||||
test('Range after new line', async () => {
|
||||
await assertFormat('<html><head>\n |<script>\nvar x=6;\n</script>\n|</head></html>', '<html><head>\n <script>\n var x = 6;\n </script>\n</head></html>');
|
||||
});
|
||||
|
||||
test('bug 36574', async () => {
|
||||
await assertFormat('<script src="/js/main.js"> </script>', '<script src="/js/main.js"> </script>');
|
||||
});
|
||||
|
||||
test('bug 48049', async () => {
|
||||
await assertFormat(
|
||||
[
|
||||
'<html>',
|
||||
'<head>',
|
||||
'</head>',
|
||||
'',
|
||||
'<body>',
|
||||
'',
|
||||
' <script>',
|
||||
' function f(x) {}',
|
||||
' f(function () {',
|
||||
' // ',
|
||||
'',
|
||||
' console.log(" vsc crashes on formatting")',
|
||||
' });',
|
||||
' </script>',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
' </body>',
|
||||
'',
|
||||
'</html>'
|
||||
].join('\n'),
|
||||
[
|
||||
'<html>',
|
||||
'',
|
||||
'<head>',
|
||||
'</head>',
|
||||
'',
|
||||
'<body>',
|
||||
'',
|
||||
' <script>',
|
||||
' function f(x) {}',
|
||||
' f(function () {',
|
||||
' // ',
|
||||
'',
|
||||
' console.log(" vsc crashes on formatting")',
|
||||
' });',
|
||||
' </script>',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'</body>',
|
||||
'',
|
||||
'</html>'
|
||||
].join('\n')
|
||||
);
|
||||
});
|
||||
test('#58435', async () => {
|
||||
let options = {
|
||||
html: {
|
||||
format: {
|
||||
contentUnformatted: 'textarea'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const content = [
|
||||
'<html>',
|
||||
'',
|
||||
'<body>',
|
||||
' <textarea name= "" id ="" cols="30" rows="10">',
|
||||
' </textarea>',
|
||||
'</body>',
|
||||
'',
|
||||
'</html>',
|
||||
].join('\n');
|
||||
|
||||
const expected = [
|
||||
'<html>',
|
||||
'',
|
||||
'<body>',
|
||||
' <textarea name="" id="" cols="30" rows="10">',
|
||||
' </textarea>',
|
||||
'</body>',
|
||||
'',
|
||||
'</html>',
|
||||
].join('\n');
|
||||
|
||||
await assertFormat(content, expected, options);
|
||||
});
|
||||
|
||||
}); /*
|
||||
content_unformatted: Array(4)["pre", "code", "textarea", …]
|
||||
end_with_newline: false
|
||||
eol: "\n"
|
||||
extra_liners: Array(3)["head", "body", "/html"]
|
||||
indent_char: "\t"
|
||||
indent_handlebars: false
|
||||
indent_inner_html: false
|
||||
indent_size: 1
|
||||
max_preserve_newlines: 32786
|
||||
preserve_newlines: true
|
||||
unformatted: Array(1)["wbr"]
|
||||
wrap_attributes: "auto"
|
||||
wrap_attributes_indent_size: undefined
|
||||
wrap_line_length: 120*/
|
@ -0,0 +1,4 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
@ -0,0 +1,4 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
@ -0,0 +1,4 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
@ -0,0 +1,4 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
@ -0,0 +1,4 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
@ -0,0 +1,81 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import { getLanguageModes, ClientCapabilities, TextDocument, SelectionRange} from '../modes/languageModes';
|
||||
import { getSelectionRanges } from '../modes/selectionRanges';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
|
||||
async function assertRanges(content: string, expected: (number | string)[][]): Promise<void> {
|
||||
let message = `${content} gives selection range:\n`;
|
||||
|
||||
const offset = content.indexOf('|');
|
||||
content = content.substr(0, offset) + content.substr(offset + 1);
|
||||
|
||||
let workspace = {
|
||||
settings: {},
|
||||
folders: [{ name: 'foo', uri: 'test://foo' }]
|
||||
};
|
||||
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
|
||||
|
||||
const document = TextDocument.create('test://foo.html', 'html', 1, content);
|
||||
const actualRanges = await getSelectionRanges(languageModes, document, [document.positionAt(offset)]);
|
||||
assert.equal(actualRanges.length, 1);
|
||||
const offsetPairs: [number, string][] = [];
|
||||
let curr: SelectionRange | undefined = actualRanges[0];
|
||||
while (curr) {
|
||||
offsetPairs.push([document.offsetAt(curr.range.start), document.getText(curr.range)]);
|
||||
curr = curr.parent;
|
||||
}
|
||||
|
||||
message += `${JSON.stringify(offsetPairs)}\n but should give:\n${JSON.stringify(expected)}\n`;
|
||||
assert.deepEqual(offsetPairs, expected, message);
|
||||
}
|
||||
|
||||
suite('HTML SelectionRange', () => {
|
||||
test('Embedded JavaScript', async () => {
|
||||
await assertRanges('<html><head><script> function foo() { return ((1|+2)*6) }</script></head></html>', [
|
||||
[48, '1'],
|
||||
[48, '1+2'],
|
||||
[47, '(1+2)'],
|
||||
[47, '(1+2)*6'],
|
||||
[46, '((1+2)*6)'],
|
||||
[39, 'return ((1+2)*6)'],
|
||||
[22, 'function foo() { return ((1+2)*6) }'],
|
||||
[20, ' function foo() { return ((1+2)*6) }'],
|
||||
[12, '<script> function foo() { return ((1+2)*6) }</script>'],
|
||||
[6, '<head><script> function foo() { return ((1+2)*6) }</script></head>'],
|
||||
[0, '<html><head><script> function foo() { return ((1+2)*6) }</script></head></html>'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('Embedded CSS', async () => {
|
||||
await assertRanges('<html><head><style>foo { display: |none; } </style></head></html>', [
|
||||
[34, 'none'],
|
||||
[25, 'display: none'],
|
||||
[24, ' display: none; '],
|
||||
[23, '{ display: none; }'],
|
||||
[19, 'foo { display: none; }'],
|
||||
[19, 'foo { display: none; } '],
|
||||
[12, '<style>foo { display: none; } </style>'],
|
||||
[6, '<head><style>foo { display: none; } </style></head>'],
|
||||
[0, '<html><head><style>foo { display: none; } </style></head></html>'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('Embedded style', async () => {
|
||||
await assertRanges('<div style="color: |red"></div>', [
|
||||
[19, 'red'],
|
||||
[12, 'color: red'],
|
||||
[11, '"color: red"'],
|
||||
[5, 'style="color: red"'],
|
||||
[1, 'div style="color: red"'],
|
||||
[0, '<div style="color: red"></div>']
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -0,0 +1,228 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'mocha';
|
||||
import * as assert from 'assert';
|
||||
import { TextDocument, getLanguageModes, ClientCapabilities, Range, Position } from '../modes/languageModes';
|
||||
import { newSemanticTokenProvider } from '../modes/semanticTokens';
|
||||
import { getNodeFSRequestService } from '../node/nodeFs';
|
||||
|
||||
interface ExpectedToken {
|
||||
startLine: number;
|
||||
character: number;
|
||||
length: number;
|
||||
tokenClassifiction: string;
|
||||
}
|
||||
|
||||
async function assertTokens(lines: string[], expected: ExpectedToken[], ranges?: Range[], message?: string): Promise<void> {
|
||||
const document = TextDocument.create('test://foo/bar.html', 'html', 1, lines.join('\n'));
|
||||
const workspace = {
|
||||
settings: {},
|
||||
folders: [{ name: 'foo', uri: 'test://foo' }]
|
||||
};
|
||||
const languageModes = getLanguageModes({ css: true, javascript: true }, workspace, ClientCapabilities.LATEST, getNodeFSRequestService());
|
||||
const semanticTokensProvider = newSemanticTokenProvider(languageModes);
|
||||
|
||||
const legend = semanticTokensProvider.legend;
|
||||
const actual = await semanticTokensProvider.getSemanticTokens(document, ranges);
|
||||
|
||||
let actualRanges = [];
|
||||
let lastLine = 0;
|
||||
let lastCharacter = 0;
|
||||
for (let i = 0; i < actual.length; i += 5) {
|
||||
const lineDelta = actual[i], charDelta = actual[i + 1], len = actual[i + 2], typeIdx = actual[i + 3], modSet = actual[i + 4];
|
||||
const line = lastLine + lineDelta;
|
||||
const character = lineDelta === 0 ? lastCharacter + charDelta : charDelta;
|
||||
const tokenClassifiction = [legend.types[typeIdx], ...legend.modifiers.filter((_, i) => modSet & 1 << i)].join('.');
|
||||
actualRanges.push(t(line, character, len, tokenClassifiction));
|
||||
lastLine = line;
|
||||
lastCharacter = character;
|
||||
}
|
||||
assert.deepEqual(actualRanges, expected, message);
|
||||
}
|
||||
|
||||
function t(startLine: number, character: number, length: number, tokenClassifiction: string): ExpectedToken {
|
||||
return { startLine, character, length, tokenClassifiction };
|
||||
}
|
||||
|
||||
suite('HTML Semantic Tokens', () => {
|
||||
|
||||
test('Variables', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script>',
|
||||
/*3*/' var x = 9, y1 = [x];',
|
||||
/*4*/' try {',
|
||||
/*5*/' for (const s of y1) { x = s }',
|
||||
/*6*/' } catch (e) {',
|
||||
/*7*/' throw y1;',
|
||||
/*8*/' }',
|
||||
/*9*/'</script>',
|
||||
/*10*/'</head>',
|
||||
/*11*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 6, 1, 'variable.declaration'), t(3, 13, 2, 'variable.declaration'), t(3, 19, 1, 'variable'),
|
||||
t(5, 15, 1, 'variable.declaration.readonly'), t(5, 20, 2, 'variable'), t(5, 26, 1, 'variable'), t(5, 30, 1, 'variable.readonly'),
|
||||
t(6, 11, 1, 'variable.declaration'),
|
||||
t(7, 10, 2, 'variable')
|
||||
]);
|
||||
});
|
||||
|
||||
test('Functions', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script>',
|
||||
/*3*/' function foo(p1) {',
|
||||
/*4*/' return foo(Math.abs(p1))',
|
||||
/*5*/' }',
|
||||
/*6*/' `/${window.location}`.split("/").forEach(s => foo(s));',
|
||||
/*7*/'</script>',
|
||||
/*8*/'</head>',
|
||||
/*9*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 11, 3, 'function.declaration'), t(3, 15, 2, 'parameter.declaration'),
|
||||
t(4, 11, 3, 'function'), t(4, 15, 4, 'interface'), t(4, 20, 3, 'member'), t(4, 24, 2, 'parameter'),
|
||||
t(6, 6, 6, 'variable'), t(6, 13, 8, 'property'), t(6, 24, 5, 'member'), t(6, 35, 7, 'member'), t(6, 43, 1, 'parameter.declaration'), t(6, 48, 3, 'function'), t(6, 52, 1, 'parameter')
|
||||
]);
|
||||
});
|
||||
|
||||
test('Members', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script>',
|
||||
/*3*/' class A {',
|
||||
/*4*/' static x = 9;',
|
||||
/*5*/' f = 9;',
|
||||
/*6*/' async m() { return A.x + await this.m(); };',
|
||||
/*7*/' get s() { return this.f; ',
|
||||
/*8*/' static t() { return new A().f; };',
|
||||
/*9*/' constructor() {}',
|
||||
/*10*/' }',
|
||||
/*11*/'</script>',
|
||||
/*12*/'</head>',
|
||||
/*13*/'</html>',
|
||||
];
|
||||
|
||||
|
||||
await assertTokens(input, [
|
||||
t(3, 8, 1, 'class.declaration'),
|
||||
t(4, 11, 1, 'property.declaration.static'),
|
||||
t(5, 4, 1, 'property.declaration'),
|
||||
t(6, 10, 1, 'member.declaration.async'), t(6, 23, 1, 'class'), t(6, 25, 1, 'property.static'), t(6, 40, 1, 'member.async'),
|
||||
t(7, 8, 1, 'property.declaration'), t(7, 26, 1, 'property'),
|
||||
t(8, 11, 1, 'member.declaration.static'), t(8, 28, 1, 'class'), t(8, 32, 1, 'property'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('Interfaces', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script type="text/typescript">',
|
||||
/*3*/' interface Position { x: number, y: number };',
|
||||
/*4*/' const p = { x: 1, y: 2 } as Position;',
|
||||
/*5*/' const foo = (o: Position) => o.x + o.y;',
|
||||
/*6*/'</script>',
|
||||
/*7*/'</head>',
|
||||
/*8*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 12, 8, 'interface.declaration'), t(3, 23, 1, 'property.declaration'), t(3, 34, 1, 'property.declaration'),
|
||||
t(4, 8, 1, 'variable.declaration.readonly'), t(4, 30, 8, 'interface'),
|
||||
t(5, 8, 3, 'variable.declaration.readonly'), t(5, 15, 1, 'parameter.declaration'), t(5, 18, 8, 'interface'), t(5, 31, 1, 'parameter'), t(5, 33, 1, 'property'), t(5, 37, 1, 'parameter'), t(5, 39, 1, 'property')
|
||||
]);
|
||||
});
|
||||
|
||||
test('Readonly', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script type="text/typescript">',
|
||||
/*3*/' const f = 9;',
|
||||
/*4*/' class A { static readonly t = 9; static url: URL; }',
|
||||
/*5*/' const enum E { A = 9, B = A + 1 }',
|
||||
/*6*/' console.log(f + A.t + A.url.origin);',
|
||||
/*7*/'</script>',
|
||||
/*8*/'</head>',
|
||||
/*9*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 8, 1, 'variable.declaration.readonly'),
|
||||
t(4, 8, 1, 'class.declaration'), t(4, 28, 1, 'property.declaration.static.readonly'), t(4, 42, 3, 'property.declaration.static'), t(4, 47, 3, 'interface'),
|
||||
t(5, 13, 1, 'enum.declaration'), t(5, 17, 1, 'property.declaration.readonly'), t(5, 24, 1, 'property.declaration.readonly'), t(5, 28, 1, 'property.readonly'),
|
||||
t(6, 2, 7, 'variable'), t(6, 10, 3, 'member'), t(6, 14, 1, 'variable.readonly'), t(6, 18, 1, 'class'), t(6, 20, 1, 'property.static.readonly'), t(6, 24, 1, 'class'), t(6, 26, 3, 'property.static'), t(6, 30, 6, 'property.readonly'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
test('Type aliases and type parameters', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script type="text/typescript">',
|
||||
/*3*/' type MyMap = Map<string, number>;',
|
||||
/*4*/' function f<T extends MyMap>(t: T | number) : T { ',
|
||||
/*5*/' return <T> <unknown> new Map<string, MyMap>();',
|
||||
/*6*/' }',
|
||||
/*7*/'</script>',
|
||||
/*8*/'</head>',
|
||||
/*9*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 7, 5, 'type.declaration'), t(3, 15, 3, 'interface') /* to investiagte */,
|
||||
t(4, 11, 1, 'function.declaration'), t(4, 13, 1, 'typeParameter.declaration'), t(4, 23, 5, 'type'), t(4, 30, 1, 'parameter.declaration'), t(4, 33, 1, 'typeParameter'), t(4, 47, 1, 'typeParameter'),
|
||||
t(5, 12, 1, 'typeParameter'), t(5, 29, 3, 'interface'), t(5, 41, 5, 'type'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('TS and JS', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script type="text/typescript">',
|
||||
/*3*/' function f<T>(p1: T): T[] { return [ p1 ]; }',
|
||||
/*4*/'</script>',
|
||||
/*5*/'<script>',
|
||||
/*6*/' window.alert("Hello");',
|
||||
/*7*/'</script>',
|
||||
/*8*/'</head>',
|
||||
/*9*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 11, 1, 'function.declaration'), t(3, 13, 1, 'typeParameter.declaration'), t(3, 16, 2, 'parameter.declaration'), t(3, 20, 1, 'typeParameter'), t(3, 24, 1, 'typeParameter'), t(3, 39, 2, 'parameter'),
|
||||
t(6, 2, 6, 'variable'), t(6, 9, 5, 'member')
|
||||
]);
|
||||
});
|
||||
|
||||
test('Ranges', async () => {
|
||||
const input = [
|
||||
/*0*/'<html>',
|
||||
/*1*/'<head>',
|
||||
/*2*/'<script>',
|
||||
/*3*/' window.alert("Hello");',
|
||||
/*4*/'</script>',
|
||||
/*5*/'<script>',
|
||||
/*6*/' window.alert("World");',
|
||||
/*7*/'</script>',
|
||||
/*8*/'</head>',
|
||||
/*9*/'</html>',
|
||||
];
|
||||
await assertTokens(input, [
|
||||
t(3, 2, 6, 'variable'), t(3, 9, 5, 'member')
|
||||
], [Range.create(Position.create(2, 0), Position.create(4, 0))]);
|
||||
|
||||
await assertTokens(input, [
|
||||
t(6, 2, 6, 'variable'),
|
||||
], [Range.create(Position.create(6, 2), Position.create(6, 8))]);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as assert from 'assert';
|
||||
import * as words from '../utils/strings';
|
||||
|
||||
suite('HTML Words', () => {
|
||||
|
||||
let wordRegex = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
|
||||
|
||||
function assertWord(value: string, expected: string): void {
|
||||
let offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
let actualRange = words.getWordAtText(value, offset, wordRegex);
|
||||
assert(actualRange.start <= offset);
|
||||
assert(actualRange.start + actualRange.length >= offset);
|
||||
assert.equal(value.substr(actualRange.start, actualRange.length), expected);
|
||||
}
|
||||
|
||||
|
||||
test('Basic', function (): any {
|
||||
assertWord('|var x1 = new F<A>(a, b);', 'var');
|
||||
assertWord('v|ar x1 = new F<A>(a, b);', 'var');
|
||||
assertWord('var| x1 = new F<A>(a, b);', 'var');
|
||||
assertWord('var |x1 = new F<A>(a, b);', 'x1');
|
||||
assertWord('var x1| = new F<A>(a, b);', 'x1');
|
||||
assertWord('var x1 = new |F<A>(a, b);', 'F');
|
||||
assertWord('var x1 = new F<|A>(a, b);', 'A');
|
||||
assertWord('var x1 = new F<A>(|a, b);', 'a');
|
||||
assertWord('var x1 = new F<A>(a, b|);', 'b');
|
||||
assertWord('var x1 = new F<A>(a, b)|;', '');
|
||||
assertWord('var x1 = new F<A>(a, b)|;|', '');
|
||||
assertWord('var x1 = | new F<A>(a, b)|;|', '');
|
||||
});
|
||||
|
||||
test('Multiline', function (): any {
|
||||
assertWord('console.log("hello");\n|var x1 = new F<A>(a, b);', 'var');
|
||||
assertWord('console.log("hello");\n|\nvar x1 = new F<A>(a, b);', '');
|
||||
assertWord('console.log("hello");\n\r |var x1 = new F<A>(a, b);', 'var');
|
||||
});
|
||||
|
||||
});
|
Reference in New Issue
Block a user