[html] split extension (for #45900)

This commit is contained in:
Martin Aeschlimann
2018-03-17 16:32:55 +01:00
parent 5b23587f08
commit 086a751a06
63 changed files with 324 additions and 303 deletions

View File

@@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as assert from 'assert';
import * as path from 'path';
import Uri from 'vscode-uri';
import { TextDocument, CompletionList, CompletionItemKind, } from 'vscode-languageserver-types';
import { getLanguageModes } from '../modes/languageModes';
import { getPathCompletionParticipant } from '../modes/pathCompletion';
import { WorkspaceFolder } from 'vscode-languageserver';
export interface ItemDescription {
label: string;
documentation?: string;
kind?: CompletionItemKind;
resultText?: string;
notAvailable?: boolean;
}
suite('Completions', () => {
let assertCompletion = function (completions: CompletionList, expected: ItemDescription, document: TextDocument, offset: number) {
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) {
assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText);
}
};
const testUri = 'test://test/test.html';
function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, uri = testUri, workspaceFolders?: WorkspaceFolder[]): void {
let offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
let document = TextDocument.create(uri, 'html', 0, value);
let position = document.positionAt(offset);
var languageModes = getLanguageModes({ css: true, javascript: true });
var mode = languageModes.getModeAtPosition(document, position)!;
if (!workspaceFolders) {
workspaceFolders = [{ name: 'x', uri: path.dirname(uri) }];
}
let participantResult = CompletionList.create([]);
if (mode.setCompletionParticipants) {
mode.setCompletionParticipants([getPathCompletionParticipant(document, workspaceFolders, participantResult)]);
}
let list = mode.doComplete!(document, position)!;
list.items = list.items.concat(participantResult.items);
if (expected.count) {
assert.equal(list.items, expected.count);
}
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document, offset);
}
}
}
test('HTML Javascript Completions', function (): any {
assertCompletions('<html><script>window.|</script></html>', {
items: [
{ label: 'location', resultText: '<html><script>window.location</script></html>' },
]
});
assertCompletions('<html><script>$.|</script></html>', {
items: [
{ label: 'getJSON', resultText: '<html><script>$.getJSON</script></html>' },
]
});
});
test('Path completion', function (): any {
let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/foo.html')).fsPath;
assertCompletions('<div><a href="about/|">', {
items: [
{ label: 'about.html', resultText: '<div><a href="about/about.html">' }
]
}, testUri);
// Unquoted value is not supported in language service yet
// assertCompletions(`<div><a href=about/|>`, {
// items: [
// { label: 'about.html', resultText: `<div><a href=about/about.html>` }
// ]
// }, testUri);
assertCompletions(`<div><a href='about/|'>`, {
items: [
{ label: 'about.html', resultText: `<div><a href='about/about.html'>` }
]
}, testUri);
// Don't think this is a common use case
// assertCompletions('<div><a href="about/about|.xml">', {
// items: [
// { label: 'about.html', resultText: '<div><a href="about/about.html">' }
// ]
// }, testUri);
assertCompletions('<div><a href="about/a|">', {
items: [
{ label: 'about.html', resultText: '<div><a href="about/about.html">' }
]
}, testUri);
// We should not prompt suggestion before user enters any trigger character
// assertCompletions('<div><a href="|">', {
// items: [
// { label: 'index.html', resultText: '<div><a href="index.html">' },
// { label: 'about', resultText: '<div><a href="about/">' }
// ]
// }, testUri);
});
});

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { getDocumentContext } from '../utils/documentContext';
suite('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,128 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as assert from 'assert';
import * as embeddedSupport from '../modes/embeddedSupport';
import { TextDocument } from 'vscode-languageserver-types';
import { getLanguageService } from 'vscode-html-languageservice';
suite('HTML Embedded Support', () => {
var htmlLanguageService = getLanguageService();
function assertLanguageId(value: string, expectedLanguageId: string | undefined): void {
let offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let position = document.positionAt(offset);
let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
let languageId = docRegions.getLanguageAtPosition(position);
assert.equal(languageId, expectedLanguageId);
}
function assertEmbeddedLanguageContent(value: string, languageId: string, expectedContent: string): void {
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let docRegions = embeddedSupport.getDocumentRegions(htmlLanguageService, document);
let 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>', void 0);
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,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as assert from 'assert';
import { getHTMLMode } from '../modes/htmlMode';
import { TextDocument, CompletionList } from 'vscode-languageserver-types';
import { getLanguageModelCache } from '../languageModelCache';
import { getLanguageService } from 'vscode-html-languageservice';
import * as embeddedSupport from '../modes/embeddedSupport';
import { getEmmetCompletionParticipants } from 'vscode-emmet-helper';
import { getCSSMode } from '../modes/cssMode';
suite('Emmet Support', () => {
const htmlLanguageService = getLanguageService();
function assertCompletions(syntax: string, value: string, expectedProposal: string | null, expectedProposalDoc: string | null): void {
const offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
const document = TextDocument.create('test://test/test.' + syntax, syntax, 0, value);
const position = document.positionAt(offset);
const documentRegions = getLanguageModelCache<embeddedSupport.HTMLDocumentRegions>(10, 60, document => embeddedSupport.getDocumentRegions(htmlLanguageService, document));
const mode = syntax === 'html' ? getHTMLMode(htmlLanguageService) : getCSSMode(documentRegions);
const emmetCompletionList = CompletionList.create([], false);
mode.setCompletionParticipants!([getEmmetCompletionParticipants(document, position, document.languageId, {}, emmetCompletionList)]);
const list = mode.doComplete!(document, position);
assert.ok(list);
assert.ok(emmetCompletionList);
if (expectedProposal && expectedProposalDoc) {
let actualLabels = emmetCompletionList.items.map(c => c.label).sort();
let actualDocs = emmetCompletionList.items.map(c => c.documentation).sort();
assert.ok(actualLabels.indexOf(expectedProposal) !== -1, 'Not found:' + expectedProposal + ' is ' + actualLabels.join(', '));
assert.ok(actualDocs.indexOf(expectedProposalDoc) !== -1, 'Not found:' + expectedProposalDoc + ' is ' + actualDocs.join(', '));
} else {
assert.ok(!emmetCompletionList.items.length && !emmetCompletionList.isIncomplete);
}
}
test('Html Emmet Completions', function (): any {
assertCompletions('html', 'ul|', 'ul', '<ul>|</ul>');
assertCompletions('html', '<ul|', null, null);
assertCompletions('html', '<html>ul|</html>', 'ul', '<ul>|</ul>');
assertCompletions('html', '<img src=|', null, null);
assertCompletions('html', '<div class=|/>', null, null);
});
test('Css Emmet Completions', function (): any {
assertCompletions('css', '<style>.foo { display: none; m10| }</style>', 'margin: 10px;', 'margin: 10px;');
assertCompletions('css', '<style>foo { display: none; pos:f| }</style>', 'position: fixed;', 'position: fixed;');
assertCompletions('css', '<style>foo { display: none; margin: a| }</style>', null, null);
assertCompletions('css', '<style>foo| { display: none; }</style>', null, null);
assertCompletions('css', '<style>foo {| display: none; }</style>', null, null);
assertCompletions('css', '<style>foo { display: none;| }</style>', null, null);
assertCompletions('css', '<style>foo { display: none|; }</style>', null, null);
assertCompletions('css', '<style>.foo { display: none; -m-m10| }</style>', 'margin: 10px;', '-moz-margin: 10px;\nmargin: 10px;');
});
});

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,259 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as assert from 'assert';
import { TextDocument } from 'vscode-languageserver';
import { getFoldingRegions } from '../modes/htmlFolding';
import { getLanguageModes } from '../modes/languageModes';
interface ExpectedIndentRange {
startLine: number;
endLine: number;
type?: string;
}
function assertRanges(lines: string[], expected: ExpectedIndentRange[], message?: string, nRanges?: number): void {
let document = TextDocument.create('test://foo/bar.json', 'json', 1, lines.join('\n'));
let languageModes = getLanguageModes({ css: true, javascript: true });
let actual = getFoldingRegions(languageModes, document, nRanges, null)!.ranges;
let actualRanges = [];
for (let i = 0; i < actual.length; i++) {
actualRanges[i] = r(actual[i].startLine, actual[i].endLine, actual[i].type);
}
actualRanges = actualRanges.sort((r1, r2) => r1.startLine - r2.startLine);
assert.deepEqual(actualRanges, expected, message);
}
function r(startLine: number, endLine: number, type?: string): ExpectedIndentRange {
return { startLine, endLine, type };
}
suite('Object Folding', () => {
test('Fold one level', () => {
let input = [
/*0*/'<html>',
/*1*/'Hello',
/*2*/'</html>'
];
assertRanges(input, [r(0, 1)]);
});
test('Fold two level', () => {
let input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'Hello',
/*3*/'</head>',
/*4*/'</html>'
];
assertRanges(input, [r(0, 3), r(1, 2)]);
});
test('Fold siblings', () => {
let input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'Head',
/*3*/'</head>',
/*4*/'<body class="f">',
/*5*/'Body',
/*6*/'</body>',
/*7*/'</html>'
];
assertRanges(input, [r(0, 6), r(1, 2), r(4, 5)]);
});
test('Fold self-closing tags', () => {
let input = [
/*0*/'<div>',
/*1*/'<a href="top"/>',
/*2*/'<img src="s">',
/*3*/'<br/>',
/*4*/'<br>',
/*5*/'<img class="c"',
/*6*/' src="top"',
/*7*/'>',
/*8*/'</div>'
];
assertRanges(input, [r(0, 7), r(5, 6)]);
});
test('Fold commment', () => {
let input = [
/*0*/'<!--',
/*1*/' multi line',
/*2*/'-->',
/*3*/'<!-- some stuff',
/*4*/' some more stuff -->',
];
assertRanges(input, [r(0, 2, 'comment'), r(3, 4, 'comment')]);
});
test('Fold regions', () => {
let input = [
/*0*/'<!-- #region -->',
/*1*/'<!-- #region -->',
/*2*/'<!-- #endregion -->',
/*3*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3, 'region'), r(1, 2, 'region')]);
});
test('Embedded JavaScript', () => {
let input = [
/*0*/'<html>',
/*1*/'<head>',
/*2*/'<script>',
/*3*/'function f() {',
/*4*/'}',
/*5*/'</script>',
/*6*/'</head>',
/*7*/'</html>',
];
assertRanges(input, [r(0, 6), r(1, 5), r(2, 4), r(3, 4)]);
});
test('Embedded JavaScript - mutiple areas', () => {
let input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<script>',
/* 3*/' var x = {',
/* 4*/' foo: true,',
/* 5*/' bar: {}',
/* 6*/' };',
/* 7*/'</script>',
/* 8*/'<script>',
/* 9*/' test(() => {',
/*10*/' f();',
/*11*/' });',
/*12*/'</script>',
/*13*/'</head>',
/*14*/'</html>',
];
assertRanges(input, [r(0, 13), r(1, 12), r(2, 6), r(3, 6), r(8, 11), r(9, 11)]);
});
test('Embedded JavaScript - incomplete', () => {
let input = [
/* 0*/'<html>',
/* 1*/'<head>',
/* 2*/'<script>',
/* 3*/' var x = {',
/* 4*/'</script>',
/* 5*/'<script>',
/* 6*/' });',
/* 7*/'</script>',
/* 8*/'</head>',
/* 9*/'</html>',
];
assertRanges(input, [r(0, 8), r(1, 7), r(2, 3), r(5, 6)]);
});
test('Embedded JavaScript - regions', () => {
let 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>',
];
assertRanges(input, [r(0, 9), r(1, 8), r(2, 7), r(3, 7, 'region'), r(4, 6, 'region')]);
});
// test('Embedded JavaScript - mulit line comment', () => {
// let input = [
// /* 0*/'<html>',
// /* 1*/'<head>',
// /* 2*/'<script>',
// /* 3*/' /*',
// /* 4*/' * Hello',
// /* 5*/' */',
// /* 6*/'</script>',
// /* 7*/'</head>',
// /* 8*/'</html>',
// ];
// assertRanges(input, [r(0, 7), r(1, 6), r(2, 5), r(3, 5, 'comment')]);
// });
test('Fold incomplete', () => {
let input = [
/*0*/'<body>',
/*1*/'<div></div>',
/*2*/'Hello',
/*3*/'</div>',
/*4*/'</body>',
];
assertRanges(input, [r(0, 3)]);
});
test('Fold incomplete 2', () => {
let input = [
/*0*/'<be><div>',
/*1*/'<!-- #endregion -->',
/*2*/'</div>',
];
assertRanges(input, [r(0, 1)]);
});
test('Fold intersecting region', () => {
let input = [
/*0*/'<body>',
/*1*/'<!-- #region -->',
/*2*/'Hello',
/*3*/'<div></div>',
/*4*/'</body>',
/*5*/'<!-- #endregion -->',
];
assertRanges(input, [r(0, 3)]);
});
test('Test limit', () => {
let 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>',
];
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', void 0);
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);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 7', 7);
assertRanges(input, [r(0, 19), r(1, 18), r(2, 3), r(5, 11), r(13, 14), r(16, 17)], 'limit 6', 6);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 5', 5);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 4', 4);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 3', 3);
assertRanges(input, [r(0, 19), r(1, 18)], 'limit 2', 2);
assertRanges(input, [r(0, 19)], 'limit 1', 1);
});
});

