Skip to content
This repository was archived by the owner on Sep 19, 2023. It is now read-only.

Commit dd79847

Browse files
committed
migrate htmlFolding.ts htmlParser.ts and htmlSelectionRange.ts to use classes instead of exported functions
1 parent 0301da5 commit dd79847

File tree

9 files changed

+431
-412
lines changed

9 files changed

+431
-412
lines changed

src/htmlLanguageService.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { createScanner } from './parser/htmlScanner';
7-
import { parse } from './parser/htmlParser';
7+
import { HTMLParser } from './parser/htmlParser';
88
import { HTMLCompletion } from './services/htmlCompletion';
99
import { HTMLHover } from './services/htmlHover';
1010
import { format } from './services/htmlFormatter';
@@ -19,12 +19,11 @@ import {
1919
IHTMLDataProvider, HTMLDataV1, LanguageServiceOptions, TextDocument, SelectionRange, WorkspaceEdit,
2020
Position, CompletionList, Hover, Range, SymbolInformation, TextEdit, DocumentHighlight, DocumentLink, FoldingRange, HoverSettings
2121
} from './htmlLanguageTypes';
22-
import { getFoldingRanges } from './services/htmlFolding';
23-
import { getSelectionRanges } from './services/htmlSelectionRange';
22+
import { HTMLFolding } from './services/htmlFolding';
23+
import { HTMLSelectionRange } from './services/htmlSelectionRange';
2424
import { HTMLDataProvider } from './languageFacts/dataProvider';
2525
import { HTMLDataManager } from './languageFacts/dataManager';
2626
import { htmlData } from './languageFacts/data/webCustomData';
27-
import { getVoidElements } from './languageFacts/fact';
2827

2928
export * from './htmlLanguageTypes';
3029

