Archived
1
0

Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@ -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);
*/
});
});

View File

@ -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');
});
});

View File

@ -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(); ');
});
});

View 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>

View 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>

View 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>

View File

@ -0,0 +1,6 @@
<app-route path="/module" element="page-module" bindRouter onUrlChange="updateModel"></app-route>
<script>
Polymer({
});
</script>

View 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>

View File

@ -0,0 +1,6 @@
<app-route path="/module" element="page-module" bindRouter onUrlChange="updateModel"></app-route>
<script>
Polymer({
});
</script>

View File

@ -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);
});
});

View File

@ -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*/

View File

@ -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.
*--------------------------------------------------------------------------------------------*/

View File

@ -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.
*--------------------------------------------------------------------------------------------*/

View File

@ -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.
*--------------------------------------------------------------------------------------------*/

View File

@ -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.
*--------------------------------------------------------------------------------------------*/

View File

@ -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.
*--------------------------------------------------------------------------------------------*/

View File

@ -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>']
]);
});
});

View File

@ -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))]);
});
});

View File

@ -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');
});
});