diff --git a/packages/language-server/src/lib/documents/parseHtml.ts b/packages/language-server/src/lib/documents/parseHtml.ts index cfa65ebe4..552c49765 100644 --- a/packages/language-server/src/lib/documents/parseHtml.ts +++ b/packages/language-server/src/lib/documents/parseHtml.ts @@ -42,7 +42,11 @@ function preprocess(text: string) { const offset = scanner.getTokenOffset(); if (token === TokenType.StartTagOpen) { - currentStartTagStart = offset; + if (shouldBlankStartOrEndTagLike(offset)) { + blankStartOrEndTagLike(offset); + } else { + currentStartTagStart = offset; + } } if (token === TokenType.StartTagClose) { @@ -74,11 +78,7 @@ function preprocess(text: string) { return text; function shouldBlankStartOrEndTagLike(offset: number) { - // not null rather than falsy, otherwise it won't work on first tag(0) - return ( - currentStartTagStart !== null && - isInsideMoustacheTag(text, currentStartTagStart, offset) - ); + return isInsideMoustacheTag(text, currentStartTagStart, offset); } function blankStartOrEndTagLike(offset: number) { diff --git a/packages/language-server/src/lib/documents/utils.ts b/packages/language-server/src/lib/documents/utils.ts index 2e6f706be..50f59cb53 100644 --- a/packages/language-server/src/lib/documents/utils.ts +++ b/packages/language-server/src/lib/documents/utils.ts @@ -40,7 +40,6 @@ function parseAttributes( } const regexIf = new RegExp('{#if\\s.*?}', 'gms'); -const regexIfElseIf = new RegExp('{:else if\\s.*?}', 'gms'); const regexIfEnd = new RegExp('{/if}', 'gms'); const regexEach = new RegExp('{#each\\s.*?}', 'gms'); const regexEachEnd = new RegExp('{/each}', 'gms'); @@ -48,23 +47,6 @@ const regexAwait = new RegExp('{#await\\s.*?}', 'gms'); const regexAwaitEnd = new RegExp('{/await}', 'gms'); const regexHtml = new RegExp('{@html\\s.*?', 'gms'); -/** - * if-blocks can contain the `<` operator, which mistakingly is - * parsed as a "open tag" character by the html parser. - * To prevent this, just replace the whole content inside the if with whitespace. - */ -function blankIfBlocks(text: string): string { - return text - .replace(regexIf, (substr) => { - return '{#if' + substr.replace(/[^\n]/g, ' ').substring(4, substr.length - 1) + '}'; - }) - .replace(regexIfElseIf, (substr) => { - return ( - '{:else if' + substr.replace(/[^\n]/g, ' ').substring(9, substr.length - 1) + '}' - ); - }); -} - /** * Extracts a tag (style or script) from the given text * and returns its start, end and the attributes on that tag. @@ -77,7 +59,6 @@ function extractTags( tag: 'script' | 'style' | 'template', html?: HTMLDocument ): TagInformation[] { - text = blankIfBlocks(text); const rootNodes = html?.roots || parseHtml(text).roots; const matchedNodes = rootNodes .filter((node) => node.tag === tag) @@ -404,7 +385,26 @@ export function getLangAttribute(...tags: Array): string return attribute.replace(/^text\//, ''); } -export function isInsideMoustacheTag(html: string, tagStart: number, position: number) { - const charactersInNode = html.substring(tagStart, position); - return charactersInNode.lastIndexOf('{') > charactersInNode.lastIndexOf('}'); +/** + * Checks whether given position is inside a moustache tag (which includes control flow tags) + * using a simple bracket matching heuristic which might fail under conditions like + * `{#if {a: true}.a}` + */ +export function isInsideMoustacheTag(html: string, tagStart: number | null, position: number) { + if (tagStart === null) { + // Not inside + const charactersBeforePosition = html.substring(0, position); + return ( + Math.max( + // TODO make this just check for '{'? + // Theoretically, someone could do {a < b} in a simple moustache tag + charactersBeforePosition.lastIndexOf('{#'), + charactersBeforePosition.lastIndexOf('{:') + ) > charactersBeforePosition.lastIndexOf('}') + ); + } else { + // Inside + const charactersInNode = html.substring(tagStart, position); + return charactersInNode.lastIndexOf('{') > charactersInNode.lastIndexOf('}'); + } } diff --git a/packages/language-server/test/lib/documents/parseHtml.test.ts b/packages/language-server/test/lib/documents/parseHtml.test.ts index 0d6d814a5..875717f85 100644 --- a/packages/language-server/test/lib/documents/parseHtml.test.ts +++ b/packages/language-server/test/lib/documents/parseHtml.test.ts @@ -37,6 +37,22 @@ describe('parseHtml', () => { ); }); + it('ignore less than operator inside control flow moustache', () => { + testRootElements( + parseHtml( + ` + {#if 1 < 2 && innWidth <= 700} + + + +
hi
+ {/if} +
+ ` + ) + ); + }); + it('ignore less than operator inside moustache with tag not self closed', () => { testRootElements( parseHtml( @@ -65,6 +81,19 @@ describe('parseHtml', () => { ); }); + it('parse baseline html with control flow moustache', () => { + testRootElements( + parseHtml( + ` + {#if true} + foo + {/if} + + ` + ) + ); + }); + it('parse baseline html with possibly un-closed start tag', () => { testRootElements( parseHtml(