[html] code polish

This commit is contained in:
Martin Aeschlimann 2016-09-14 19:50:24 +02:00
parent e69fb28130
commit 04f9562a1a
14 changed files with 477 additions and 432 deletions

View file

@ -84,7 +84,7 @@ connection.onDocumentHighlight(documentHighlightParams => {
return languageService.findDocumentHighlights(document, documentHighlightParams.position, htmlDocument);
});
function merge(src: any, dst: any) : any {
function merge(src: any, dst: any): any {
for (var key in src) {
if (src.hasOwnProperty(key)) {
dst[key] = src[key];
@ -93,7 +93,7 @@ function merge(src: any, dst: any) : any {
return dst;
}
function getFormattingOptions(formatParams : any) {
function getFormattingOptions(formatParams: any) {
let formatSettings = languageSettings && languageSettings.format;
if (!formatSettings) {
return formatParams;

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import {parse} from './parser/htmlParser';
import {doComplete} from './services/htmlCompletion';
import {format} from './services/htmlFormatter';
@ -26,7 +27,7 @@ export interface HTMLFormatConfiguration {
}
export interface CompletionConfiguration {
[provider:string]:boolean;
[provider: string]: boolean;
}
export declare type HTMLDocument = {};
@ -36,12 +37,12 @@ export interface LanguageService {
findDocumentHighlights(document: TextDocument, position: Position, htmlDocument: HTMLDocument): DocumentHighlight[];
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): CompletionList;
format(document: TextDocument, range: Range, options: HTMLFormatConfiguration): TextEdit[];
findDocumentLinks(document: TextDocument, workspacePath:string): DocumentLink[];
findDocumentLinks(document: TextDocument, workspacePath: string): DocumentLink[];
}
export function getLanguageService() : LanguageService {
export function getLanguageService(): LanguageService {
return {
parseHTMLDocument: (document) => parse(document.getText()),
parseHTMLDocument: document => parse(document.getText()),
doComplete,
format,
findDocumentHighlights,

View file

@ -18,7 +18,7 @@ export class Node {
public get firstChild(): Node { return this.children[0]; }
public get lastChild(): Node { return this.children.length ? this.children[this.children.length - 1] : void 0; }
public findNodeBefore(offset:number) : Node {
public findNodeBefore(offset: number): Node {
let idx = findFirst(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
let child = this.children[idx];
@ -36,7 +36,7 @@ export class Node {
return this;
}
public findNodeAt(offset:number) : Node {
public findNodeAt(offset: number): Node {
let idx = findFirst(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
let child = this.children[idx];
@ -50,16 +50,16 @@ export class Node {
export interface HTMLDocument {
roots: Node[];
findNodeBefore(offset:number) : Node;
findNodeAt(offset:number) : Node;
findNodeBefore(offset: number): Node;
findNodeAt(offset: number): Node;
}
export function parse(text: string) : HTMLDocument {
export function parse(text: string): HTMLDocument {
let scanner = createScanner(text);
let htmlDocument = new Node(0, text.length, [], null);
let curr = htmlDocument;
let endTagStart : number = -1;
let endTagStart: number = -1;
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {

View file

@ -196,7 +196,7 @@ export enum ScannerState {
}
export interface Scanner {
scan() : TokenType;
scan(): TokenType;
getTokenType(): TokenType;
getTokenOffset(): number;
getTokenLength(): number;
@ -210,7 +210,7 @@ const htmlScriptContents = {
'text/x-handlebars-template': true
};
export function createScanner(input: string, initialOffset = 0, initialState: ScannerState = ScannerState.WithinContent) : Scanner {
export function createScanner(input: string, initialOffset = 0, initialState: ScannerState = ScannerState.WithinContent): Scanner {
let stream = new MultiLineStream(input, initialOffset);
let state = initialState;
@ -342,7 +342,7 @@ export function createScanner(input: string, initialOffset = 0, initialState: Sc
state = ScannerState.WithinContent;
} else {
state = ScannerState.WithinScriptContent;
}
}
} else if (lastTag === 'style') {
state = ScannerState.WithinStyleContent;
} else {

View file

@ -39,13 +39,13 @@ import arrays = require('../utils/arrays');
import * as nls from 'vscode-nls';
let localize = nls.loadMessageBundle();
export const EMPTY_ELEMENTS:string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
export const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
export function isEmptyElement(e: string) : boolean {
export function isEmptyElement(e: string): boolean {
return e && arrays.binarySearch(EMPTY_ELEMENTS, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0;
}
export function isSameTag(t1: string, t2: string) : boolean {
export function isSameTag(t1: string, t2: string): boolean {
return t1 && t2 && t1.toLowerCase() === t2.toLowerCase();
}

View file

@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextDocument, Position, CompletionList, CompletionItemKind, Range } from 'vscode-languageserver-types';
import { HTMLDocument } from '../parser/htmlParser';
import { TokenType, createScanner, ScannerState } from '../parser/htmlScanner';
import { getHTML5TagProvider, getAngularTagProvider, getIonicTagProvider } from '../parser/htmlTags';
import { getRazorTagProvider } from '../parser/razorTags';
import { CompletionConfiguration } from '../htmlLanguageService';
import {TextDocument, Position, CompletionList, CompletionItemKind, Range} from 'vscode-languageserver-types';
import {HTMLDocument} from '../parser/htmlParser';
import {TokenType, createScanner, ScannerState} from '../parser/htmlScanner';
import {getHTML5TagProvider, getAngularTagProvider, getIonicTagProvider} from '../parser/htmlTags';
import {getRazorTagProvider} from '../parser/razorTags';
import {CompletionConfiguration} from '../htmlLanguageService';
let allTagProviders = [
getHTML5TagProvider(),
@ -35,14 +35,14 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
let currentTag: string;
let currentAttributeName: string;
function getReplaceRange(replaceStart: number) : Range {
function getReplaceRange(replaceStart: number): Range {
if (replaceStart > offset) {
replaceStart = offset;
}
return { start: document.positionAt(replaceStart), end: document.positionAt(offset)};
return { start: document.positionAt(replaceStart), end: document.positionAt(offset) };
}
function collectOpenTagSuggestions(afterOpenBracket: number) : CompletionList {
function collectOpenTagSuggestions(afterOpenBracket: number): CompletionList {
let range = getReplaceRange(afterOpenBracket);
tagProviders.forEach((provider) => {
provider.collectTags((tag, label) => {
@ -57,7 +57,7 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
return result;
}
function collectCloseTagSuggestions(afterOpenBracket: number, matchingOnly: boolean) : CompletionList {
function collectCloseTagSuggestions(afterOpenBracket: number, matchingOnly: boolean): CompletionList {
let range = getReplaceRange(afterOpenBracket);
let contentAfter = document.getText().substr(offset);
let closeTag = contentAfter.match(/^\s*>/) ? '' : '>';
@ -93,13 +93,13 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
return result;
}
function collectTagSuggestions(tagStart: number) : CompletionList {
function collectTagSuggestions(tagStart: number): CompletionList {
collectOpenTagSuggestions(tagStart);
collectCloseTagSuggestions(tagStart, true);
return result;
}
function collectAttributeNameSuggestions(nameStart: number) : CompletionList {
function collectAttributeNameSuggestions(nameStart: number): CompletionList {
let range = getReplaceRange(nameStart);
tagProviders.forEach((provider) => {
provider.collectAttributes(currentTag, (attribute, type) => {
@ -117,7 +117,7 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
return result;
}
function collectAttributeValueSuggestions(valueStart: number) : CompletionList {
function collectAttributeValueSuggestions(valueStart: number): CompletionList {
let range = getReplaceRange(valueStart);
tagProviders.forEach((provider) => {
provider.collectValues(currentTag, currentAttributeName, (value) => {
@ -206,6 +206,6 @@ export function doComplete(document: TextDocument, position: Position, doc: HTML
return result;
}
function isWhiteSpace(s:string) : boolean {
function isWhiteSpace(s: string): boolean {
return /^\s*$/.test(s);
}

View file

@ -15,7 +15,7 @@ export function format(document: TextDocument, range: Range, options: HTMLFormat
let endOffset = document.offsetAt(range.end);
value = value.substring(startOffset, endOffset);
}
let htmlOptions : IBeautifyHTMLOptions = {
let htmlOptions: IBeautifyHTMLOptions = {
indent_size: options.insertSpaces ? options.tabSize : 1,
indent_char: options.insertSpaces ? ' ' : '\t',
wrap_line_length: getFormatOption(options, 'wrapLineLength', 120),
@ -46,7 +46,7 @@ function getFormatOption(options: HTMLFormatConfiguration, key: string, dflt: an
}
function getTagsFormatOption(options: HTMLFormatConfiguration, key: string, dflt: string[]): string[] {
let list = <string> getFormatOption(options, key, null);
let list = <string>getFormatOption(options, key, null);
if (typeof list === 'string') {
if (list.length > 0) {
return list.split(',').map(t => t.trim().toLowerCase());

View file

@ -36,14 +36,14 @@ function covers(range: Range, position: Position) {
return isBeforeOrEqual(range.start, position) && isBeforeOrEqual(position, range.end);
}
function getTagNameRange(tokenType: TokenType, document: TextDocument, startOffset: number) : Range {
function getTagNameRange(tokenType: TokenType, document: TextDocument, startOffset: number): Range {
let scanner = createScanner(document.getText(), startOffset);
let token = scanner.scan();
while (token !== TokenType.EOS && token !== tokenType) {
token = scanner.scan();
}
if (token !== TokenType.EOS) {
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
}
return null;
}

View file

@ -14,8 +14,8 @@ import {DocumentLink} from '../htmlLanguageService';
function stripQuotes(url: string): string {
return url
.replace(/^'([^']+)'$/,(substr, match1) => match1)
.replace(/^"([^"]+)"$/,(substr, match1) => match1);
.replace(/^'([^']+)'$/, (substr, match1) => match1)
.replace(/^"([^"]+)"$/, (substr, match1) => match1);
}
function getWorkspaceUrl(modelAbsoluteUri: Uri, rootAbsoluteUrl: Uri, tokenContent: string): string {
@ -79,7 +79,7 @@ function createLink(document: TextDocument, rootAbsoluteUrl: Uri, attributeValue
};
}
export function findDocumentLinks(document: TextDocument, workspacePath:string): DocumentLink[] {
export function findDocumentLinks(document: TextDocument, workspacePath: string): DocumentLink[] {
let newLinks: DocumentLink[] = [];
let rootAbsoluteUrl: Uri = null;
@ -111,7 +111,7 @@ export function findDocumentLinks(document: TextDocument, workspacePath:string):
}
break;
}
token = scanner.scan();
token = scanner.scan();
}
return newLinks;
}

View file

@ -7,7 +7,7 @@
import * as assert from 'assert';
import * as htmlLanguageService from '../htmlLanguageService';
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver-types';
import {CompletionList, TextDocument, CompletionItemKind} from 'vscode-languageserver-types';
import {applyEdits} from './textEditSupport';
export interface ItemDescription {
@ -53,12 +53,10 @@ export let assertCompletion = function (completions: CompletionList, expected: I
if (actualText !== expectedText) {
assert.equal(actualText, expectedText);
}
}
};
let testCompletionFor = function (value: string, expected: { count?: number, items?: ItemDescription[] }, settings? : htmlLanguageService.CompletionConfiguration): Thenable<void> {
let testCompletionFor = function (value: string, expected: { count?: number, items?: ItemDescription[] }, settings?: htmlLanguageService.CompletionConfiguration): Thenable<void> {
let offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
@ -78,6 +76,7 @@ let testCompletionFor = function (value: string, expected: { count?: number, ite
}
return Promise.resolve();
};
function run(tests: Thenable<void>[], testDone) {
Promise.all(tests).then(() => {
testDone();
@ -272,7 +271,7 @@ suite('HTML Completion', () => {
test('Handlebar Completion', function (testDone) {
run([
testCompletionFor('<script id="entry-template" type="text/x-handlebars-template"> <| </script>' , {
testCompletionFor('<script id="entry-template" type="text/x-handlebars-template"> <| </script>', {
items: [
{ label: 'div', resultText: '<script id="entry-template" type="text/x-handlebars-template"> <div </script>' },
]
@ -377,7 +376,7 @@ suite('HTML Completion', () => {
run([
testCompletionFor('<|', {
items: [
{ label: 'ion-checkbox'},
{ label: 'ion-checkbox' },
{ label: 'div', notAvailable: true },
]
}, { html5: false, ionic: true, angular1: false }),

View file

@ -6,7 +6,7 @@
import * as assert from 'assert';
import * as htmlLanguageService from '../htmlLanguageService';
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver-types';
import {TextDocument} from 'vscode-languageserver-types';
export function assertHighlights(value: string, expectedMatches: number[], elementName: string): void {
let offset = value.indexOf('|');
@ -46,7 +46,6 @@ suite('HTML Highlighting', () => {
assertHighlights('<html></htm|l>', [1, 8], 'html');
assertHighlights('<html></html|>', [1, 8], 'html');
assertHighlights('<html></html>|', [], null);
});
test('Nested', function (): any {
@ -63,10 +62,8 @@ suite('HTML Highlighting', () => {
});
test('Selfclosed', function (): any {
assertHighlights('<html><|div/></html>', [ 7 ], 'div');
assertHighlights('<html><|br></html>', [ 7 ], 'br');
assertHighlights('<html><div><d|iv/></div></html>', [ 12 ], 'div');
assertHighlights('<html><|div/></html>', [7], 'div');
assertHighlights('<html><|br></html>', [7], 'br');
assertHighlights('<html><div><d|iv/></div></html>', [12], 'div');
});
});

View file

@ -6,21 +6,19 @@
'use strict';
import * as assert from 'assert';
import * as htmlLinks from '../services/htmlLinks';
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver-types';
import Uri from 'vscode-uri';
import {TextDocument} from 'vscode-languageserver-types';
import * as htmlLanguageService from '../htmlLanguageService';
suite('HTML Link Detection', () => {
function testLinkCreation(modelUrl:string, rootUrl:string, tokenContent:string, expected:string): void {
function testLinkCreation(modelUrl: string, rootUrl: string, tokenContent: string, expected: string): void {
let document = TextDocument.create(modelUrl, 'html', 0, `<a href="${tokenContent}">`);
let ls = htmlLanguageService.getLanguageService();
let links = ls.findDocumentLinks(document, rootUrl);
assert.equal(links[0] && links[0].target, expected);
}
function testLinkDetection(value:string, expectedLinkLocations:number[]): void {
function testLinkDetection(value: string, expectedLinkLocations: number[]): void {
let document = TextDocument.create('test://test/test.html', 'html', 0, value);
let ls = htmlLanguageService.getLanguageService();
@ -80,8 +78,8 @@ suite('HTML Link Detection', () => {
});
test('Link detection', () => {
testLinkDetection('<img src="foo.png">', [ 9 ]);
testLinkDetection('<a href="http://server/foo.html">', [ 8 ]);
testLinkDetection('<img src="foo.png">', [9]);
testLinkDetection('<a href="http://server/foo.html">', [8]);
});
});

View file

@ -25,30 +25,32 @@ suite('HTML Parser', () => {
}
test('Simple', () => {
assertDocument('<html></html>', [ { tag: 'html', start: 0, end: 13, endTagStart: 6, closed: true, children: []}] );
assertDocument('<html><body></body></html>', [ { tag: 'html', start: 0, end: 26, endTagStart: 19, closed: true, children: [ { tag: 'body', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }]}] );
assertDocument('<html><head></head><body></body></html>', [ { tag: 'html', start: 0, end: 39, endTagStart: 32, closed: true, children: [ { tag: 'head', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }, { tag: 'body', start: 19, end: 32, endTagStart: 25, closed: true, children: [] }]}] );
assertDocument('<html></html>', [{ tag: 'html', start: 0, end: 13, endTagStart: 6, closed: true, children: [] }]);
assertDocument('<html><body></body></html>', [{ tag: 'html', start: 0, end: 26, endTagStart: 19, closed: true, children: [{ tag: 'body', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }] }]);
assertDocument('<html><head></head><body></body></html>', [{ tag: 'html', start: 0, end: 39, endTagStart: 32, closed: true, children: [{ tag: 'head', start: 6, end: 19, endTagStart: 12, closed: true, children: [] }, { tag: 'body', start: 19, end: 32, endTagStart: 25, closed: true, children: [] }] }]);
});
test('SelfClose', () => {
assertDocument('<br/>', [ { tag: 'br', start: 0, end: 5, endTagStart: void 0, closed: true, children: []}] );
assertDocument('<div><br/><span></span></div>', [ { tag: 'div', start: 0, end: 29, endTagStart: 23, closed: true, children: [{ tag: 'br', start: 5, end: 10, endTagStart: void 0, closed: true, children: [] }, { tag: 'span', start: 10, end: 23, endTagStart: 16, closed: true, children: [] }]}] );
assertDocument('<br/>', [{ tag: 'br', start: 0, end: 5, endTagStart: void 0, closed: true, children: [] }]);
assertDocument('<div><br/><span></span></div>', [{ tag: 'div', start: 0, end: 29, endTagStart: 23, closed: true, children: [{ tag: 'br', start: 5, end: 10, endTagStart: void 0, closed: true, children: [] }, { tag: 'span', start: 10, end: 23, endTagStart: 16, closed: true, children: [] }] }]);
});
test('EmptyTag', () => {
assertDocument('<meta>', [ { tag: 'meta', start: 0, end: 6, endTagStart: void 0, closed: true, children: []}] );
assertDocument('<div><input type="button"><span><br><br></span></div>', [ { tag: 'div', start: 0, end: 53, endTagStart: 47, closed: true, children: [
{ tag: 'input', start: 5, end: 26, endTagStart: void 0, closed: true, children: [] },
{ tag: 'span', start: 26, end: 47, endTagStart: 40, closed: true, children: [{ tag: 'br', start: 32, end: 36, endTagStart: void 0, closed: true, children: [] }, { tag: 'br', start: 36, end: 40, endTagStart: void 0, closed: true, children: [] }] }
]}] );
assertDocument('<meta>', [{ tag: 'meta', start: 0, end: 6, endTagStart: void 0, closed: true, children: [] }]);
assertDocument('<div><input type="button"><span><br><br></span></div>', [{
tag: 'div', start: 0, end: 53, endTagStart: 47, closed: true, children: [
{ tag: 'input', start: 5, end: 26, endTagStart: void 0, closed: true, children: [] },
{ tag: 'span', start: 26, end: 47, endTagStart: 40, closed: true, children: [{ tag: 'br', start: 32, end: 36, endTagStart: void 0, closed: true, children: [] }, { tag: 'br', start: 36, end: 40, endTagStart: void 0, closed: true, children: [] }] }
]
}]);
});
test('MissingTags', () => {
assertDocument('</meta>', [] );
assertDocument('<div></div></div>', [ { tag: 'div', start: 0, end: 11, endTagStart: 5, closed: true, children: [] }] );
assertDocument('<div><div></div>', [ { tag: 'div', start: 0, end: 16, endTagStart: void 0, closed: false, children: [ { tag: 'div', start: 5, end: 16, endTagStart: 10, closed: true, children: [] } ] }] );
assertDocument('<title><div></title>', [ { tag: 'title', start: 0, end: 20, endTagStart: 12, closed: true, children: [ { tag: 'div', start: 7, end: 12, endTagStart: void 0, closed: false, children: [] } ] }] );
assertDocument('<h1><div><span></h1>', [ { tag: 'h1', start: 0, end: 20, endTagStart: 15, closed: true, children: [ { tag: 'div', start: 4, end: 15, endTagStart: void 0, closed: false, children: [ { tag: 'span', start: 9, end: 15, endTagStart: void 0, closed: false, children: [] }] } ] }] );
});
assertDocument('</meta>', []);
assertDocument('<div></div></div>', [{ tag: 'div', start: 0, end: 11, endTagStart: 5, closed: true, children: [] }]);
assertDocument('<div><div></div>', [{ tag: 'div', start: 0, end: 16, endTagStart: void 0, closed: false, children: [{ tag: 'div', start: 5, end: 16, endTagStart: 10, closed: true, children: [] }] }]);
assertDocument('<title><div></title>', [{ tag: 'title', start: 0, end: 20, endTagStart: 12, closed: true, children: [{ tag: 'div', start: 7, end: 12, endTagStart: void 0, closed: false, children: [] }] }]);
assertDocument('<h1><div><span></h1>', [{ tag: 'h1', start: 0, end: 20, endTagStart: 15, closed: true, children: [{ tag: 'div', start: 4, end: 15, endTagStart: void 0, closed: false, children: [{ tag: 'span', start: 9, end: 15, endTagStart: void 0, closed: false, children: [] }] }] }]);
});
test('FindNodeBefore', () => {

File diff suppressed because it is too large Load diff