@@ -58,11 +57,14 @@ export function getLanguageService(options: LanguageServiceOptions = defaultLang
5857

5958
const htmlHover = new HTMLHover(options, dataManager);
6059
const htmlCompletion = new HTMLCompletion(options, dataManager);
60+
const htmlParser = new HTMLParser(dataManager);
61+
const htmlSelectionRange = new HTMLSelectionRange(htmlParser);
62+
const htmlFolding = new HTMLFolding(dataManager);
6163

6264
return {
6365
setDataProviders: dataManager.setDataProviders.bind(dataManager),
6466
createScanner,
65-
parseHTMLDocument: document => parse(document.getText(), getVoidElements(dataManager, document.languageId)),
67+
parseHTMLDocument: htmlParser.parseDocument.bind(htmlParser),
6668
doComplete: htmlCompletion.doComplete.bind(htmlCompletion),
6769
doComplete2: htmlCompletion.doComplete2.bind(htmlCompletion),
6870
setCompletionParticipants: htmlCompletion.setCompletionParticipants.bind(htmlCompletion),
@@ -71,8 +73,8 @@ export function getLanguageService(options: LanguageServiceOptions = defaultLang
7173
findDocumentHighlights,
7274
findDocumentLinks,
7375
findDocumentSymbols,
74-
getFoldingRanges: (document, context) => getFoldingRanges(document, context, getVoidElements(dataManager, document.languageId)),
75-
getSelectionRanges: (document, positions) => getSelectionRanges(document, positions, getVoidElements(dataManager, document.languageId)),
76+
getFoldingRanges: htmlFolding.getFoldingRanges.bind(htmlFolding),
77+
getSelectionRanges: htmlSelectionRange.getSelectionRanges.bind(htmlSelectionRange),
7678
doQuoteComplete: htmlCompletion.doQuoteComplete.bind(htmlCompletion),
7779
doTagComplete: htmlCompletion.doTagComplete.bind(htmlCompletion),
7880
doRename,

src/languageFacts/dataManager.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { IHTMLDataProvider } from '../htmlLanguageTypes';
77
import { HTMLDataProvider } from './dataProvider';
88
import { htmlData } from './data/webCustomData';
9+
import * as arrays from '../utils/arrays';
910

1011
export class HTMLDataManager {
1112
private dataProviders: IHTMLDataProvider[] = [];
@@ -24,4 +25,19 @@ export class HTMLDataManager {
2425
getDataProviders() {
2526
return this.dataProviders;
2627
}
27-
}
28+
29+
isVoidElement(e: string, voidElements: string[]) {
30+
return !!e && arrays.binarySearch(voidElements, e.toLowerCase(), (s1: string, s2: string) => s1.localeCompare(s2)) >= 0;
31+
}
32+
33+
getVoidElements(languageId: string):string[];
34+
getVoidElements(dataProviders: IHTMLDataProvider[]): string[];
35+
getVoidElements(languageOrProviders: string| IHTMLDataProvider[]): string[] {
36+
const dataProviders = Array.isArray(languageOrProviders) ? languageOrProviders : this.getDataProviders().filter(p => p.isApplicable(languageOrProviders!));
37+
const voidTags: string[] = [];
38+
dataProviders.forEach((provider) => {
39+
provider.provideTags().filter(tag => tag.void).forEach(tag => voidTags.push(tag.name));
40+
});
41+
return voidTags.sort();
42+
}
43+
}

src/languageFacts/fact.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/parser/htmlParser.ts

Lines changed: 109 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import { createScanner } from './htmlScanner';
77
import { findFirst } from '../utils/arrays';
8-
import { TokenType } from '../htmlLanguageTypes';
9-
import { isVoidElement } from '../languageFacts/fact';
8+
import { TokenType, TextDocument } from '../htmlLanguageTypes';
9+
import { HTMLDataManager } from '../languageFacts/dataManager';
1010

1111
export class Node {
1212
public tag: string | undefined;
@@ -63,102 +63,112 @@ export interface HTMLDocument {
6363
findNodeAt(offset: number): Node;
6464
}
6565

66-
export function parse(text: string, voidElements: string[]): HTMLDocument {
67-
const scanner = createScanner(text, undefined, undefined, true);
66+
export class HTMLParser {
67+
constructor(private dataManager: HTMLDataManager) {
6868

69-
const htmlDocument = new Node(0, text.length, [], void 0);
70-
let curr = htmlDocument;
71-
let endTagStart: number = -1;
72-
let endTagName: string | undefined = undefined;
73-
let pendingAttribute: string | null = null;
74-
let token = scanner.scan();
75-
while (token !== TokenType.EOS) {
76-
switch (token) {
77-
case TokenType.StartTagOpen:
78-
const child = new Node(scanner.getTokenOffset(), text.length, [], curr);
79-
curr.children.push(child);
80-
curr = child;
81-
break;
82-
case TokenType.StartTag:
83-
curr.tag = scanner.getTokenText();
84-
break;
85-
case TokenType.StartTagClose:
86-
if (curr.parent) {
87-
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
88-
if (scanner.getTokenLength()) {
89-
curr.startTagEnd = scanner.getTokenEnd();
90-
if (curr.tag && isVoidElement(curr.tag, voidElements)) {
91-
curr.closed = true;
92-
curr = curr.parent;
93-
}
94-
} else {
95-
// pseudo close token from an incomplete start tag
96-
curr = curr.parent;
97-
}
98-
}
99-
break;
100-
case TokenType.StartTagSelfClose:
101-
if (curr.parent) {
102-
curr.closed = true;
103-
curr.end = scanner.getTokenEnd();
104-
curr.startTagEnd = scanner.getTokenEnd();
105-
curr = curr.parent;
106-
}
107-
break;
108-
case TokenType.EndTagOpen:
109-
endTagStart = scanner.getTokenOffset();
110-
endTagName = undefined;
111-
break;
112-
case TokenType.EndTag:
113-
endTagName = scanner.getTokenText().toLowerCase();
114-
break;
115-
case TokenType.EndTagClose:
116-
let node = curr;
117-
// see if we can find a matching tag
118-
while (!node.isSameTag(endTagName) && node.parent) {
119-
node = node.parent;
120-
}
121-
if (node.parent) {
122-
while (curr !== node) {
123-
curr.end = endTagStart;
124-
curr.closed = false;
125-
curr = curr.parent!;
126-
}
127-
curr.closed = true;
128-
curr.endTagStart = endTagStart;
129-
curr.end = scanner.getTokenEnd();
130-
curr = curr.parent!;
131-
}
132-
break;
133-
case TokenType.AttributeName: {
134-
pendingAttribute = scanner.getTokenText();
135-
let attributes = curr.attributes;
136-
if (!attributes) {
137-
curr.attributes = attributes = {};
138-
}
139-
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
140-
break;
141-
}
142-
case TokenType.AttributeValue: {
143-
const value = scanner.getTokenText();
144-
const attributes = curr.attributes;
145-
if (attributes && pendingAttribute) {
146-
attributes[pendingAttribute] = value;
147-
pendingAttribute = null;
148-
}
149-
break;
150-
}
151-
}
152-
token = scanner.scan();
153-
}
154-
while (curr.parent) {
155-
curr.end = text.length;
156-
curr.closed = false;
157-
curr = curr.parent;
158-
}
159-
return {
160-
roots: htmlDocument.children,
161-
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
162-
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
163-
};
69+
}
70+
71+
public parseDocument(document: TextDocument): HTMLDocument {
72+
return this.parse(document.getText(), this.dataManager.getVoidElements(document.languageId));
73+
}
74+
75+
public parse(text: string, voidElements: string[]): HTMLDocument {
76+
const scanner = createScanner(text, undefined, undefined, true);
77+
78+
const htmlDocument = new Node(0, text.length, [], void 0);
79+
let curr = htmlDocument;
80+
let endTagStart: number = -1;
81+
let endTagName: string | undefined = undefined;
82+
let pendingAttribute: string | null = null;
83+
let token = scanner.scan();
84+
while (token !== TokenType.EOS) {
85+
switch (token) {
86+
case TokenType.StartTagOpen:
87+
const child = new Node(scanner.getTokenOffset(), text.length, [], curr);
88+
curr.children.push(child);
89+
curr = child;
90+
break;
91+
case TokenType.StartTag:
92+
curr.tag = scanner.getTokenText();
93+
break;
94+
case TokenType.StartTagClose:
95+
if (curr.parent) {
96+
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
97+
if (scanner.getTokenLength()) {
98+
curr.startTagEnd = scanner.getTokenEnd();
99+
if (curr.tag && this.dataManager.isVoidElement(curr.tag, voidElements)) {
100+
curr.closed = true;
101+
curr = curr.parent;
102+
}
103+
} else {
104+
// pseudo close token from an incomplete start tag
105+
curr = curr.parent;
106+
}
107+
}
108+
break;
109+
case TokenType.StartTagSelfClose:
110+
if (curr.parent) {
111+
curr.closed = true;
112+
curr.end = scanner.getTokenEnd();
113+
curr.startTagEnd = scanner.getTokenEnd();
114+
curr = curr.parent;
115+
}
116+
break;
117+
case TokenType.EndTagOpen:
118+
endTagStart = scanner.getTokenOffset();
119+
endTagName = undefined;
120+
break;
121+
case TokenType.EndTag:
122+
endTagName = scanner.getTokenText().toLowerCase();
123+
break;
124+
case TokenType.EndTagClose:
125+
let node = curr;
126+
// see if we can find a matching tag
127+
while (!node.isSameTag(endTagName) && node.parent) {
128+
node = node.parent;
129+
}
130+
if (node.parent) {
131+
while (curr !== node) {
132+
curr.end = endTagStart;
133+
curr.closed = false;
134+
curr = curr.parent!;
135+
}
136+
curr.closed = true;
137+
curr.endTagStart = endTagStart;
138+
curr.end = scanner.getTokenEnd();
139+
curr = curr.parent!;
140+
}
141+
break;
142+
case TokenType.AttributeName: {
143+
pendingAttribute = scanner.getTokenText();
144+
let attributes = curr.attributes;
145+
if (!attributes) {
146+
curr.attributes = attributes = {};
147+
}
148+
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
149+
break;
150+
}
151+
case TokenType.AttributeValue: {
152+
const value = scanner.getTokenText();
153+
const attributes = curr.attributes;
154+
if (attributes && pendingAttribute) {
155+
attributes[pendingAttribute] = value;
156+
pendingAttribute = null;
157+
}
158+
break;
159+
}
160+
}
161+
token = scanner.scan();
162+
}
163+
while (curr.parent) {
164+
curr.end = text.length;
165+
curr.closed = false;
166+
curr = curr.parent;
167+
}
168+
return {
169+
roots: htmlDocument.children,
170+
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
171+
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
172+
};
173+
}
164174
}

src/services/htmlCompletion.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import { entities } from '../parser/htmlEntities';
1414
import * as nls from 'vscode-nls';
1515
import { isLetterOrDigit, endsWith, startsWith } from '../utils/strings';
1616
import { HTMLDataManager } from '../languageFacts/dataManager';
17-
import { getVoidElements, isVoidElement } from '../languageFacts/fact';
1817
import { isDefined } from '../utils/object';
1918
import { generateDocumentation } from '../languageFacts/dataProvider';
2019
import { PathCompletionParticipant } from './pathCompletion';
@@ -66,7 +65,7 @@ export class HTMLCompletion {
6665
};
6766
const completionParticipants = this.completionParticipants;
6867
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId) && (!settings || settings[p.getId()] !== false));
69-
const voidElements = getVoidElements(dataProviders);
68+
const voidElements = this.dataManager.getVoidElements(dataProviders);
7069
const doesSupportMarkdown = this.doesSupportMarkdown();
7170

7271
const text = document.getText();
@@ -167,11 +166,11 @@ export class HTMLCompletion {
167166
return result;
168167
}
169168

170-
function collectAutoCloseTagSuggestion(tagCloseEnd: number, tag: string): CompletionList {
169+
const collectAutoCloseTagSuggestion = (tagCloseEnd: number, tag: string): CompletionList => {
171170
if (settings && settings.hideAutoCompleteProposals) {
172171
return result;
173172
}
174-
if (!isVoidElement(tag, voidElements)) {
173+
if (!this.dataManager.isVoidElement(tag, voidElements)) {
175174
const pos = document.positionAt(tagCloseEnd);
176175
result.items.push({
177176
label: '</' + tag + '>',
@@ -182,7 +181,7 @@ export class HTMLCompletion {
182181
});
183182
}
184183
return result;
185-
}
184+
};
186185

187186
function collectTagSuggestions(tagStart: number, tagEnd: number): CompletionList {
188187
collectOpenTagSuggestions(tagStart, tagEnd);
@@ -536,9 +535,9 @@ export class HTMLCompletion {
536535
}
537536
const char = document.getText().charAt(offset - 1);
538537
if (char === '>') {
539-
const voidElements = getVoidElements(this.dataManager, document.languageId);
538+
const voidElements = this.dataManager.getVoidElements(document.languageId);
540539
const node = htmlDocument.findNodeBefore(offset);
541-
if (node && node.tag && !isVoidElement(node.tag, voidElements) && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
540+
if (node && node.tag && !this.dataManager.isVoidElement(node.tag, voidElements) && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
542541
const scanner = createScanner(document.getText(), node.start);
543542
let token = scanner.scan();
544543
while (token !== TokenType.EOS && scanner.getTokenEnd() <= offset) {

0 commit comments

Comments
 (0)