View File

@@ -0,0 +1,114 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'mocha';
import * as path from 'path';
import * as fs from 'fs';
import * as assert from 'assert';
import { getLanguageModes } from '../modes/languageModes';
import { TextDocument, Range, FormattingOptions } from 'vscode-languageserver-types';
import { format } from '../modes/formatting';
suite('HTML Embedded Formatting', () => {
function assertFormat(value: string, expected: string, options?: any, formatOptions?: FormattingOptions, message?: string): void {
var languageModes = getLanguageModes({ css: true, javascript: true });
if (options) {
languageModes.getAllModes().forEach(m => m.configure!(options));
}
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 = format(languageModes, document, range, formatOptions, void 0, { css: true, javascript: true });
let actual = TextDocument.applyEdits(document, result);
assert.equal(actual, expected, message);
}
function assertFormatWithFixture(fixtureName: string, expectedPath: string, options?: any, formatOptions?: FormattingOptions): void {
let input = fs.readFileSync(path.join(__dirname, 'fixtures', 'inputs', fixtureName)).toString().replace(/\r\n/mg, '\n');
let expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'expected', expectedPath)).toString().replace(/\r\n/mg, '\n');
assertFormat(input, expected, options, formatOptions, expectedPath);
}
test('HTML only', function (): any {
assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
assertFormat('|<html><body><p>Hello</p></body></html>|', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>');
assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>');
});
test('HTML & Scripts', function (): any {
assertFormat('<html><head><script></script></head></html>', '<html>\n\n<head>\n <script></script>\n</head>\n\n</html>');
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>');
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>');
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>');
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>');
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', function () {
assertFormatWithFixture('19813.html', '19813.html');
assertFormatWithFixture('19813.html', '19813-4spaces.html', void 0, FormattingOptions.create(4, true));
assertFormatWithFixture('19813.html', '19813-tab.html', void 0, FormattingOptions.create(1, false));
assertFormatWithFixture('21634.html', '21634.html');
});
test('Script end tag', function (): any {
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', function (): any {
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', function (): any {
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', function (): any {
let options = {
html: {
format: {
endWithNewline: true
}
}
};
assertFormat('<html><body><p>Hello</p></body></html>', '<html>\n\n<body>\n <p>Hello</p>\n</body>\n\n</html>\n', options);
assertFormat('<html>|<body><p>Hello</p></body>|</html>', '<html><body>\n <p>Hello</p>\n</body></html>', options);
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', function (): any {
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>');
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', function (): any {
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', function (): any {
assertFormat('<script src="/js/main.js"> </script>', '<script src="/js/main.js"> </script>');
});
});

