diff --git a/packages/language-server/package.json b/packages/language-server/package.json index 9596cdb8f..99d6d08d7 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -50,7 +50,6 @@ "estree-walker": "^2.0.1", "lodash": "^4.17.10", "magic-string": "^0.25.3", - "parse5": "^5.1.0", "prettier": "2.0.5", "prettier-plugin-svelte": "1.1.0", "source-map": "^0.7.3", diff --git a/packages/language-server/src/lib/documents/utils.ts b/packages/language-server/src/lib/documents/utils.ts index ec5b8153c..6ff07f39e 100644 --- a/packages/language-server/src/lib/documents/utils.ts +++ b/packages/language-server/src/lib/documents/utils.ts @@ -1,6 +1,6 @@ -import { clamp, isInRange } from '../../utils'; +import { clamp, isInRange, regexLastIndexOf } from '../../utils'; import { Position, Range } from 'vscode-languageserver'; -import parse5, { Location } from 'parse5'; +import { Node, getLanguageService } from 'vscode-html-languageservice'; export interface TagInformation { content: string; @@ -12,43 +12,44 @@ export interface TagInformation { container: { start: number; end: number }; } -function parseAttributes(attrlist: { name: string; value: string }[]): Record { +function parseAttributes( + rawAttrs: Record | undefined, +): Record { const attrs: Record = {}; - attrlist.forEach((attr) => { - attrs[attr.name] = attr.value === '' ? attr.name : attr.value; // in order to support boolean attributes (see utils.test.ts) + if (!rawAttrs) { + return attrs; + } + + Object.keys(rawAttrs).forEach((attrName) => { + const attrValue = rawAttrs[attrName]; + attrs[attrName] = attrValue === null ? attrName : removeOuterQuotes(attrValue); }); return attrs; -} -function isMatchingTag(source: string, node: ParsedNode, tag: string): boolean { - if (node.nodeName !== tag) { - return false; + function removeOuterQuotes(attrValue: string) { + if ( + (attrValue.startsWith('"') && attrValue.endsWith('"')) || + (attrValue.startsWith("'") && attrValue.endsWith("'")) + ) { + return attrValue.slice(1, attrValue.length - 1); + } + return attrValue; } +} - // node name equals tag, but we still have to check for case sensitivity - const orgStart = node.sourceCodeLocation?.startTag.startOffset || 0; - const orgEnd = node.sourceCodeLocation?.startTag.endOffset || 0; - const tagHtml = source.substring(orgStart, orgEnd); - return tagHtml.startsWith(`<${tag}`); +const parser = getLanguageService(); +function parseHtml(text: string) { + // We can safely only set getText because only this is used for parsing + return parser.parseHTMLDocument({ getText: () => text }); } -// parse5's DefaultTreeNode type is insufficient; make our own type to make TS happy -type ParsedNode = { - nodeName: string; - tagName: string; - value?: string; - attrs: { name: string; value: string }[]; - childNodes: ParsedNode[]; - parentNode: ParsedNode; - sourceCodeLocation: Location & { startTag: Location; endTag: Location }; -}; - -const regexIf = new RegExp('{#if\\s(.*?)*}', 'igms'); +const regexIf = new RegExp('{#if\\s.*?}', 'igms'); const regexIfEnd = new RegExp('{/if}', 'igms'); -const regexEach = new RegExp('{#each\\s(.*?)*}', 'igms'); +const regexEach = new RegExp('{#each\\s.*?}', 'igms'); const regexEachEnd = new RegExp('{/each}', 'igms'); -const regexAwait = new RegExp('{#await\\s(.*?)*}', 'igms'); +const regexAwait = new RegExp('{#await\\s.*?}', 'igms'); const regexAwaitEnd = new RegExp('{/await}', 'igms'); +const regexHtml = new RegExp('{@html\\s.*?', 'igms'); /** * Extracts a tag (style or script) from the given text @@ -57,76 +58,72 @@ const regexAwaitEnd = new RegExp('{/await}', 'igms'); * @param source text content to extract tag from * @param tag the tag to extract */ -function extractTags(source: string, tag: 'script' | 'style'): TagInformation[] { - const { childNodes } = parse5.parseFragment(source, { - sourceCodeLocationInfo: true, - }) as { childNodes: ParsedNode[] }; - - const matchedNodes: ParsedNode[] = []; - let currentSvelteDirective; - for (const node of childNodes) { - /** - * skip matching tags if we are inside a directive - * - * extractTag's goal is solely to identify the top level {/if}
    {#each cats as cat} {/each}
{#await promise} {:then number} {:catch error} {/await} -

{@html }

+

{@html }

{@html mycontent} {@debug myvar} @@ -156,11 +178,11 @@ describe('document/utils', () => { assert.deepStrictEqual(extractScriptTags(text)?.script, { content: 'top level script', attributes: {}, - start: 1212, - end: 1228, + start: 1243, + end: 1259, startPos: Position.create(34, 24), endPos: Position.create(34, 40), - container: { start: 1204, end: 1237 }, + container: { start: 1235, end: 1268 }, }); });