View File

@@ -0,0 +1,237 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as path from 'path';
import { providePathSuggestions } from '../../modes/pathCompletion';
import { CompletionItemKind, Range, Position, CompletionItem, TextEdit, Command } from 'vscode-languageserver-types';
const fixtureRoot = path.resolve(__dirname, '../../../test/pathCompletionFixtures');
function toRange(line: number, startChar: number, endChar: number) {
return Range.create(Position.create(line, startChar), Position.create(line, endChar));
}
function toTextEdit(line: number, startChar: number, endChar: number, newText: string) {
const range = Range.create(Position.create(line, startChar), Position.create(line, endChar));
return TextEdit.replace(range, newText);
}
interface PathSuggestion {
label?: string;
kind?: CompletionItemKind;
textEdit?: TextEdit;
command?: Command;
}
function assertSuggestions(actual: CompletionItem[], expected: PathSuggestion[]) {
assert.equal(actual.length, expected.length, `Suggestions have length ${actual.length} but should have length ${expected.length}`);
for (let i = 0; i < expected.length; i++) {
if (expected[i].label) {
assert.equal(
actual[i].label,
expected[i].label,
`Suggestion ${actual[i].label} should have label ${expected[i].label}`
);
}
if (expected[i].kind) {
assert.equal(actual[i].kind,
expected[i].kind,
`Suggestion ${actual[i].label} has kind ${actual[i].kind} but should have ${expected[i].kind}`
);
}
if (expected[i].textEdit) {
assert.equal(actual[i].textEdit!.newText, expected[i].textEdit!.newText);
assert.deepEqual(actual[i].textEdit!.range, expected[i].textEdit!.range);
}
if (expected[i].command) {
assert.equal(
actual[i].command!.title,
expected[i].command!.title,
`Suggestion ${actual[i].label} has command title ${actual[i].command!.title} but should have command title ${expected[i].command!.title}`
);
assert.equal(
actual[i].command!.command,
expected[i].command!.command,
`Suggestion ${actual[i].label} has command ${actual[i].command!.command} but should have command ${expected[i].command!.command}`
);
}
}
}
suite('Path Completion - Relative Path:', () => {
const mockRange = toRange(0, 3, 5);
test('Current Folder', () => {
const value = './';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath);
assertSuggestions(suggestions, [
{ label: 'about/', kind: CompletionItemKind.Folder },
{ label: 'index.html', kind: CompletionItemKind.File },
{ label: 'src/', kind: CompletionItemKind.Folder }
]);
});
test('Parent Folder:', () => {
const value = '../';
const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath);
assertSuggestions(suggestions, [
{ label: 'about/', kind: CompletionItemKind.Folder },
{ label: 'index.html', kind: CompletionItemKind.File },
{ label: 'src/', kind: CompletionItemKind.Folder }
]);
});
test('Adjacent Folder:', () => {
const value = '../src/';
const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File },
{ label: 'test.js', kind: CompletionItemKind.File }
]);
});
});
suite('Path Completion - Absolute Path:', () => {
const mockRange = toRange(0, 3, 5);
test('Root', () => {
const value = '/';
const activeFileFsPath1 = path.resolve(fixtureRoot, 'index.html');
const activeFileFsPath2 = path.resolve(fixtureRoot, 'about/index.html');
const suggestions1 = providePathSuggestions(value, mockRange, activeFileFsPath1, fixtureRoot);
const suggestions2 = providePathSuggestions(value, mockRange, activeFileFsPath2, fixtureRoot);
const verify = (suggestions: CompletionItem[]) => {
assertSuggestions(suggestions, [
{ label: 'about/', kind: CompletionItemKind.Folder },
{ label: 'index.html', kind: CompletionItemKind.File },
{ label: 'src/', kind: CompletionItemKind.Folder }
]);
};
verify(suggestions1);
verify(suggestions2);
});
test('Sub Folder', () => {
const value = '/src/';
const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File },
{ label: 'test.js', kind: CompletionItemKind.File }
]);
});
});
suite('Path Completion - Folder Commands:', () => {
const mockRange = toRange(0, 3, 5);
test('Folder should have command `editor.action.triggerSuggest', () => {
const value = './';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath);
assertSuggestions(suggestions, [
{ label: 'about/', command: { title: 'Suggest', command: 'editor.action.triggerSuggest' } },
{ label: 'index.html' },
{ label: 'src/', command: { title: 'Suggest', command: 'editor.action.triggerSuggest' } },
]);
});
});
suite('Path Completion - Incomplete Path at End:', () => {
const mockRange = toRange(0, 3, 5);
test('Incomplete Path that starts with slash', () => {
const value = '/src/f';
const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File },
{ label: 'test.js', kind: CompletionItemKind.File }
]);
});
test('Incomplete Path that does not start with slash', () => {
const value = '../src/f';
const activeFileFsPath = path.resolve(fixtureRoot, 'about/about.html');
const suggestions = providePathSuggestions(value, mockRange, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File },
{ label: 'test.js', kind: CompletionItemKind.File }
]);
});
});
suite('Path Completion - No leading dot or slash:', () => {
test('Top level completion', () => {
const value = 's';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const range = toRange(0, 3, 5);
const suggestions = providePathSuggestions(value, range, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'about/', kind: CompletionItemKind.Folder, textEdit: toTextEdit(0, 4, 4, 'about/') },
{ label: 'index.html', kind: CompletionItemKind.File, textEdit: toTextEdit(0, 4, 4, 'index.html') },
{ label: 'src/', kind: CompletionItemKind.Folder, textEdit: toTextEdit(0, 4, 4, 'src/') }
]);
});
test('src/', () => {
const value = 'src/';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const range = toRange(0, 3, 8);
const suggestions = providePathSuggestions(value, range, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File, textEdit: toTextEdit(0, 7, 7, 'feature.js') },
{ label: 'test.js', kind: CompletionItemKind.File, textEdit: toTextEdit(0, 7, 7, 'test.js') }
]);
});
test('src/f', () => {
const value = 'src/f';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const range = toRange(0, 3, 9);
const suggestions = providePathSuggestions(value, range, activeFileFsPath, fixtureRoot);
assertSuggestions(suggestions, [
{ label: 'feature.js', kind: CompletionItemKind.File, textEdit: toTextEdit(0, 7, 8, 'feature.js') },
{ label: 'test.js', kind: CompletionItemKind.File, textEdit: toTextEdit(0, 7, 8, 'test.js') }
]);
});
});
suite('Path Completion - TextEdit:', () => {
test('TextEdit has correct replace text and range', () => {
const value = './';
const activeFileFsPath = path.resolve(fixtureRoot, 'index.html');
const range = toRange(0, 3, 5);
const expectedReplaceRange = toRange(0, 4, 4);
const suggestions = providePathSuggestions(value, range, activeFileFsPath);
assertSuggestions(suggestions, [
{ textEdit: TextEdit.replace(expectedReplaceRange, 'about/') },
{ textEdit: TextEdit.replace(expectedReplaceRange, 'index.html') },
{ textEdit: TextEdit.replace(expectedReplaceRange, 'src/') },
]);
});
});

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as words from '../utils/strings';
suite('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');
});
});