From a27697a715072ae6adddf228976f23bab6d48fb8 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Sun, 3 Mar 2024 13:43:38 +0900 Subject: [PATCH] feat: change it to use modern AST, if svelte v5 is installed (#437) --- .changeset/loud-geese-trade.md | 5 + src/context/index.ts | 41 +- src/context/script-let.ts | 8 +- src/parser/compat.ts | 278 +++ src/parser/converts/attr.ts | 166 +- src/parser/converts/block.ts | 280 ++- src/parser/converts/const.ts | 6 +- src/parser/converts/element.ts | 359 ++-- src/parser/converts/mustache.ts | 11 +- src/parser/converts/render.ts | 4 +- src/parser/converts/root.ts | 47 +- src/parser/converts/text.ts | 20 - src/parser/espree.ts | 5 +- src/parser/html.ts | 439 +++-- src/parser/index.ts | 6 +- src/parser/svelte-ast-types.ts | 27 +- src/parser/template.ts | 7 +- .../parser/ast/at-const02-input.svelte | 15 + .../parser/ast/at-const02-output.json | 1617 +++++++++++++++++ .../parser/ast/at-const02-scope-output.json | 1463 +++++++++++++++ .../parser/ast/await03-requirements.json | 6 - tests/src/parser/__snapshots__/html.ts.snap | 547 ++++-- tests/src/parser/html.ts | 21 + tests/src/parser/test-utils.ts | 14 +- tests/src/parser/typescript/assert-result.ts | 1 - 25 files changed, 4756 insertions(+), 637 deletions(-) create mode 100644 .changeset/loud-geese-trade.md create mode 100644 src/parser/compat.ts create mode 100644 tests/fixtures/parser/ast/at-const02-input.svelte create mode 100644 tests/fixtures/parser/ast/at-const02-output.json create mode 100644 tests/fixtures/parser/ast/at-const02-scope-output.json delete mode 100644 tests/fixtures/parser/ast/await03-requirements.json diff --git a/.changeset/loud-geese-trade.md b/.changeset/loud-geese-trade.md new file mode 100644 index 00000000..ae153d49 --- /dev/null +++ b/.changeset/loud-geese-trade.md @@ -0,0 +1,5 @@ +--- +"svelte-eslint-parser": minor +--- + +feat: change it to use modern AST, if svelte v5 is installed diff --git a/src/context/index.ts b/src/context/index.ts index 32d0fd8a..f613ae68 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -12,9 +12,9 @@ import type { } from "../ast"; import type ESTree from "estree"; import type * as SvAST from "../parser/svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; import { ScriptLetContext } from "./script-let"; import { LetDirectiveCollections } from "./let-directive-collection"; -import type { AttributeToken } from "../parser/html"; import { parseAttributes } from "../parser/html"; import { sortedLastIndex } from "../utils"; import { @@ -164,6 +164,19 @@ export class Context { | SvAST.SlotTemplate | SvAST.Slot | SvAST.Title + | Compiler.RegularElement + | Compiler.Component + | Compiler.SvelteComponent + | Compiler.SvelteElement + | Compiler.SvelteWindow + | Compiler.SvelteBody + | Compiler.SvelteHead + | Compiler.SvelteDocument + | Compiler.SvelteFragment + | Compiler.SvelteSelf + | Compiler.SvelteOptionsRaw + | Compiler.SlotElement + | Compiler.TitleElement >(); public readonly snippets: SvelteSnippetBlock[] = []; @@ -190,8 +203,16 @@ export class Context { if (block.selfClosing) { continue; } - const lang = block.attrs.find((attr) => attr.key.name === "lang"); - if (!lang || !lang.value || lang.value.value === "html") { + const lang = block.attrs.find((attr) => attr.name === "lang"); + if (!lang || !Array.isArray(lang.value)) { + continue; + } + const langValue = lang.value[0]; + if ( + !langValue || + langValue.type !== "Text" || + langValue.data === "html" + ) { continue; } } @@ -221,7 +242,13 @@ export class Context { spaces.slice(start, block.contentRange[0]) + code.slice(...block.contentRange); for (const attr of block.attrs) { - scriptAttrs[attr.key.name] = attr.value?.value; + if (Array.isArray(attr.value)) { + const attrValue = attr.value[0]; + scriptAttrs[attr.name] = + attrValue && attrValue.type === "Text" + ? attrValue.data + : undefined; + } } } else { scriptCode += spaces.slice(start, block.contentRange[1]); @@ -347,7 +374,7 @@ type Block = | { tag: "script" | "style" | "template"; originalTag: string; - attrs: AttributeToken[]; + attrs: Compiler.Attribute[]; selfClosing?: false; contentRange: [number, number]; startTagRange: [number, number]; @@ -358,7 +385,7 @@ type Block = type SelfClosingBlock = { tag: "script" | "style" | "template"; originalTag: string; - attrs: AttributeToken[]; + attrs: Compiler.Attribute[]; selfClosing: true; startTagRange: [number, number]; }; @@ -380,7 +407,7 @@ function* extractBlocks(code: string): IterableIterator { const lowerTag = tag.toLowerCase() as "script" | "style" | "template"; - let attrs: AttributeToken[] = []; + let attrs: Compiler.Attribute[] = []; if (!nextChar.trim()) { const attrsData = parseAttributes(code, startTagOpenRe.lastIndex); attrs = attrsData.attributes; diff --git a/src/context/script-let.ts b/src/context/script-let.ts index d5718448..80682121 100644 --- a/src/context/script-let.ts +++ b/src/context/script-let.ts @@ -246,11 +246,15 @@ export class ScriptLetContext { } public addVariableDeclarator( - expression: ESTree.AssignmentExpression, + declarator: ESTree.VariableDeclarator | ESTree.AssignmentExpression, parent: SvelteNode, ...callbacks: ScriptLetCallback[] ): ScriptLetCallback[] { - const range = getNodeRange(expression); + const range = + declarator.type === "VariableDeclarator" + ? // As of Svelte v5-next.65, VariableDeclarator nodes do not have location information. + [getNodeRange(declarator.id)[0], getNodeRange(declarator.init!)[1]] + : getNodeRange(declarator); const part = this.ctx.code.slice(...range); this.appendScript( `const ${part};`, diff --git a/src/parser/compat.ts b/src/parser/compat.ts new file mode 100644 index 00000000..0a7a11ce --- /dev/null +++ b/src/parser/compat.ts @@ -0,0 +1,278 @@ +/** Compatibility for Svelte v4 <-> v5 */ +import type ESTree from "estree"; +import type * as SvAST from "./svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; +import { parseAttributes } from "./html"; + +export type Child = + | Compiler.Text + | Compiler.Tag + | Compiler.ElementLike + | Compiler.Block + | Compiler.Comment; +type HasChildren = { children?: SvAST.TemplateNode[] }; +// Root +export function getFragmentFromRoot( + svelteAst: Compiler.Root | SvAST.AstLegacy, +): SvAST.Fragment | Compiler.Fragment | undefined { + return ( + (svelteAst as Compiler.Root).fragment ?? (svelteAst as SvAST.AstLegacy).html + ); +} +export function getInstanceFromRoot( + svelteAst: Compiler.Root | SvAST.AstLegacy, +): SvAST.Script | Compiler.Script | null | undefined { + return svelteAst.instance; +} +export function getModuleFromRoot( + svelteAst: Compiler.Root | SvAST.AstLegacy, +): SvAST.Script | Compiler.Script | null | undefined { + return svelteAst.module; +} +export function getOptionsFromRoot( + svelteAst: Compiler.Root | SvAST.AstLegacy, + code: string, +): Compiler.SvelteOptionsRaw | null { + const root = svelteAst as Compiler.Root; + if (root.options) { + if ((root.options as any).__raw__) { + return (root.options as any).__raw__; + } + // If there is no `__raw__` property in the `SvelteOptions` node, + // we will parse `` ourselves. + return parseSvelteOptions(root.options, code); + } + return null; +} + +export function getChildren( + fragment: Required | { nodes: (Child | SvAST.TemplateNode)[] }, +): (SvAST.TemplateNode | Child)[]; +export function getChildren( + fragment: HasChildren | { nodes: (Child | SvAST.TemplateNode)[] }, +): (SvAST.TemplateNode | Child)[] | undefined; +export function getChildren( + fragment: HasChildren | { nodes: (Child | SvAST.TemplateNode)[] }, +): (SvAST.TemplateNode | Child)[] | undefined { + return ( + (fragment as { nodes: (Child | SvAST.TemplateNode)[] }).nodes ?? + (fragment as HasChildren).children + ); +} +export function trimChildren( + children: (SvAST.TemplateNode | Child)[], +): (SvAST.TemplateNode | Child)[] { + if ( + !startsWithWhitespace(children[0]) && + !endsWithWhitespace(children[children.length - 1]) + ) { + return children; + } + + const nodes = [...children]; + while (isWhitespace(nodes[0])) { + nodes.shift(); + } + const first = nodes[0]; + if (startsWithWhitespace(first)) { + nodes[0] = { ...first, data: first.data.trimStart() }; + } + while (isWhitespace(nodes[nodes.length - 1])) { + nodes.pop(); + } + const last = nodes[nodes.length - 1]; + if (endsWithWhitespace(last)) { + nodes[nodes.length - 1] = { ...last, data: last.data.trimEnd() }; + } + return nodes; + + function startsWithWhitespace( + child: SvAST.TemplateNode | Child | undefined, + ): child is SvAST.Text | Compiler.Text { + if (!child) { + return false; + } + return child.type === "Text" && child.data.trimStart() !== child.data; + } + + function endsWithWhitespace( + child: SvAST.TemplateNode | Child | undefined, + ): child is SvAST.Text | Compiler.Text { + if (!child) { + return false; + } + return child.type === "Text" && child.data.trimEnd() !== child.data; + } + + function isWhitespace(child: SvAST.TemplateNode | Child | undefined) { + if (!child) { + return false; + } + return child.type === "Text" && child.data.trim() === ""; + } +} +export function getFragment( + element: + | { + fragment: Compiler.Fragment; + } + | Required, +): Compiler.Fragment | Required; +export function getFragment( + element: + | { + fragment: Compiler.Fragment; + } + | HasChildren, +): Compiler.Fragment | HasChildren; +export function getFragment( + element: + | { + fragment: Compiler.Fragment; + } + | HasChildren, +): Compiler.Fragment | HasChildren { + if ( + ( + element as { + fragment: Compiler.Fragment; + } + ).fragment + ) { + return ( + element as { + fragment: Compiler.Fragment; + } + ).fragment; + } + return element as HasChildren; +} +export function getModifiers( + node: SvAST.Directive | SvAST.StyleDirective | Compiler.Directive, +): string[] { + return (node as { modifiers?: string[] }).modifiers ?? []; +} +// IfBlock +export function getTestFromIfBlock( + block: SvAST.IfBlock | Compiler.IfBlock, +): ESTree.Expression { + return ( + (block as SvAST.IfBlock).expression ?? (block as Compiler.IfBlock).test + ); +} +export function getConsequentFromIfBlock( + block: SvAST.IfBlock | Compiler.IfBlock, +): Compiler.Fragment | SvAST.IfBlock { + return (block as Compiler.IfBlock).consequent ?? (block as SvAST.IfBlock); +} +export function getAlternateFromIfBlock( + block: SvAST.IfBlock | Compiler.IfBlock, +): Compiler.Fragment | SvAST.ElseBlock | null { + if ((block as Compiler.IfBlock).alternate) { + return (block as Compiler.IfBlock).alternate; + } + return (block as SvAST.IfBlock).else ?? null; +} +// EachBlock +export function getBodyFromEachBlock( + block: SvAST.EachBlock | Compiler.EachBlock, +): Compiler.Fragment | SvAST.EachBlock { + if ((block as Compiler.EachBlock).body) { + return (block as Compiler.EachBlock).body; + } + return block as SvAST.EachBlock; +} +export function getFallbackFromEachBlock( + block: SvAST.EachBlock | Compiler.EachBlock, +): Compiler.Fragment | SvAST.ElseBlock | null { + if ((block as Compiler.EachBlock).fallback) { + return (block as Compiler.EachBlock).fallback!; + } + return (block as SvAST.EachBlock).else ?? null; +} +// AwaitBlock +export function getPendingFromAwaitBlock( + block: SvAST.AwaitBlock | Compiler.AwaitBlock, +): Compiler.Fragment | SvAST.PendingBlock | null { + const pending = block.pending; + if (!pending) { + return null; + } + if (pending.type === "Fragment") { + return pending; + } + return pending.skip ? null : pending; +} +export function getThenFromAwaitBlock( + block: SvAST.AwaitBlock | Compiler.AwaitBlock, +): Compiler.Fragment | SvAST.ThenBlock | null { + const then = block.then; + if (!then) { + return null; + } + if (then.type === "Fragment") { + return then; + } + return then.skip ? null : then; +} +export function getCatchFromAwaitBlock( + block: SvAST.AwaitBlock | Compiler.AwaitBlock, +): Compiler.Fragment | SvAST.CatchBlock | null { + const catchFragment = block.catch; + if (!catchFragment) { + return null; + } + if (catchFragment.type === "Fragment") { + return catchFragment; + } + return catchFragment.skip ? null : catchFragment; +} + +// ConstTag +export function getDeclaratorFromConstTag( + node: SvAST.ConstTag | Compiler.ConstTag, +): + | ESTree.AssignmentExpression + | Compiler.ConstTag["declaration"]["declarations"][0] { + return ( + (node as Compiler.ConstTag).declaration?.declarations?.[0] ?? + (node as SvAST.ConstTag).expression + ); +} + +function parseSvelteOptions( + options: Compiler.SvelteOptions, + code: string, +): Compiler.SvelteOptionsRaw { + const { start, end } = options; + const nameEndName = start + "", tagEndIndex)) { + const childEndIndex = code.indexOf(" & { + expression: SvAST.LetDirective["expression"]; +}; +type StandardDirective = + | Exclude + | LetDirective; /** Convert for Attributes */ export function* convertAttributes( - attributes: SvAST.AttributeOrDirective[], + attributes: ( + | SvAST.AttributeOrDirective + | Compiler.Attribute + | Compiler.SpreadAttribute + | Compiler.Directive + )[], parent: SvelteStartTag, ctx: Context, ): IterableIterator< @@ -53,19 +63,19 @@ export function* convertAttributes( yield convertAttribute(attr, parent, ctx); continue; } - if (attr.type === "Spread") { + if (attr.type === "SpreadAttribute" || attr.type === "Spread") { yield convertSpreadAttribute(attr, parent, ctx); continue; } - if (attr.type === "Binding") { + if (attr.type === "BindDirective" || attr.type === "Binding") { yield convertBindingDirective(attr, parent, ctx); continue; } - if (attr.type === "EventHandler") { + if (attr.type === "OnDirective" || attr.type === "EventHandler") { yield convertEventHandlerDirective(attr, parent, ctx); continue; } - if (attr.type === "Class") { + if (attr.type === "ClassDirective" || attr.type === "Class") { yield convertClassDirective(attr, parent, ctx); continue; } @@ -73,18 +83,22 @@ export function* convertAttributes( yield convertStyleDirective(attr, parent, ctx); continue; } - if (attr.type === "Transition") { + if (attr.type === "TransitionDirective" || attr.type === "Transition") { yield convertTransitionDirective(attr, parent, ctx); continue; } - if (attr.type === "Animation") { + if (attr.type === "AnimateDirective" || attr.type === "Animation") { yield convertAnimationDirective(attr, parent, ctx); continue; } - if (attr.type === "Action") { + if (attr.type === "UseDirective" || attr.type === "Action") { yield convertActionDirective(attr, parent, ctx); continue; } + if (attr.type === "LetDirective") { + yield convertLetDirective(attr as LetDirective, parent, ctx); + continue; + } if (attr.type === "Let") { yield convertLetDirective(attr, parent, ctx); continue; @@ -107,45 +121,9 @@ export function* convertAttributes( } } -/** Convert for attribute tokens */ -export function* convertAttributeTokens( - attributes: AttributeToken[], - parent: SvelteStartTag, - ctx: Context, -): IterableIterator { - for (const attr of attributes) { - const attribute: SvelteAttribute = { - type: "SvelteAttribute", - boolean: false, - key: null as any, - value: [], - parent, - ...ctx.getConvertLocation({ - start: attr.key.start, - end: attr.value?.end ?? attr.key.end, - }), - }; - attribute.key = { - type: "SvelteName", - name: attr.key.name, - parent: attribute, - ...ctx.getConvertLocation(attr.key), - }; - ctx.addToken("HTMLIdentifier", attr.key); - if (attr.value == null) { - attribute.boolean = true; - } else { - attribute.value.push( - convertAttributeValueTokenToLiteral(attr.value, attribute, ctx), - ); - } - yield attribute; - } -} - /** Convert for Attribute */ function convertAttribute( - node: SvAST.Attribute, + node: SvAST.Attribute | Compiler.Attribute, parent: SvelteAttribute["parent"], ctx: Context, ): SvelteAttribute | SvelteShorthandAttribute { @@ -172,7 +150,20 @@ function convertAttribute( ctx.addToken("HTMLIdentifier", keyRange); return attribute; } - const shorthand = node.value.find((v) => v.type === "AttributeShorthand"); + const value = node.value as ( + | Compiler.Text + | Compiler.ExpressionTag + | SvAST.Text + | SvAST.MustacheTag + | SvAST.AttributeShorthand + )[]; + const shorthand = + value.find((v) => v.type === "AttributeShorthand") || + // for Svelte v5 + (value.length === 1 && + value[0].type === "ExpressionTag" && + ctx.code[node.start] === "{" && + ctx.code[node.end - 1] === "}"); if (shorthand) { const key: ESTree.Identifier = { ...attribute.key, @@ -199,22 +190,32 @@ function convertAttribute( }); return sAttr; } + // Not required for shorthands. Therefore, register the token here. + ctx.addToken("HTMLIdentifier", keyRange); + processAttributeValue( - node.value as (SvAST.Text | SvAST.MustacheTag)[], + node.value as ( + | SvAST.Text + | SvAST.MustacheTag + | Compiler.Text + | Compiler.ExpressionTag + )[], attribute, parent, ctx, ); - // Not required for shorthands. Therefore, register the token here. - ctx.addToken("HTMLIdentifier", keyRange); - return attribute; } /** Common process attribute value */ function processAttributeValue( - nodeValue: (SvAST.Text | SvAST.MustacheTag)[], + nodeValue: ( + | SvAST.Text + | SvAST.MustacheTag + | Compiler.Text + | Compiler.ExpressionTag + )[], attribute: SvelteAttribute | SvelteStyleDirectiveLongform, attributeParent: (SvelteAttribute | SvelteStyleDirectiveLongform)["parent"], ctx: Context, @@ -242,7 +243,7 @@ function processAttributeValue( }); if ( nodes.length === 1 && - nodes[0].type === "MustacheTag" && + (nodes[0].type === "ExpressionTag" || nodes[0].type === "MustacheTag") && attribute.type === "SvelteAttribute" ) { const typing = buildAttributeType( @@ -259,7 +260,7 @@ function processAttributeValue( attribute.value.push(convertTextToLiteral(v, attribute, ctx)); continue; } - if (v.type === "MustacheTag") { + if (v.type === "ExpressionTag" || v.type === "MustacheTag") { const mustache = convertMustacheTag(v, attribute, null, ctx); attribute.value.push(mustache); continue; @@ -316,7 +317,7 @@ function buildAttributeType( /** Convert for Spread */ function convertSpreadAttribute( - node: SvAST.Spread, + node: SvAST.Spread | Compiler.SpreadAttribute, parent: SvelteSpreadAttribute["parent"], ctx: Context, ): SvelteSpreadAttribute { @@ -342,7 +343,7 @@ function convertSpreadAttribute( /** Convert for Binding Directive */ function convertBindingDirective( - node: SvAST.DirectiveForExpression, + node: SvAST.DirectiveForExpression | Compiler.BindDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteBindingDirective { @@ -385,7 +386,7 @@ function convertBindingDirective( /** Convert for EventHandler Directive */ function convertEventHandlerDirective( - node: SvAST.DirectiveForExpression, + node: SvAST.DirectiveForExpression | Compiler.OnDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteEventHandlerDirective { @@ -494,7 +495,7 @@ function buildEventHandlerType( /** Convert for Class Directive */ function convertClassDirective( - node: SvAST.DirectiveForExpression, + node: SvAST.DirectiveForExpression | Compiler.ClassDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteClassDirective { @@ -518,7 +519,7 @@ function convertClassDirective( /** Convert for Style Directive */ function convertStyleDirective( - node: SvAST.StyleDirective, + node: SvAST.StyleDirective | Compiler.StyleDirective, parent: SvelteStyleDirective["parent"], ctx: Context, ): SvelteStyleDirective { @@ -566,7 +567,7 @@ function convertStyleDirective( /** Convert for Transition Directive */ function convertTransitionDirective( - node: SvAST.TransitionDirective, + node: SvAST.TransitionDirective | Compiler.TransitionDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteTransitionDirective { @@ -599,7 +600,7 @@ function convertTransitionDirective( /** Convert for Animation Directive */ function convertAnimationDirective( - node: SvAST.DirectiveForExpression, + node: SvAST.DirectiveForExpression | Compiler.AnimateDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteAnimationDirective { @@ -630,7 +631,7 @@ function convertAnimationDirective( /** Convert for Action Directive */ function convertActionDirective( - node: SvAST.DirectiveForExpression, + node: SvAST.DirectiveForExpression | Compiler.UseDirective, parent: SvelteDirective["parent"], ctx: Context, ): SvelteActionDirective { @@ -661,7 +662,7 @@ function convertActionDirective( /** Convert for Let Directive */ function convertLetDirective( - node: SvAST.LetDirective, + node: SvAST.LetDirective | LetDirective, parent: SvelteLetDirective["parent"], ctx: Context, ): SvelteLetDirective { @@ -715,11 +716,16 @@ function buildLetDirectiveType( let slotName = "default"; let componentName: string; const svelteNode = ctx.elements.get(element)!; - const slotAttr = svelteNode.attributes.find( - (attr): attr is SvAST.Attribute => { - return attr.type === "Attribute" && attr.name === "slot"; - }, - ); + const slotAttr = ( + svelteNode.attributes as ( + | SvAST.AttributeOrDirective + | Compiler.Attribute + | Compiler.SpreadAttribute + | Compiler.Directive + )[] + ).find((attr): attr is SvAST.Attribute | Compiler.Attribute => { + return attr.type === "Attribute" && attr.name === "slot"; + }); if (slotAttr) { if ( Array.isArray(slotAttr.value) && @@ -764,7 +770,7 @@ function buildLetDirectiveType( } type DirectiveProcessors< - D extends SvAST.Directive, + D extends SvAST.Directive | StandardDirective, S extends SvelteDirective, E extends D["expression"] & S["expression"], > = @@ -791,7 +797,7 @@ type DirectiveProcessors< /** Common process for directive */ function processDirective< - D extends SvAST.Directive, + D extends SvAST.Directive | StandardDirective, S extends SvelteDirective, E extends D["expression"] & S["expression"], >( @@ -806,7 +812,11 @@ function processDirective< /** Common process for directive key */ function processDirectiveKey< - D extends SvAST.Directive | SvAST.StyleDirective, + D extends + | SvAST.Directive + | SvAST.StyleDirective + | StandardDirective + | Compiler.StyleDirective, S extends SvelteDirective | SvelteStyleDirective, >(node: D, directive: S, ctx: Context) { const colonIndex = ctx.code.indexOf(":", directive.range[0]); @@ -846,7 +856,9 @@ function processDirectiveKey< const key = (directive.key = { type: "SvelteDirectiveKey", name: null as any, - modifiers: node.modifiers, + modifiers: getModifiers( + node as SvAST.Directive | SvAST.StyleDirective | Compiler.Directive, + ), parent: directive, ...ctx.getConvertLocation({ start: node.start, end: keyEndIndex }), }); @@ -862,7 +874,7 @@ function processDirectiveKey< /** Common process for directive expression */ function processDirectiveExpression< - D extends SvAST.Directive, + D extends SvAST.Directive | StandardDirective, S extends SvelteDirective, E extends D["expression"], >( diff --git a/src/parser/converts/block.ts b/src/parser/converts/block.ts index 66aeb9e9..cd7e8fd7 100644 --- a/src/parser/converts/block.ts +++ b/src/parser/converts/block.ts @@ -1,4 +1,5 @@ import type * as SvAST from "../svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; import type { SvelteAwaitBlock, SvelteAwaitBlockAwaitCatch, @@ -20,6 +21,19 @@ import type { Context } from "../../context"; import { convertChildren } from "./element"; import { getWithLoc, indexOf, lastIndexOf } from "./common"; import type * as ESTree from "estree"; +import { + getAlternateFromIfBlock, + getBodyFromEachBlock, + getCatchFromAwaitBlock, + getChildren, + getConsequentFromIfBlock, + getFallbackFromEachBlock, + getFragment, + getPendingFromAwaitBlock, + getTestFromIfBlock, + getThenFromAwaitBlock, + trimChildren, +} from "../compat"; /** Get start index of block */ function startBlockIndex( @@ -46,20 +60,65 @@ function startBlockIndex( ); } +function startIndexFromFragment( + fragment: + | Compiler.Fragment + | SvAST.ElseBlock + | SvAST.ThenBlock + | SvAST.CatchBlock, + getBeforeEndIndex: () => number, +) { + if ((fragment as { start: number }).start != null) { + return (fragment as { start: number }).start; + } + const children = getChildren(fragment); + return children.length ? children[0].start : getBeforeEndIndex(); +} + +function endIndexFromFragment( + fragment: + | Compiler.Fragment + | SvAST.IfBlock + | SvAST.ElseBlock + | SvAST.PendingBlock + | SvAST.EachBlock + | SvAST.ThenBlock + | SvAST.CatchBlock, + getBeforeEndIndex: () => number, +): number { + if ((fragment as { end: number }).end != null) { + return (fragment as { end: number }).end; + } + const children = getChildren(fragment); + return children.length + ? children[children.length - 1].end + : getBeforeEndIndex(); +} + +function endIndexFromBlock( + fragment: Compiler.Fragment | SvAST.PendingBlock, + lastExpression: ESTree.Node | { start: number; end: number }, + ctx: Context, +) { + return endIndexFromFragment(fragment, () => { + return ctx.code.indexOf("}", getWithLoc(lastExpression).end) + 1; + }); +} + export function convertIfBlock( - node: SvAST.IfBlock, + node: SvAST.IfBlock | Compiler.IfBlock, parent: SvelteIfBlock["parent"], ctx: Context, ): SvelteIfBlockAlone; export function convertIfBlock( - node: SvAST.IfBlock, + node: SvAST.IfBlock | Compiler.IfBlock, parent: SvelteIfBlock["parent"], ctx: Context, elseif: true, ): SvelteIfBlockElseIf; /** Convert for IfBlock */ export function convertIfBlock( - node: SvAST.IfBlock, + node: SvAST.IfBlock | Compiler.IfBlock, parent: SvelteIfBlock["parent"], ctx: Context, elseif?: true, @@ -81,10 +140,24 @@ export function convertIfBlock( ...ctx.getConvertLocation({ start: nodeStart, end: node.end }), } as SvelteIfBlock; - ctx.scriptLet.nestIfBlock(node.expression, ifBlock, (es) => { + const test = getTestFromIfBlock(node); + ctx.scriptLet.nestIfBlock(test, ifBlock, (es) => { ifBlock.expression = es; }); - ifBlock.children.push(...convertChildren(node, ifBlock, ctx)); + const consequent = getConsequentFromIfBlock(node); + + ifBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(getChildren(consequent)), + }, + ifBlock, + ctx, + ), + ); + ctx.scriptLet.closeScope(); if (elseif) { const index = ctx.code.indexOf("if", nodeStart); @@ -92,22 +165,16 @@ export function convertIfBlock( } extractMustacheBlockTokens(ifBlock, ctx, { startOnly: elseif }); - if (!node.else) { + const elseFragment = getAlternateFromIfBlock(node); + if (!elseFragment) { return ifBlock; } - let baseStart = node.else.start; - if (node.else.children.length === 1) { - const c = node.else.children[0]; - if (c.type === "IfBlock" && c.elseif) { - baseStart = Math.min(baseStart, c.start, getWithLoc(c.expression).start); - } - } - - const elseStart = startBlockIndex(ctx.code, baseStart - 1, ":else"); + const elseStart = startBlockIndexForElse(elseFragment, consequent, test, ctx); - if (node.else.children.length === 1) { - const c = node.else.children[0]; + const elseChildren = getChildren(elseFragment); + if (elseChildren.length === 1) { + const c = elseChildren[0]; if (c.type === "IfBlock" && c.elseif) { const elseBlock: SvelteElseBlockElseIf = { type: "SvelteElseBlock", @@ -116,7 +183,7 @@ export function convertIfBlock( parent: ifBlock, ...ctx.getConvertLocation({ start: elseStart, - end: node.else.end, + end: c.end, }), }; ifBlock.else = elseBlock; @@ -139,22 +206,59 @@ export function convertIfBlock( parent: ifBlock, ...ctx.getConvertLocation({ start: elseStart, - end: node.else.end, + end: endIndexFromFragment( + elseFragment, + () => ctx.code.indexOf("}", elseStart + 5) + 1, + ), }), }; ifBlock.else = elseBlock; ctx.scriptLet.nestBlock(elseBlock); - elseBlock.children.push(...convertChildren(node.else, elseBlock, ctx)); + elseBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(elseChildren), + }, + elseBlock, + ctx, + ), + ); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(elseBlock, ctx, { startOnly: true }); return ifBlock; } +function startBlockIndexForElse( + elseFragment: Compiler.Fragment | SvAST.ElseBlock, + beforeFragment: Compiler.Fragment | SvAST.IfBlock | SvAST.EachBlock, + lastExpression: ESTree.Node | { start: number; end: number }, + ctx: Context, +) { + let baseStart: number; + const elseChildren = getChildren(elseFragment); + if (elseChildren.length > 0) { + const c = elseChildren[0]; + baseStart = c.start; + if (c.type === "IfBlock" && c.elseif) { + baseStart = Math.min(baseStart, getWithLoc(getTestFromIfBlock(c)).start); + } + } else { + const beforeEnd = endIndexFromFragment(beforeFragment, () => { + return ctx.code.indexOf("}", getWithLoc(lastExpression).end) + 1; + }); + baseStart = beforeEnd + 1; + } + + return startBlockIndex(ctx.code, baseStart - 1, ":else"); +} + /** Convert for EachBlock */ export function convertEachBlock( - node: SvAST.EachBlock, + node: SvAST.EachBlock | Compiler.EachBlock, parent: SvelteEachBlock["parent"], ctx: Context, ): SvelteEachBlock { @@ -205,16 +309,33 @@ export function convertEachBlock( eachBlock.key = key; }); } - eachBlock.children.push(...convertChildren(node, eachBlock, ctx)); + const body = getBodyFromEachBlock(node); + eachBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(getChildren(body)), + }, + eachBlock, + ctx, + ), + ); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(eachBlock, ctx); - if (!node.else) { + const fallbackFragment = getFallbackFromEachBlock(node); + if (!fallbackFragment) { return eachBlock; } - const elseStart = startBlockIndex(ctx.code, node.else.start - 1, ":else"); + const elseStart = startBlockIndexForElse( + fallbackFragment, + body, + node.key || indexRange || node.context, + ctx, + ); const elseBlock: SvelteElseBlockAlone = { type: "SvelteElseBlock", @@ -223,13 +344,23 @@ export function convertEachBlock( parent: eachBlock, ...ctx.getConvertLocation({ start: elseStart, - end: node.else.end, + end: endIndexFromFragment(fallbackFragment, () => elseStart), }), }; eachBlock.else = elseBlock; ctx.scriptLet.nestBlock(elseBlock); - elseBlock.children.push(...convertChildren(node.else, elseBlock, ctx)); + elseBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(getChildren(fallbackFragment)), + }, + elseBlock, + ctx, + ), + ); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(elseBlock, ctx, { startOnly: true }); @@ -238,7 +369,7 @@ export function convertEachBlock( /** Convert for AwaitBlock */ export function convertAwaitBlock( - node: SvAST.AwaitBlock, + node: SvAST.AwaitBlock | Compiler.AwaitBlock, parent: SvelteAwaitBlock["parent"], ctx: Context, ): SvelteAwaitBlock { @@ -263,30 +394,40 @@ export function convertAwaitBlock( }, ); - if (!node.pending.skip) { + const pending = getPendingFromAwaitBlock(node); + if (pending) { const pendingBlock: SvelteAwaitPendingBlock = { type: "SvelteAwaitPendingBlock", children: [], parent: awaitBlock, ...ctx.getConvertLocation({ start: awaitBlock.range[0], - end: node.pending.end, + end: endIndexFromBlock(pending, node.expression, ctx), }), }; ctx.scriptLet.nestBlock(pendingBlock); - pendingBlock.children.push( - ...convertChildren(node.pending, pendingBlock, ctx), - ); + pendingBlock.children.push(...convertChildren(pending, pendingBlock, ctx)); awaitBlock.pending = pendingBlock; ctx.scriptLet.closeScope(); } - if (!node.then.skip) { - const awaitThen = Boolean(node.pending.skip); + const then = getThenFromAwaitBlock(node); + if (then) { + const awaitThen = !pending; if (awaitThen) { (awaitBlock as SvelteAwaitBlockAwaitThen).kind = "await-then"; } - const thenStart = awaitBlock.pending ? node.then.start : nodeStart; + const thenStart = awaitBlock.pending + ? startBlockIndex( + ctx.code, + node.value + ? getWithLoc(node.value).start + : startIndexFromFragment(then, () => { + return awaitBlock.pending.range[1]; + }), + ":then", + ) + : nodeStart; const thenBlock: SvelteAwaitThenBlock = { type: "SvelteAwaitThenBlock", awaitThen, @@ -295,7 +436,14 @@ export function convertAwaitBlock( parent: awaitBlock as any, ...ctx.getConvertLocation({ start: thenStart, - end: node.then.end, + end: endIndexFromFragment(then, () => { + return ( + ctx.code.indexOf( + "}", + node.value ? getWithLoc(node.value).end : thenStart + 5, + ) + 1 + ); + }), }), }; if (node.value) { @@ -352,7 +500,7 @@ export function convertAwaitBlock( } else { ctx.scriptLet.nestBlock(thenBlock); } - thenBlock.children.push(...convertChildren(node.then, thenBlock, ctx)); + thenBlock.children.push(...convertChildren(then, thenBlock, ctx)); if (awaitBlock.pending) { extractMustacheBlockTokens(thenBlock, ctx, { startOnly: true }); } else { @@ -368,13 +516,24 @@ export function convertAwaitBlock( awaitBlock.then = thenBlock; ctx.scriptLet.closeScope(); } - if (!node.catch.skip) { - const awaitCatch = Boolean(node.pending.skip && node.then.skip); + const catchFragment = getCatchFromAwaitBlock(node); + if (catchFragment) { + const awaitCatch = !pending && !then; if (awaitCatch) { (awaitBlock as SvelteAwaitBlockAwaitCatch).kind = "await-catch"; } const catchStart = - awaitBlock.pending || awaitBlock.then ? node.catch.start : nodeStart; + awaitBlock.then || awaitBlock.pending + ? startBlockIndex( + ctx.code, + node.error + ? getWithLoc(node.error).start + : startIndexFromFragment(catchFragment, () => { + return (awaitBlock.then || awaitBlock.pending).range[1]; + }), + ":catch", + ) + : nodeStart; const catchBlock = { type: "SvelteAwaitCatchBlock", awaitCatch, @@ -383,7 +542,14 @@ export function convertAwaitBlock( parent: awaitBlock, ...ctx.getConvertLocation({ start: catchStart, - end: node.catch.end, + end: endIndexFromFragment(catchFragment, () => { + return ( + ctx.code.indexOf( + "}", + node.error ? getWithLoc(node.error).end : catchStart + 6, + ) + 1 + ); + }), }), } as SvelteAwaitCatchBlock; @@ -401,7 +567,9 @@ export function convertAwaitBlock( } else { ctx.scriptLet.nestBlock(catchBlock); } - catchBlock.children.push(...convertChildren(node.catch, catchBlock, ctx)); + catchBlock.children.push( + ...convertChildren(catchFragment, catchBlock, ctx), + ); if (awaitBlock.pending || awaitBlock.then) { extractMustacheBlockTokens(catchBlock, ctx, { startOnly: true }); } else { @@ -425,7 +593,7 @@ export function convertAwaitBlock( /** Convert for KeyBlock */ export function convertKeyBlock( - node: SvAST.KeyBlock, + node: SvAST.KeyBlock | Compiler.KeyBlock, parent: SvelteKeyBlock["parent"], ctx: Context, ): SvelteKeyBlock { @@ -443,7 +611,17 @@ export function convertKeyBlock( }); ctx.scriptLet.nestBlock(keyBlock); - keyBlock.children.push(...convertChildren(node, keyBlock, ctx)); + keyBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(getChildren(getFragment(node))), + }, + keyBlock, + ctx, + ), + ); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(keyBlock, ctx); @@ -453,7 +631,7 @@ export function convertKeyBlock( /** Convert for SnippetBlock */ export function convertSnippetBlock( - node: SvAST.SnippetBlock, + node: Compiler.SnippetBlock, parent: SvelteSnippetBlock["parent"], ctx: Context, ): SvelteSnippetBlock { @@ -487,7 +665,17 @@ export function convertSnippetBlock( }, ); - snippetBlock.children.push(...convertChildren(node, snippetBlock, ctx)); + snippetBlock.children.push( + ...convertChildren( + { + nodes: + // Adjust for Svelte v5 + trimChildren(node.body.nodes), + }, + snippetBlock, + ctx, + ), + ); ctx.scriptLet.closeScope(); extractMustacheBlockTokens(snippetBlock, ctx); diff --git a/src/parser/converts/const.ts b/src/parser/converts/const.ts index 9aeea7b6..3f9684c5 100644 --- a/src/parser/converts/const.ts +++ b/src/parser/converts/const.ts @@ -1,10 +1,12 @@ import type { SvelteConstTag } from "../../ast"; import type { Context } from "../../context"; +import { getDeclaratorFromConstTag } from "../compat"; import type * as SvAST from "../svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; /** Convert for ConstTag */ export function convertConstTag( - node: SvAST.ConstTag, + node: SvAST.ConstTag | Compiler.ConstTag, parent: SvelteConstTag["parent"], ctx: Context, ): SvelteConstTag { @@ -15,7 +17,7 @@ export function convertConstTag( ...ctx.getConvertLocation(node), }; ctx.scriptLet.addVariableDeclarator( - node.expression, + getDeclaratorFromConstTag(node), mustache, (declaration) => { mustache.declaration = declaration; diff --git a/src/parser/converts/element.ts b/src/parser/converts/element.ts index a4174cd4..4a87bc69 100644 --- a/src/parser/converts/element.ts +++ b/src/parser/converts/element.ts @@ -30,6 +30,7 @@ import type { import type ESTree from "estree"; import type { Context } from "../../context"; import type * as SvAST from "../svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; import { convertAwaitBlock, @@ -51,10 +52,15 @@ import { sortNodes } from "../sort"; import type { ScriptLetBlockParam } from "../../context/script-let"; import { ParseError } from "../.."; import { convertRenderTag } from "./render"; +import type { Child } from "../compat"; +import { getChildren, getFragment } from "../compat"; /** Convert for Fragment or Element or ... */ export function* convertChildren( - fragment: { children?: SvAST.TemplateNode[] }, + fragment: + | { children?: SvAST.TemplateNode[] } + | Compiler.Fragment + | { nodes: (Child | SvAST.TemplateNode)[] }, parent: | SvelteProgram | SvelteElement @@ -81,8 +87,9 @@ export function* convertChildren( | SvelteSnippetBlock | SvelteHTMLComment > { - if (!fragment.children) return; - for (const child of fragment.children) { + const children = getChildren(fragment); + if (!children) return; + for (const child of children) { if (child.type === "Comment") { yield convertComment(child, parent, ctx); continue; @@ -94,6 +101,10 @@ export function* convertChildren( yield convertText(child, parent, ctx); continue; } + if (child.type === "RegularElement") { + yield convertHTMLElement(child, parent, ctx); + continue; + } if (child.type === "Element") { if (child.name.includes(":")) { yield convertSpecialElement(child, parent, ctx); @@ -102,6 +113,10 @@ export function* convertChildren( } continue; } + if (child.type === "Component") { + yield convertComponentElement(child, parent, ctx); + continue; + } if (child.type === "InlineComponent") { if (child.name.includes(":")) { yield convertSpecialElement(child, parent, ctx); @@ -110,15 +125,23 @@ export function* convertChildren( } continue; } - if (child.type === "Slot") { + if ( + child.type === "SvelteComponent" || + child.type === "SvelteElement" || + child.type === "SvelteSelf" + ) { + yield convertSpecialElement(child, parent, ctx); + continue; + } + if (child.type === "SlotElement" || child.type === "Slot") { yield convertSlotElement(child, parent, ctx); continue; } - if (child.type === "MustacheTag") { + if (child.type === "ExpressionTag" || child.type === "MustacheTag") { yield convertMustacheTag(child, parent, null, ctx); continue; } - if (child.type === "RawMustacheTag") { + if (child.type === "HtmlTag" || child.type === "RawMustacheTag") { yield convertRawMustacheTag(child, parent, ctx); continue; } @@ -147,27 +170,27 @@ export function* convertChildren( yield convertSnippetBlock(child, parent, ctx); continue; } - if (child.type === "Window") { + if (child.type === "SvelteWindow" || child.type === "Window") { yield convertWindowElement(child, parent, ctx); continue; } - if (child.type === "Body") { + if (child.type === "SvelteBody" || child.type === "Body") { yield convertBodyElement(child, parent, ctx); continue; } - if (child.type === "Head") { + if (child.type === "SvelteHead" || child.type === "Head") { yield convertHeadElement(child, parent, ctx); continue; } - if (child.type === "Title") { + if (child.type === "TitleElement" || child.type === "Title") { yield convertTitleElement(child, parent, ctx); continue; } - if (child.type === "Options") { + if (child.type === "SvelteOptions" || child.type === "Options") { yield convertOptionsElement(child, parent, ctx); continue; } - if (child.type === "SlotTemplate") { + if (child.type === "SvelteFragment" || child.type === "SlotTemplate") { yield convertSlotTemplateElement(child, parent, ctx); continue; } @@ -183,7 +206,7 @@ export function* convertChildren( yield convertRenderTag(child, parent, ctx); continue; } - if (child.type === "Document") { + if (child.type === "SvelteDocument" || child.type === "Document") { yield convertDocumentElement(child, parent, ctx); continue; } @@ -194,16 +217,29 @@ export function* convertChildren( /** Extract `let:` directives. */ function extractLetDirectives(fragment: { - attributes: SvAST.AttributeOrDirective[]; + attributes: + | SvAST.AttributeOrDirective[] + | (Compiler.Attribute | Compiler.SpreadAttribute | Compiler.Directive)[]; }): { - letDirectives: SvAST.LetDirective[]; - attributes: Exclude[]; + letDirectives: (SvAST.LetDirective | Compiler.LetDirective)[]; + attributes: Exclude< + | SvAST.AttributeOrDirective + | Compiler.Attribute + | Compiler.SpreadAttribute + | Compiler.Directive, + SvAST.LetDirective | Compiler.LetDirective + >[]; } { - const letDirectives: SvAST.LetDirective[] = []; - const attributes: Exclude[] = - []; + const letDirectives: (SvAST.LetDirective | Compiler.LetDirective)[] = []; + const attributes: Exclude< + | SvAST.AttributeOrDirective + | Compiler.Attribute + | Compiler.SpreadAttribute + | Compiler.Directive, + SvAST.LetDirective | Compiler.LetDirective + >[] = []; for (const attr of fragment.attributes) { - if (attr.type === "Let") { + if (attr.type === "LetDirective" || attr.type === "Let") { letDirectives.push(attr); } else { attributes.push(attr); @@ -213,11 +249,16 @@ function extractLetDirectives(fragment: { } /** Check if children needs a scope. */ -function needScopeByChildren(fragment: { - children?: SvAST.TemplateNode[]; -}): boolean { - if (!fragment.children) return false; - for (const child of fragment.children) { +function needScopeByChildren( + fragment: + | { + children?: SvAST.TemplateNode[]; + } + | Compiler.Fragment, +): boolean { + const children = getChildren(fragment); + if (!children) return false; + for (const child of children) { if (child.type === "ConstTag") { return true; } @@ -248,7 +289,13 @@ function convertComment( /** Convert for HTMLElement */ function convertHTMLElement( - node: SvAST.Element | SvAST.Slot | SvAST.Title, + node: + | SvAST.Element + | SvAST.Slot + | SvAST.Title + | Compiler.RegularElement + | Compiler.SlotElement + | Compiler.TitleElement, parent: SvelteHTMLElement["parent"], ctx: Context, ): SvelteHTMLElement { @@ -289,18 +336,19 @@ function convertHTMLElement( ); letParams.push(...ctx.letDirCollections.extract().getLetParams()); } - if (!letParams.length && !needScopeByChildren(node)) { + const fragment = getFragment(node); + if (!letParams.length && !needScopeByChildren(fragment)) { element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); } else { ctx.scriptLet.nestBlock(element, letParams); element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); sortNodes(element.startTag.attributes); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); ctx.scriptLet.closeScope(); } @@ -358,7 +406,16 @@ function convertSpecialElement( | SvAST.Body | SvAST.Head | SvAST.Options - | SvAST.SlotTemplate, + | SvAST.SlotTemplate + | Compiler.SvelteComponent + | Compiler.SvelteElement + | Compiler.SvelteWindow + | Compiler.SvelteBody + | Compiler.SvelteHead + | Compiler.SvelteDocument + | Compiler.SvelteFragment + | Compiler.SvelteSelf + | Compiler.SvelteOptionsRaw, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -399,22 +456,25 @@ function convertSpecialElement( ); letParams.push(...ctx.letDirCollections.extract().getLetParams()); } - if (!letParams.length && !needScopeByChildren(node)) { + const fragment = getFragment(node); + if (!letParams.length && !needScopeByChildren(fragment)) { element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); } else { ctx.scriptLet.nestBlock(element, letParams); element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); sortNodes(element.startTag.attributes); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); ctx.scriptLet.closeScope(); } const thisExpression = + (node.type === "SvelteComponent" && node.expression) || + (node.type === "SvelteElement" && node.tag) || (node.type === "InlineComponent" && elementName === "svelte:component" && node.expression) || @@ -441,123 +501,39 @@ function convertSpecialElement( /** process `this=` */ function processThisAttribute( - node: SvAST.SvelteElement | SvAST.InlineSvelteComponent, + node: + | SvAST.SvelteElement + | SvAST.InlineSvelteComponent + | Compiler.SvelteElement + | Compiler.SvelteComponent, thisValue: string | ESTree.Expression, element: SvelteSpecialElement, ctx: Context, ) { + const startIndex = findStartIndexOfThis(node, ctx); + const eqIndex = ctx.code.indexOf("=", startIndex + 4 /* t,h,i,s */); + let thisNode: SvelteSpecialDirective | SvelteAttribute; if (typeof thisValue === "string") { + // Svelte v4 // this="..." - const startIndex = findStartIndexOfThis(node, ctx); - const eqIndex = ctx.code.indexOf("=", startIndex + 4 /* t,h,i,s */); + thisNode = createSvelteAttribute(startIndex, eqIndex, thisValue); + } else { + // this={...} const valueStartIndex = indexOf( ctx.code, (c) => Boolean(c.trim()), eqIndex + 1, ); - if (ctx.code[valueStartIndex] === "{") { - // Svelte v5 `this={"..."}` - const openingQuoteIndex = indexOf( - ctx.code, - (c) => c === '"' || c === "'", - valueStartIndex + 1, - ); - const quote = ctx.code[openingQuoteIndex]; - const closingQuoteIndex = indexOf( - ctx.code, - (c) => c === quote, - openingQuoteIndex + thisValue.length, - ); - const closeIndex = ctx.code.indexOf("}", closingQuoteIndex + 1); - const endIndex = indexOf( - ctx.code, - (c) => c === ">" || !c.trim(), - closeIndex, - ); - thisNode = createSvelteSpecialDirective(startIndex, endIndex, eqIndex, { - type: "Literal", - value: thisValue, - range: [openingQuoteIndex, closingQuoteIndex + 1], - }); + if ( + thisValue.type === "Literal" && + typeof thisValue.value === "string" && + ctx.code[valueStartIndex] !== "{" + ) { + thisNode = createSvelteAttribute(startIndex, eqIndex, thisValue.value); } else { - const quote = ctx.code.startsWith(thisValue, valueStartIndex) - ? null - : ctx.code[valueStartIndex]; - const literalStartIndex = quote - ? valueStartIndex + quote.length - : valueStartIndex; - const literalEndIndex = literalStartIndex + thisValue.length; - const endIndex = quote ? literalEndIndex + quote.length : literalEndIndex; - const thisAttr: SvelteAttribute = { - type: "SvelteAttribute", - key: null as any, - boolean: false, - value: [], - parent: element.startTag, - ...ctx.getConvertLocation({ start: startIndex, end: endIndex }), - }; - thisAttr.key = { - type: "SvelteName", - name: "this", - parent: thisAttr, - ...ctx.getConvertLocation({ start: startIndex, end: eqIndex }), - }; - thisAttr.value.push({ - type: "SvelteLiteral", - value: thisValue, - parent: thisAttr, - ...ctx.getConvertLocation({ - start: literalStartIndex, - end: literalEndIndex, - }), - }); - // this - ctx.addToken("HTMLIdentifier", { - start: startIndex, - end: startIndex + 4, - }); - // = - ctx.addToken("Punctuator", { - start: eqIndex, - end: eqIndex + 1, - }); - if (quote) { - // " - ctx.addToken("Punctuator", { - start: valueStartIndex, - end: literalStartIndex, - }); - } - ctx.addToken("HTMLText", { - start: literalStartIndex, - end: literalEndIndex, - }); - if (quote) { - // " - ctx.addToken("Punctuator", { - start: literalEndIndex, - end: endIndex, - }); - } - thisNode = thisAttr; + thisNode = createSvelteSpecialDirective(startIndex, eqIndex, thisValue); } - } else { - // this={...} - const eqIndex = ctx.code.lastIndexOf("=", getWithLoc(thisValue).start); - const startIndex = ctx.code.lastIndexOf("this", eqIndex); - const closeIndex = ctx.code.indexOf("}", getWithLoc(thisValue).end); - const endIndex = indexOf( - ctx.code, - (c) => c === ">" || !c.trim(), - closeIndex, - ); - thisNode = createSvelteSpecialDirective( - startIndex, - endIndex, - eqIndex, - thisValue, - ); } const targetIndex = element.startTag.attributes.findIndex( @@ -569,13 +545,91 @@ function processThisAttribute( element.startTag.attributes.splice(targetIndex, 0, thisNode); } + /** Create SvelteAttribute */ + function createSvelteAttribute( + startIndex: number, + eqIndex: number, + thisValue: string, + ): SvelteAttribute { + const valueStartIndex = indexOf( + ctx.code, + (c) => Boolean(c.trim()), + eqIndex + 1, + ); + const quote = ctx.code.startsWith(thisValue, valueStartIndex) + ? null + : ctx.code[valueStartIndex]; + const literalStartIndex = quote + ? valueStartIndex + quote.length + : valueStartIndex; + const literalEndIndex = literalStartIndex + thisValue.length; + const endIndex = quote ? literalEndIndex + quote.length : literalEndIndex; + const thisAttr: SvelteAttribute = { + type: "SvelteAttribute", + key: null as any, + boolean: false, + value: [], + parent: element.startTag, + ...ctx.getConvertLocation({ start: startIndex, end: endIndex }), + }; + thisAttr.key = { + type: "SvelteName", + name: "this", + parent: thisAttr, + ...ctx.getConvertLocation({ start: startIndex, end: eqIndex }), + }; + thisAttr.value.push({ + type: "SvelteLiteral", + value: thisValue, + parent: thisAttr, + ...ctx.getConvertLocation({ + start: literalStartIndex, + end: literalEndIndex, + }), + }); + // this + ctx.addToken("HTMLIdentifier", { + start: startIndex, + end: startIndex + 4, + }); + // = + ctx.addToken("Punctuator", { + start: eqIndex, + end: eqIndex + 1, + }); + if (quote) { + // " + ctx.addToken("Punctuator", { + start: valueStartIndex, + end: literalStartIndex, + }); + } + ctx.addToken("HTMLText", { + start: literalStartIndex, + end: literalEndIndex, + }); + if (quote) { + // " + ctx.addToken("Punctuator", { + start: literalEndIndex, + end: endIndex, + }); + } + return thisAttr; + } + /** Create SvelteSpecialDirective */ function createSvelteSpecialDirective( startIndex: number, - endIndex: number, eqIndex: number, expression: ESTree.Expression, ): SvelteSpecialDirective { + const closeIndex = ctx.code.indexOf("}", getWithLoc(expression).end); + const endIndex = indexOf( + ctx.code, + (c) => c === ">" || !c.trim(), + closeIndex, + ); const thisDir: SvelteSpecialDirective = { type: "SvelteSpecialDirective", kind: "this", @@ -609,7 +663,11 @@ function processThisAttribute( /** Find the start index of `this` */ function findStartIndexOfThis( - node: SvAST.SvelteElement | SvAST.InlineSvelteComponent, + node: + | SvAST.SvelteElement + | SvAST.InlineSvelteComponent + | Compiler.SvelteElement + | Compiler.SvelteComponent, ctx: Context, ) { // Get the end index of `svelte:element` @@ -641,7 +699,7 @@ function findStartIndexOfThis( /** Convert for ComponentElement */ function convertComponentElement( - node: SvAST.InlineComponent, + node: SvAST.InlineComponent | Compiler.Component, parent: SvelteComponentElement["parent"], ctx: Context, ): SvelteComponentElement { @@ -682,18 +740,19 @@ function convertComponentElement( ); letParams.push(...ctx.letDirCollections.extract().getLetParams()); } - if (!letParams.length && !needScopeByChildren(node)) { + const fragment = getFragment(node); + if (!letParams.length && !needScopeByChildren(fragment)) { element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); } else { ctx.scriptLet.nestBlock(element, letParams); element.startTag.attributes.push( ...convertAttributes(attributes, element.startTag, ctx), ); sortNodes(element.startTag.attributes); - element.children.push(...convertChildren(node, element, ctx)); + element.children.push(...convertChildren(fragment, element, ctx)); ctx.scriptLet.closeScope(); } @@ -766,7 +825,7 @@ function convertComponentElement( /** Convert for Slot */ function convertSlotElement( - node: SvAST.Slot, + node: SvAST.Slot | Compiler.SlotElement, parent: SvelteHTMLElement["parent"], ctx: Context, ): SvelteHTMLElement { @@ -778,7 +837,7 @@ function convertSlotElement( /** Convert for window element. e.g. */ function convertWindowElement( - node: SvAST.Window, + node: SvAST.Window | Compiler.SvelteWindow, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -787,7 +846,7 @@ function convertWindowElement( /** Convert for document element. e.g. */ function convertDocumentElement( - node: SvAST.Document, + node: SvAST.Document | Compiler.SvelteDocument, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -796,7 +855,7 @@ function convertDocumentElement( /** Convert for body element. e.g. */ function convertBodyElement( - node: SvAST.Body, + node: SvAST.Body | Compiler.SvelteBody, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -805,7 +864,7 @@ function convertBodyElement( /** Convert for head element. e.g. */ function convertHeadElement( - node: SvAST.Head, + node: SvAST.Head | Compiler.SvelteHead, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -814,7 +873,7 @@ function convertHeadElement( /** Convert for title element. e.g. */ function convertTitleElement( - node: SvAST.Title, + node: SvAST.Title | Compiler.TitleElement, parent: SvelteHTMLElement["parent"], ctx: Context, ): SvelteHTMLElement { @@ -823,7 +882,7 @@ function convertTitleElement( /** Convert for options element. e.g. <svelte:options> */ function convertOptionsElement( - node: SvAST.Options, + node: SvAST.Options | Compiler.SvelteOptionsRaw, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { @@ -832,7 +891,7 @@ function convertOptionsElement( /** Convert for <svelte:fragment> element. */ function convertSlotTemplateElement( - node: SvAST.SlotTemplate, + node: SvAST.SlotTemplate | Compiler.SvelteFragment, parent: SvelteSpecialElement["parent"], ctx: Context, ): SvelteSpecialElement { diff --git a/src/parser/converts/mustache.ts b/src/parser/converts/mustache.ts index c1be5371..6057e7b9 100644 --- a/src/parser/converts/mustache.ts +++ b/src/parser/converts/mustache.ts @@ -7,10 +7,11 @@ import type { import type { Context } from "../../context"; import type * as SvAST from "../svelte-ast-types"; import { hasTypeInfo } from "../../utils"; +import type * as Compiler from "svelte/compiler"; /** Convert for MustacheTag */ export function convertMustacheTag( - node: SvAST.MustacheTag, + node: SvAST.MustacheTag | Compiler.ExpressionTag, parent: SvelteMustacheTag["parent"], typing: string | null, ctx: Context, @@ -19,7 +20,7 @@ export function convertMustacheTag( } /** Convert for MustacheTag */ export function convertRawMustacheTag( - node: SvAST.RawMustacheTag, + node: SvAST.RawMustacheTag | Compiler.HtmlTag, parent: SvelteMustacheTag["parent"], ctx: Context, ): SvelteMustacheTagRaw { @@ -65,7 +66,11 @@ export function convertDebugTag( /** Convert to MustacheTag */ function convertMustacheTag0<T extends SvelteMustacheTag>( - node: SvAST.MustacheTag | SvAST.RawMustacheTag, + node: + | SvAST.MustacheTag + | SvAST.RawMustacheTag + | Compiler.ExpressionTag + | Compiler.HtmlTag, kind: T["kind"], parent: T["parent"], typing: string | null, diff --git a/src/parser/converts/render.ts b/src/parser/converts/render.ts index 27aa62b1..d64c1b90 100644 --- a/src/parser/converts/render.ts +++ b/src/parser/converts/render.ts @@ -1,12 +1,12 @@ import type * as ESTree from "estree"; import type { SvelteRenderTag } from "../../ast"; import type { Context } from "../../context"; -import type * as SvAST from "../svelte-ast-types"; import { getWithLoc } from "./common"; +import type * as Compiler from "svelte/compiler"; /** Convert for RenderTag */ export function convertRenderTag( - node: SvAST.RenderTag, + node: Compiler.RenderTag, parent: SvelteRenderTag["parent"], ctx: Context, ): SvelteRenderTag { diff --git a/src/parser/converts/root.ts b/src/parser/converts/root.ts index a0b01cb6..6cfb117d 100644 --- a/src/parser/converts/root.ts +++ b/src/parser/converts/root.ts @@ -1,4 +1,5 @@ import type * as SvAST from "../svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; import type { SvelteAttribute, SvelteGenericsDirective, @@ -11,19 +12,27 @@ import type { import {} from "./common"; import type { Context } from "../../context"; import { convertChildren, extractElementTags } from "./element"; -import { convertAttributeTokens } from "./attr"; +import { convertAttributes } from "./attr"; import type { Scope } from "eslint-scope"; import { parseScriptWithoutAnalyzeScope } from "../script"; import type { TSESParseForESLintResult } from "../typescript/types"; import type * as ESTree from "estree"; import type { TSESTree } from "@typescript-eslint/types"; import { fixLocations } from "../../context/fix-locations"; +import { + getChildren, + getFragmentFromRoot, + getInstanceFromRoot, + getModuleFromRoot, + getOptionsFromRoot, +} from "../compat"; +import { sortNodes } from "../sort"; /** * Convert root */ export function convertSvelteRoot( - svelteAst: SvAST.Ast, + svelteAst: Compiler.Root | SvAST.AstLegacy, ctx: Context, ): SvelteProgram { const ast: SvelteProgram = { @@ -36,12 +45,28 @@ export function convertSvelteRoot( ...ctx.getConvertLocation({ start: 0, end: ctx.code.length }), }; const body = ast.body; - if (svelteAst.html) { - const fragment = svelteAst.html; - body.push(...convertChildren(fragment, ast, ctx)); + const fragment = getFragmentFromRoot(svelteAst); + if (fragment) { + let children = getChildren(fragment); + const options = getOptionsFromRoot(svelteAst, ctx.code); + if (options) { + children = [...children]; + if ( + !children.some((node, idx) => { + if (options.end <= node.start) { + children.splice(idx, 0, options); + return true; + } + return false; + }) + ) { + children.push(options); + } + } + body.push(...convertChildren({ nodes: children }, ast, ctx)); } - if (svelteAst.instance) { - const instance = svelteAst.instance; + const instance = getInstanceFromRoot(svelteAst); + if (instance) { const script: SvelteScriptElement = { type: "SvelteScriptElement", name: null as any, @@ -68,8 +93,9 @@ export function convertSvelteRoot( }); body.push(script); } - if (svelteAst.module) { - const module = svelteAst.module; + + const module = getModuleFromRoot(svelteAst); + if (module) { const script: SvelteScriptElement = { type: "SvelteScriptElement", name: null as any, @@ -187,7 +213,7 @@ function extractAttributes( const block = ctx.findBlock(element); if (block) { element.startTag.attributes.push( - ...convertAttributeTokens(block.attrs, element.startTag, ctx), + ...convertAttributes(block.attrs, element.startTag, ctx), ); } } @@ -235,6 +261,7 @@ function convertGenericsAttribute(script: SvelteScriptElement, ctx: Context) { delete (genericsAttribute as any).value; // Remove value token indexes + sortNodes(ctx.tokens); const firstTokenIndex = ctx.tokens.findIndex( (token) => value.range[0] <= token.range[0] && token.range[1] <= value.range[1], diff --git a/src/parser/converts/text.ts b/src/parser/converts/text.ts index 89f5d0ad..21289a1c 100644 --- a/src/parser/converts/text.ts +++ b/src/parser/converts/text.ts @@ -1,6 +1,5 @@ import type { SvelteLiteral, SvelteText } from "../../ast"; import type { Context } from "../../context"; -import type { AttributeValueToken } from "../html"; import type * as SvAST from "../svelte-ast-types"; /** Convert for Text */ export function convertText( @@ -34,25 +33,6 @@ export function convertTextToLiteral( return text; } -/** Convert for AttributeValueToken to Literal */ -export function convertAttributeValueTokenToLiteral( - node: AttributeValueToken, - parent: SvelteLiteral["parent"], - ctx: Context, -): SvelteLiteral { - const valueLoc = node.quote - ? { start: node.start + 1, end: node.end - 1 } - : node; - const text: SvelteLiteral = { - type: "SvelteLiteral", - value: node.value, - parent, - ...ctx.getConvertLocation(valueLoc), - }; - extractTextTokens(valueLoc, ctx); - return text; -} - /** Extract tokens */ function extractTextTokens( node: { start: number; end: number }, diff --git a/src/parser/espree.ts b/src/parser/espree.ts index 3c5b21ab..ae8dc73e 100644 --- a/src/parser/espree.ts +++ b/src/parser/espree.ts @@ -19,7 +19,8 @@ const createRequire: (filename: string) => (modName: string) => any = return mod.exports; }); -let espreeCache: BasicParserObject | null = null; +let espreeCache: (BasicParserObject & { latestEcmaVersion: number }) | null = + null; /** Checks if given path is linter path */ function isLinterPath(p: string): boolean { @@ -35,7 +36,7 @@ function isLinterPath(p: string): boolean { * Load `espree` from the loaded ESLint. * If the loaded ESLint was not found, just returns `require("espree")`. */ -export function getEspree(): BasicParserObject { +export function getEspree(): BasicParserObject & { latestEcmaVersion: number } { if (!espreeCache) { // Lookup the loaded eslint const linterPath = Object.keys(require.cache || {}).find(isLinterPath); diff --git a/src/parser/html.ts b/src/parser/html.ts index 09330f29..34851f24 100644 --- a/src/parser/html.ts +++ b/src/parser/html.ts @@ -1,204 +1,329 @@ -export type AttributeToken = { - key: AttributeKeyToken; - value: AttributeValueToken | null; -}; -export type AttributeKeyToken = { - name: string; - start: number; - end: number; -}; -export type AttributeValueToken = { - value: string; - quote: '"' | "'" | null; - start: number; - end: number; -}; - -const spacePattern = /\s/; +import type * as Compiler from "svelte/compiler"; +import type ESTree from "estree"; +import { getEspree } from "./espree"; + +const RE_IS_SPACE = /^\s$/u; + +class State { + public readonly code: string; + + public index: number; + + public constructor(code: string, index: number) { + this.code = code; + this.index = index; + } + + public getCurr(): string | undefined { + return this.code[this.index]; + } + + public skipSpaces(): void { + while (this.currIsSpace()) { + this.advance(); + if (this.eof()) break; + } + } + + public currIsSpace(): boolean { + return RE_IS_SPACE.test(this.getCurr() || ""); + } + + public currIs(expect: string): boolean { + return this.code.startsWith(expect, this.index); + } + + public eof(): boolean { + return this.index >= this.code.length; + } + + public eat<E extends string>(expect: E): E | null { + if (!this.currIs(expect)) { + return null; + } + this.index += expect.length; + return expect; + } + + public advance(): string | undefined { + this.index++; + return this.getCurr(); + } +} /** Parse HTML attributes */ export function parseAttributes( code: string, startIndex: number, -): { attributes: AttributeToken[]; index: number } { - const attributes: AttributeToken[] = []; - - let index = startIndex; - while (index < code.length) { - const char = code[index]; - if (spacePattern.test(char)) { - index++; - continue; - } - if (char === ">" || (char === "/" && code[index + 1] === ">")) break; - const attrData = parseAttribute(code, index); - attributes.push(attrData.attribute); - index = attrData.index; +): { + attributes: Compiler.Attribute[]; + index: number; +} { + const attributes: Compiler.Attribute[] = []; + + const state = new State(code, startIndex); + + while (!state.eof()) { + state.skipSpaces(); + if (state.currIs(">") || state.currIs("/>") || state.eof()) break; + attributes.push(parseAttribute(state)); } - return { attributes, index }; + return { attributes, index: state.index }; } /** Parse HTML attribute */ -function parseAttribute( - code: string, - startIndex: number, -): { attribute: AttributeToken; index: number } { +function parseAttribute(state: State): Compiler.Attribute { + const start = state.index; // parse key - const keyData = parseAttributeKey(code, startIndex); - const key = keyData.key; - let index = keyData.index; - if (code[index] !== "=") { + const key = parseAttributeKey(state); + const keyEnd = state.index; + state.skipSpaces(); + if (!state.eat("=")) { return { - attribute: { - key, - value: null, - }, - index, + type: "Attribute", + name: key, + value: true, + start, + end: keyEnd, + metadata: null as any, + parent: null, }; } - - index++; - - // skip spaces - while (index < code.length) { - const char = code[index]; - if (spacePattern.test(char)) { - index++; - continue; - } - break; + state.skipSpaces(); + if (state.eof()) { + return { + type: "Attribute", + name: key, + value: true, + start, + end: keyEnd, + metadata: null as any, + parent: null, + }; } - // parse value - const valueData = parseAttributeValue(code, index); - + const value = parseAttributeValue(state); return { - attribute: { - key, - value: valueData.value, - }, - index: valueData.index, + type: "Attribute", + name: key, + value: [value], + start, + end: state.index, + metadata: null as any, + parent: null, }; } /** Parse HTML attribute key */ -function parseAttributeKey( - code: string, - startIndex: number, -): { key: AttributeKeyToken; index: number } { - const key: AttributeKeyToken = { - name: code[startIndex], - start: startIndex, - end: startIndex + 1, - }; - let index = key.end; - while (index < code.length) { - const char = code[index]; +function parseAttributeKey(state: State): string { + const start = state.index; + while (state.advance()) { if ( - char === "=" || - char === ">" || - (char === "/" && code[index + 1] === ">") + state.currIs("=") || + state.currIs(">") || + state.currIs("/>") || + state.currIsSpace() ) { break; } - if (spacePattern.test(char)) { - for (let i = index; i < code.length; i++) { - const c = code[i]; - if (c === "=") { - return { - key, - index: i, - }; - } - if (spacePattern.test(c)) { - continue; - } + } + const end = state.index; + return state.code.slice(start, end); +} + +/** Parse HTML attribute value */ +function parseAttributeValue( + state: State, +): Compiler.Text | Compiler.ExpressionTag { + const start = state.index; + const quote = state.eat('"') || state.eat("'"); + + const startBk = state.index; + const expression = parseAttributeMustache(state); + if (expression) { + if (!quote || state.eat(quote)) { + const end = state.index; + return { + type: "ExpressionTag", + expression, + start, + end, + metadata: null as any, + parent: null, + }; + } + } + state.index = startBk; + + if (quote) { + if (state.eof()) { + return { + type: "Text", + data: quote, + raw: quote, + start, + end: state.index, + parent: null, + }; + } + let c: string | undefined; + while ((c = state.getCurr())) { + state.advance(); + if (c === quote) { + const end = state.index; + const data = state.code.slice(start + 1, end - 1); return { - key, - index, + type: "Text", + data, + raw: data, + start: start + 1, + end: end - 1, + parent: null, }; } - break; } - key.name += char; - index++; - key.end = index; + } else { + while (state.advance()) { + if (state.currIsSpace() || state.currIs(">") || state.currIs("/>")) { + break; + } + } } + const end = state.index; + const data = state.code.slice(start, end); return { - key, - index, + type: "Text", + data, + raw: data, + start, + end, + parent: null, }; } -/** Parse HTML attribute value */ -function parseAttributeValue( - code: string, - startIndex: number, -): { value: AttributeValueToken | null; index: number } { - let index = startIndex; - const maybeQuote = code[index]; - if (maybeQuote == null) { - return { - value: null, - index, - }; +/** Parse mustache */ +function parseAttributeMustache(state: State): + | (ESTree.Expression & { + start: number; + end: number; + }) + | null { + if (!state.eat("{")) { + return null; } - const quote = maybeQuote === '"' || maybeQuote === "'" ? maybeQuote : null; - if (quote) { - index++; + // parse simple expression + const leadingComments: ESTree.Comment[] = []; + const startBk = state.index; + state.skipSpaces(); + let start = state.index; + while (!state.eof()) { + if (state.eat("//")) { + leadingComments.push(parseInlineComment(state.index - 2)); + state.skipSpaces(); + start = state.index; + continue; + } + if (state.eat("/*")) { + leadingComments.push(parseBlockComment(state.index - 2)); + state.skipSpaces(); + start = state.index; + continue; + } + const stringQuote = state.eat('"') || state.eat("'"); + if (stringQuote) { + skipString(stringQuote); + state.skipSpaces(); + continue; + } + const endCandidate = state.index; + state.skipSpaces(); + if (state.eat("}")) { + const end = endCandidate; + try { + const espree = getEspree(); + + const expression = ( + espree.parse(state.code.slice(start, end), { + ecmaVersion: espree.latestEcmaVersion, + }).body[0] as ESTree.ExpressionStatement + ).expression; + delete expression.range; + return { + ...expression, + leadingComments, + start, + end, + }; + } catch { + break; + } + } + state.advance(); } - const valueFirstChar = code[index]; - if (valueFirstChar == null) { + state.index = startBk; + return null; + + function parseInlineComment(tokenStart: number): ESTree.Comment & { + start: number; + end: number; + } { + const valueStart = state.index; + let valueEnd: number | null = null; + while (!state.eof()) { + if (state.eat("\n")) { + valueEnd = state.index - 1; + break; + } + state.advance(); + } + if (valueEnd == null) { + valueEnd = state.index; + } + return { - value: { - value: maybeQuote, - quote: null, - start: startIndex, - end: index, - }, - index, + type: "Line", + value: state.code.slice(valueStart, valueEnd), + start: tokenStart, + end: state.index, }; } - if (valueFirstChar === quote) { + + function parseBlockComment(tokenStart: number): ESTree.Comment & { + start: number; + end: number; + } { + const valueStart = state.index; + let valueEnd: number | null = null; + while (!state.eof()) { + if (state.eat("*/")) { + valueEnd = state.index - 2; + break; + } + state.advance(); + } + if (valueEnd == null) { + valueEnd = state.index; + } + return { - value: { - value: "", - quote, - start: startIndex, - end: index + 1, - }, - index: index + 1, + type: "Block", + value: state.code.slice(valueStart, valueEnd), + start: tokenStart, + end: state.index, }; } - const value: AttributeValueToken = { - value: valueFirstChar, - quote, - start: startIndex, - end: index + 1, - }; - index = value.end; - while (index < code.length) { - const char = code[index]; - if (quote) { - if (quote === char) { - index++; - value.end = index; + + function skipString(stringQuote: string) { + while (!state.eof()) { + if (state.eat(stringQuote)) { break; } - } else if ( - spacePattern.test(char) || - char === ">" || - (char === "/" && code[index + 1] === ">") - ) { - break; + if (state.eat("\\")) { + // escape + state.advance(); + } + state.advance(); } - value.value += char; - index++; - value.end = index; } - return { - value, - index, - }; } diff --git a/src/parser/index.ts b/src/parser/index.ts index 9b8e1980..34c75371 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -12,6 +12,7 @@ import type { ScopeManager } from "eslint-scope"; import { Variable } from "eslint-scope"; import { parseScript, parseScriptInSvelte } from "./script"; import type * as SvAST from "./svelte-ast-types"; +import type * as Compiler from "svelte/compiler"; import { sortNodes } from "./sort"; import { parseTemplate } from "./template"; import { @@ -37,6 +38,7 @@ import { globals, globalsForSvelteScript } from "./globals"; import { svelteVersion } from "./svelte-version"; import type { NormalizedParserOptions } from "./parser-options"; import { isTypeScript, normalizeParserOptions } from "./parser-options"; +import { getFragmentFromRoot } from "./compat"; export { StyleContext, @@ -70,7 +72,7 @@ type ParseResult = { | { isSvelte: true; isSvelteScript: false; - getSvelteHtmlAst: () => SvAST.Fragment; + getSvelteHtmlAst: () => SvAST.Fragment | Compiler.Fragment; getStyleContext: () => StyleContext; } | { isSvelte: false; isSvelteScript: true } @@ -196,7 +198,7 @@ function parseAsSvelte( isSvelte: true, isSvelteScript: false, getSvelteHtmlAst() { - return resultTemplate.svelteAst.html; + return getFragmentFromRoot(resultTemplate.svelteAst); }, getStyleContext() { if (styleContext === null) { diff --git a/src/parser/svelte-ast-types.ts b/src/parser/svelte-ast-types.ts index 35ac239c..31c8a3a8 100644 --- a/src/parser/svelte-ast-types.ts +++ b/src/parser/svelte-ast-types.ts @@ -3,11 +3,11 @@ interface BaseNode { start: number; end: number; } -export interface Ast { - html: Fragment; - css: Style; - instance: Script; - module: Script; +export interface AstLegacy { + html?: Fragment; + css?: Style; + instance?: Script; + module?: Script; } export declare type TemplateNode = | Text @@ -15,7 +15,6 @@ export declare type TemplateNode = | RawMustacheTag | DebugTag | ConstTag - | RenderTag | Directive | StyleDirective | Element @@ -32,8 +31,7 @@ export declare type TemplateNode = | IfBlock | EachBlock | AwaitBlock - | KeyBlock - | SnippetBlock; + | KeyBlock; export interface Fragment extends BaseNode { type: "Fragment"; children: TemplateNode[]; @@ -58,12 +56,6 @@ export interface ConstTag extends BaseNode { type: "ConstTag"; expression: ESTree.AssignmentExpression; } -export interface RenderTag extends BaseNode { - type: "RenderTag"; - expression: - | ESTree.SimpleCallExpression - | (ESTree.ChainExpression & { expression: ESTree.SimpleCallExpression }); -} export interface IfBlock extends BaseNode { type: "IfBlock"; expression: ESTree.Expression; @@ -114,13 +106,6 @@ export interface KeyBlock extends BaseNode { expression: ESTree.Expression; children: TemplateNode[]; } -export interface SnippetBlock extends BaseNode { - type: "SnippetBlock"; - expression: ESTree.Identifier; - parameters: ESTree.Pattern[]; - children: TemplateNode[]; -} - export interface BaseElement extends BaseNode { type: "Element"; name: string; diff --git a/src/parser/template.ts b/src/parser/template.ts index 3a211c60..af8f75cf 100644 --- a/src/parser/template.ts +++ b/src/parser/template.ts @@ -1,5 +1,6 @@ import type {} from "svelte"; // FIXME: Workaround to get type information for "svelte/compiler" import { parse } from "svelte/compiler"; +import type * as Compiler from "svelte/compiler"; import type * as SvAST from "./svelte-ast-types"; import type { Context } from "../context"; import { convertSvelteRoot } from "./converts/index"; @@ -7,6 +8,7 @@ import { sortNodes } from "./sort"; import type { SvelteProgram } from "../ast"; import { ParseError } from ".."; import type { NormalizedParserOptions } from "./parser-options"; +import { svelteVersion } from "./svelte-version"; /** * Parse for template @@ -17,12 +19,13 @@ export function parseTemplate( parserOptions: NormalizedParserOptions, ): { ast: SvelteProgram; - svelteAst: SvAST.Ast; + svelteAst: Compiler.Root | SvAST.AstLegacy; } { try { const svelteAst = parse(code, { filename: parserOptions.filePath, - }) as never as SvAST.Ast; + ...(svelteVersion.gte(5) ? { modern: true } : {}), + }) as never as Compiler.Root | SvAST.AstLegacy; const ast = convertSvelteRoot(svelteAst, ctx); sortNodes(ast.body); diff --git a/tests/fixtures/parser/ast/at-const02-input.svelte b/tests/fixtures/parser/ast/at-const02-input.svelte new file mode 100644 index 00000000..bceb2b26 --- /dev/null +++ b/tests/fixtures/parser/ast/at-const02-input.svelte @@ -0,0 +1,15 @@ +<script> +export let boxes; +</script> + +{#each boxes as box} +{ +@const +area += +box.width +* +box.height +} +{box.width} * {box.height} = {area} +{/each} diff --git a/tests/fixtures/parser/ast/at-const02-output.json b/tests/fixtures/parser/ast/at-const02-output.json new file mode 100644 index 00000000..3895e5e3 --- /dev/null +++ b/tests/fixtures/parser/ast/at-const02-output.json @@ -0,0 +1,1617 @@ +{ + "type": "Program", + "body": [ + { + "type": "SvelteScriptElement", + "name": { + "type": "SvelteName", + "name": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + "startTag": { + "type": "SvelteStartTag", + "attributes": [], + "selfClosing": false, + "range": [ + 0, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + "body": [ + { + "type": "ExportNamedDeclaration", + "declaration": { + "type": "VariableDeclaration", + "kind": "let", + "declarations": [ + { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + }, + "init": null, + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + ], + "range": [ + 16, + 26 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 17 + } + } + }, + "source": null, + "specifiers": [], + "range": [ + 9, + 26 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 17 + } + } + } + ], + "endTag": { + "type": "SvelteEndTag", + "range": [ + 27, + 36 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + "range": [ + 0, + 36 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "SvelteText", + "value": "\n\n", + "range": [ + 36, + 38 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "SvelteEachBlock", + "expression": { + "type": "Identifier", + "name": "boxes", + "range": [ + 45, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 12 + } + } + }, + "context": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + }, + "index": null, + "key": null, + "children": [ + { + "type": "SvelteConstTag", + "declaration": { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "init": { + "type": "BinaryExpression", + "left": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "width", + "range": [ + 79, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "range": [ + 75, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "operator": "*", + "right": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "height", + "range": [ + 91, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 87, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 75, + 97 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 68, + 97 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 59, + 99 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 13, + "column": 1 + } + } + }, + { + "type": "SvelteText", + "value": "\n", + "range": [ + 99, + 100 + ], + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 14, + "column": 0 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 101, + 104 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 4 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "width", + "range": [ + 105, + 110 + ], + "loc": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + "range": [ + 101, + 110 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + "range": [ + 100, + 111 + ], + "loc": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 14, + "column": 11 + } + } + }, + { + "type": "SvelteText", + "value": " * ", + "range": [ + 111, + 114 + ], + "loc": { + "start": { + "line": 14, + "column": 11 + }, + "end": { + "line": 14, + "column": 14 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 115, + 118 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 18 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "height", + "range": [ + 119, + 125 + ], + "loc": { + "start": { + "line": 14, + "column": 19 + }, + "end": { + "line": 14, + "column": 25 + } + } + }, + "range": [ + 115, + 125 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 25 + } + } + }, + "range": [ + 114, + 126 + ], + "loc": { + "start": { + "line": 14, + "column": 14 + }, + "end": { + "line": 14, + "column": 26 + } + } + }, + { + "type": "SvelteText", + "value": " = ", + "range": [ + 126, + 129 + ], + "loc": { + "start": { + "line": 14, + "column": 26 + }, + "end": { + "line": 14, + "column": 29 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "Identifier", + "name": "area", + "range": [ + 130, + 134 + ], + "loc": { + "start": { + "line": 14, + "column": 30 + }, + "end": { + "line": 14, + "column": 34 + } + } + }, + "range": [ + 129, + 135 + ], + "loc": { + "start": { + "line": 14, + "column": 29 + }, + "end": { + "line": 14, + "column": 35 + } + } + } + ], + "else": null, + "range": [ + 38, + 143 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 15, + "column": 7 + } + } + } + ], + "sourceType": "module", + "comments": [], + "tokens": [ + { + "type": "Punctuator", + "value": "<", + "range": [ + 0, + 1 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 1, + "column": 1 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 1, + 7 + ], + "loc": { + "start": { + "line": 1, + "column": 1 + }, + "end": { + "line": 1, + "column": 7 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 7, + 8 + ], + "loc": { + "start": { + "line": 1, + "column": 7 + }, + "end": { + "line": 1, + "column": 8 + } + } + }, + { + "type": "Keyword", + "value": "export", + "range": [ + 9, + 15 + ], + "loc": { + "start": { + "line": 2, + "column": 0 + }, + "end": { + "line": 2, + "column": 6 + } + } + }, + { + "type": "Keyword", + "value": "let", + "range": [ + 16, + 19 + ], + "loc": { + "start": { + "line": 2, + "column": 7 + }, + "end": { + "line": 2, + "column": 10 + } + } + }, + { + "type": "Identifier", + "value": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + }, + { + "type": "Punctuator", + "value": ";", + "range": [ + 25, + 26 + ], + "loc": { + "start": { + "line": 2, + "column": 16 + }, + "end": { + "line": 2, + "column": 17 + } + } + }, + { + "type": "Punctuator", + "value": "<", + "range": [ + 27, + 28 + ], + "loc": { + "start": { + "line": 3, + "column": 0 + }, + "end": { + "line": 3, + "column": 1 + } + } + }, + { + "type": "Punctuator", + "value": "/", + "range": [ + 28, + 29 + ], + "loc": { + "start": { + "line": 3, + "column": 1 + }, + "end": { + "line": 3, + "column": 2 + } + } + }, + { + "type": "HTMLIdentifier", + "value": "script", + "range": [ + 29, + 35 + ], + "loc": { + "start": { + "line": 3, + "column": 2 + }, + "end": { + "line": 3, + "column": 8 + } + } + }, + { + "type": "Punctuator", + "value": ">", + "range": [ + 35, + 36 + ], + "loc": { + "start": { + "line": 3, + "column": 8 + }, + "end": { + "line": 3, + "column": 9 + } + } + }, + { + "type": "HTMLText", + "value": "\n\n", + "range": [ + 36, + 38 + ], + "loc": { + "start": { + "line": 3, + "column": 9 + }, + "end": { + "line": 5, + "column": 0 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 38, + 39 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 5, + "column": 1 + } + } + }, + { + "type": "MustacheKeyword", + "value": "#each", + "range": [ + 39, + 44 + ], + "loc": { + "start": { + "line": 5, + "column": 1 + }, + "end": { + "line": 5, + "column": 6 + } + } + }, + { + "type": "Identifier", + "value": "boxes", + "range": [ + 45, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 12 + } + } + }, + { + "type": "Keyword", + "value": "as", + "range": [ + 51, + 53 + ], + "loc": { + "start": { + "line": 5, + "column": 13 + }, + "end": { + "line": 5, + "column": 15 + } + } + }, + { + "type": "Identifier", + "value": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 57, + 58 + ], + "loc": { + "start": { + "line": 5, + "column": 19 + }, + "end": { + "line": 5, + "column": 20 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 59, + 60 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 6, + "column": 1 + } + } + }, + { + "type": "MustacheKeyword", + "value": "@const", + "range": [ + 61, + 67 + ], + "loc": { + "start": { + "line": 7, + "column": 0 + }, + "end": { + "line": 7, + "column": 6 + } + } + }, + { + "type": "Identifier", + "value": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + { + "type": "Punctuator", + "value": "=", + "range": [ + 73, + 74 + ], + "loc": { + "start": { + "line": 9, + "column": 0 + }, + "end": { + "line": 9, + "column": 1 + } + } + }, + { + "type": "Identifier", + "value": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + { + "type": "Punctuator", + "value": ".", + "range": [ + 78, + 79 + ], + "loc": { + "start": { + "line": 10, + "column": 3 + }, + "end": { + "line": 10, + "column": 4 + } + } + }, + { + "type": "Identifier", + "value": "width", + "range": [ + 79, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + { + "type": "Punctuator", + "value": "*", + "range": [ + 85, + 86 + ], + "loc": { + "start": { + "line": 11, + "column": 0 + }, + "end": { + "line": 11, + "column": 1 + } + } + }, + { + "type": "Identifier", + "value": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + { + "type": "Punctuator", + "value": ".", + "range": [ + 90, + 91 + ], + "loc": { + "start": { + "line": 12, + "column": 3 + }, + "end": { + "line": 12, + "column": 4 + } + } + }, + { + "type": "Identifier", + "value": "height", + "range": [ + 91, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 98, + 99 + ], + "loc": { + "start": { + "line": 13, + "column": 0 + }, + "end": { + "line": 13, + "column": 1 + } + } + }, + { + "type": "HTMLText", + "value": "\n", + "range": [ + 99, + 100 + ], + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 14, + "column": 0 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 100, + 101 + ], + "loc": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 14, + "column": 1 + } + } + }, + { + "type": "Identifier", + "value": "box", + "range": [ + 101, + 104 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 4 + } + } + }, + { + "type": "Punctuator", + "value": ".", + "range": [ + 104, + 105 + ], + "loc": { + "start": { + "line": 14, + "column": 4 + }, + "end": { + "line": 14, + "column": 5 + } + } + }, + { + "type": "Identifier", + "value": "width", + "range": [ + 105, + 110 + ], + "loc": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 110, + 111 + ], + "loc": { + "start": { + "line": 14, + "column": 10 + }, + "end": { + "line": 14, + "column": 11 + } + } + }, + { + "type": "HTMLText", + "value": " ", + "range": [ + 111, + 112 + ], + "loc": { + "start": { + "line": 14, + "column": 11 + }, + "end": { + "line": 14, + "column": 12 + } + } + }, + { + "type": "HTMLText", + "value": "*", + "range": [ + 112, + 113 + ], + "loc": { + "start": { + "line": 14, + "column": 12 + }, + "end": { + "line": 14, + "column": 13 + } + } + }, + { + "type": "HTMLText", + "value": " ", + "range": [ + 113, + 114 + ], + "loc": { + "start": { + "line": 14, + "column": 13 + }, + "end": { + "line": 14, + "column": 14 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 114, + 115 + ], + "loc": { + "start": { + "line": 14, + "column": 14 + }, + "end": { + "line": 14, + "column": 15 + } + } + }, + { + "type": "Identifier", + "value": "box", + "range": [ + 115, + 118 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 18 + } + } + }, + { + "type": "Punctuator", + "value": ".", + "range": [ + 118, + 119 + ], + "loc": { + "start": { + "line": 14, + "column": 18 + }, + "end": { + "line": 14, + "column": 19 + } + } + }, + { + "type": "Identifier", + "value": "height", + "range": [ + 119, + 125 + ], + "loc": { + "start": { + "line": 14, + "column": 19 + }, + "end": { + "line": 14, + "column": 25 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 125, + 126 + ], + "loc": { + "start": { + "line": 14, + "column": 25 + }, + "end": { + "line": 14, + "column": 26 + } + } + }, + { + "type": "HTMLText", + "value": " ", + "range": [ + 126, + 127 + ], + "loc": { + "start": { + "line": 14, + "column": 26 + }, + "end": { + "line": 14, + "column": 27 + } + } + }, + { + "type": "HTMLText", + "value": "=", + "range": [ + 127, + 128 + ], + "loc": { + "start": { + "line": 14, + "column": 27 + }, + "end": { + "line": 14, + "column": 28 + } + } + }, + { + "type": "HTMLText", + "value": " ", + "range": [ + 128, + 129 + ], + "loc": { + "start": { + "line": 14, + "column": 28 + }, + "end": { + "line": 14, + "column": 29 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 129, + 130 + ], + "loc": { + "start": { + "line": 14, + "column": 29 + }, + "end": { + "line": 14, + "column": 30 + } + } + }, + { + "type": "Identifier", + "value": "area", + "range": [ + 130, + 134 + ], + "loc": { + "start": { + "line": 14, + "column": 30 + }, + "end": { + "line": 14, + "column": 34 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 134, + 135 + ], + "loc": { + "start": { + "line": 14, + "column": 34 + }, + "end": { + "line": 14, + "column": 35 + } + } + }, + { + "type": "Punctuator", + "value": "{", + "range": [ + 136, + 137 + ], + "loc": { + "start": { + "line": 15, + "column": 0 + }, + "end": { + "line": 15, + "column": 1 + } + } + }, + { + "type": "MustacheKeyword", + "value": "/each", + "range": [ + 137, + 142 + ], + "loc": { + "start": { + "line": 15, + "column": 1 + }, + "end": { + "line": 15, + "column": 6 + } + } + }, + { + "type": "Punctuator", + "value": "}", + "range": [ + 142, + 143 + ], + "loc": { + "start": { + "line": 15, + "column": 6 + }, + "end": { + "line": 15, + "column": 7 + } + } + } + ], + "range": [ + 0, + 144 + ], + "loc": { + "start": { + "line": 1, + "column": 0 + }, + "end": { + "line": 16, + "column": 0 + } + } +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/at-const02-scope-output.json b/tests/fixtures/parser/ast/at-const02-scope-output.json new file mode 100644 index 00000000..5f9e06f8 --- /dev/null +++ b/tests/fixtures/parser/ast/at-const02-scope-output.json @@ -0,0 +1,1463 @@ +{ + "type": "global", + "variables": [ + { + "name": "$$slots", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$props", + "identifiers": [], + "defs": [], + "references": [] + }, + { + "name": "$$restProps", + "identifiers": [], + "defs": [], + "references": [] + } + ], + "references": [], + "childScopes": [ + { + "type": "module", + "variables": [ + { + "name": "boxes", + "identifiers": [ + { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + ], + "defs": [ + { + "type": "Variable", + "name": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + }, + "node": { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + }, + "init": null, + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "boxes", + "range": [ + 45, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 12 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + } + ] + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "boxes", + "range": [ + 45, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 12 + } + } + }, + "from": "module", + "init": null, + "resolved": { + "type": "Identifier", + "name": "boxes", + "range": [ + 20, + 25 + ], + "loc": { + "start": { + "line": 2, + "column": 11 + }, + "end": { + "line": 2, + "column": 16 + } + } + } + } + ], + "childScopes": [ + { + "type": "function", + "variables": [ + { + "name": "box", + "identifiers": [ + { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + ], + "defs": [ + { + "type": "Parameter", + "name": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + }, + "node": { + "type": "SvelteEachBlock", + "expression": { + "type": "Identifier", + "name": "boxes", + "range": [ + 45, + 50 + ], + "loc": { + "start": { + "line": 5, + "column": 7 + }, + "end": { + "line": 5, + "column": 12 + } + } + }, + "context": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + }, + "index": null, + "key": null, + "children": [ + { + "type": "SvelteConstTag", + "declaration": { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "init": { + "type": "BinaryExpression", + "left": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "width", + "range": [ + 79, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "range": [ + 75, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "operator": "*", + "right": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "height", + "range": [ + 91, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 87, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 75, + 97 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 68, + 97 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 59, + 99 + ], + "loc": { + "start": { + "line": 6, + "column": 0 + }, + "end": { + "line": 13, + "column": 1 + } + } + }, + { + "type": "SvelteText", + "value": "\n", + "range": [ + 99, + 100 + ], + "loc": { + "start": { + "line": 13, + "column": 1 + }, + "end": { + "line": 14, + "column": 0 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 101, + 104 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 4 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "width", + "range": [ + 105, + 110 + ], + "loc": { + "start": { + "line": 14, + "column": 5 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + "range": [ + 101, + 110 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 10 + } + } + }, + "range": [ + 100, + 111 + ], + "loc": { + "start": { + "line": 14, + "column": 0 + }, + "end": { + "line": 14, + "column": 11 + } + } + }, + { + "type": "SvelteText", + "value": " * ", + "range": [ + 111, + 114 + ], + "loc": { + "start": { + "line": 14, + "column": 11 + }, + "end": { + "line": 14, + "column": 14 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 115, + 118 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 18 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "height", + "range": [ + 119, + 125 + ], + "loc": { + "start": { + "line": 14, + "column": 19 + }, + "end": { + "line": 14, + "column": 25 + } + } + }, + "range": [ + 115, + 125 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 25 + } + } + }, + "range": [ + 114, + 126 + ], + "loc": { + "start": { + "line": 14, + "column": 14 + }, + "end": { + "line": 14, + "column": 26 + } + } + }, + { + "type": "SvelteText", + "value": " = ", + "range": [ + 126, + 129 + ], + "loc": { + "start": { + "line": 14, + "column": 26 + }, + "end": { + "line": 14, + "column": 29 + } + } + }, + { + "type": "SvelteMustacheTag", + "kind": "text", + "expression": { + "type": "Identifier", + "name": "area", + "range": [ + 130, + 134 + ], + "loc": { + "start": { + "line": 14, + "column": 30 + }, + "end": { + "line": 14, + "column": 34 + } + } + }, + "range": [ + 129, + 135 + ], + "loc": { + "start": { + "line": 14, + "column": 29 + }, + "end": { + "line": 14, + "column": 35 + } + } + } + ], + "else": null, + "range": [ + 38, + 143 + ], + "loc": { + "start": { + "line": 5, + "column": 0 + }, + "end": { + "line": 15, + "column": 7 + } + } + } + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 101, + 104 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 4 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 115, + 118 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 18 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + } + ] + }, + { + "name": "area", + "identifiers": [ + { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + } + ], + "defs": [ + { + "type": "Variable", + "name": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "node": { + "type": "VariableDeclarator", + "id": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "init": { + "type": "BinaryExpression", + "left": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "width", + "range": [ + 79, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 4 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "range": [ + 75, + 84 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 9 + } + } + }, + "operator": "*", + "right": { + "type": "MemberExpression", + "computed": false, + "object": { + "type": "Identifier", + "name": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "optional": false, + "property": { + "type": "Identifier", + "name": "height", + "range": [ + 91, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 4 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 87, + 97 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 75, + 97 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + }, + "range": [ + 68, + 97 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 12, + "column": 10 + } + } + } + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "from": "function", + "init": true, + "resolved": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "area", + "range": [ + 130, + 134 + ], + "loc": { + "start": { + "line": 14, + "column": 30 + }, + "end": { + "line": 14, + "column": 34 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + } + } + ] + } + ], + "references": [ + { + "identifier": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + }, + "from": "function", + "init": true, + "resolved": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 75, + 78 + ], + "loc": { + "start": { + "line": 10, + "column": 0 + }, + "end": { + "line": 10, + "column": 3 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 87, + 90 + ], + "loc": { + "start": { + "line": 12, + "column": 0 + }, + "end": { + "line": 12, + "column": 3 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 101, + 104 + ], + "loc": { + "start": { + "line": 14, + "column": 1 + }, + "end": { + "line": 14, + "column": 4 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "box", + "range": [ + 115, + 118 + ], + "loc": { + "start": { + "line": 14, + "column": 15 + }, + "end": { + "line": 14, + "column": 18 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "box", + "range": [ + 54, + 57 + ], + "loc": { + "start": { + "line": 5, + "column": 16 + }, + "end": { + "line": 5, + "column": 19 + } + } + } + }, + { + "identifier": { + "type": "Identifier", + "name": "area", + "range": [ + 130, + 134 + ], + "loc": { + "start": { + "line": 14, + "column": 30 + }, + "end": { + "line": 14, + "column": 34 + } + } + }, + "from": "function", + "init": null, + "resolved": { + "type": "Identifier", + "name": "area", + "range": [ + 68, + 72 + ], + "loc": { + "start": { + "line": 8, + "column": 0 + }, + "end": { + "line": 8, + "column": 4 + } + } + } + } + ], + "childScopes": [], + "through": [] + } + ], + "through": [] + } + ], + "through": [] +} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/await03-requirements.json b/tests/fixtures/parser/ast/await03-requirements.json deleted file mode 100644 index acfee03d..00000000 --- a/tests/fixtures/parser/ast/await03-requirements.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "FIXME": "As of now, Svelte v5 gives an error with single-line await blocks.", - "parse": { - "svelte": "^4 || ^3" - } -} diff --git a/tests/src/parser/__snapshots__/html.ts.snap b/tests/src/parser/__snapshots__/html.ts.snap index dfb8ce79..56794774 100644 --- a/tests/src/parser/__snapshots__/html.ts.snap +++ b/tests/src/parser/__snapshots__/html.ts.snap @@ -11,17 +11,22 @@ exports[`parseAttributes <script lang="ts"> 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 12, - "name": "lang", - "start": 8, - }, - "value": Object { - "end": 17, - "quote": "\\"", - "start": 13, - "value": "ts", - }, + "end": 17, + "metadata": null, + "name": "lang", + "parent": null, + "start": 8, + "type": "Attribute", + "value": Array [ + Object { + "data": "ts", + "end": 16, + "parent": null, + "raw": "ts", + "start": 14, + "type": "Text", + }, + ], }, ], "index": 17, @@ -32,17 +37,22 @@ exports[`parseAttributes <script lang='ts'> 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 12, - "name": "lang", - "start": 8, - }, - "value": Object { - "end": 17, - "quote": "'", - "start": 13, - "value": "ts", - }, + "end": 17, + "metadata": null, + "name": "lang", + "parent": null, + "start": 8, + "type": "Attribute", + "value": Array [ + Object { + "data": "ts", + "end": 16, + "parent": null, + "raw": "ts", + "start": 14, + "type": "Text", + }, + ], }, ], "index": 17, @@ -53,17 +63,22 @@ exports[`parseAttributes <script lang=ts> 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 12, - "name": "lang", - "start": 8, - }, - "value": Object { - "end": 15, - "quote": null, - "start": 13, - "value": "ts", - }, + "end": 15, + "metadata": null, + "name": "lang", + "parent": null, + "start": 8, + "type": "Attribute", + "value": Array [ + Object { + "data": "ts", + "end": 15, + "parent": null, + "raw": "ts", + "start": 13, + "type": "Text", + }, + ], }, ], "index": 15, @@ -74,12 +89,13 @@ exports[`parseAttributes <style global/> 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 13, - "name": "global", - "start": 7, - }, - "value": null, + "end": 13, + "metadata": null, + "name": "global", + "parent": null, + "start": 7, + "type": "Attribute", + "value": true, }, ], "index": 13, @@ -90,12 +106,13 @@ exports[`parseAttributes <style global> 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 13, - "name": "global", - "start": 7, - }, - "value": null, + "end": 13, + "metadata": null, + "name": "global", + "parent": null, + "start": 7, + "type": "Attribute", + "value": true, }, ], "index": 13, @@ -106,12 +123,13 @@ exports[`parseAttributes attr 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 4, - "name": "attr", - "start": 0, - }, - "value": null, + "end": 4, + "metadata": null, + "name": "attr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": true, }, ], "index": 6, @@ -122,17 +140,22 @@ exports[`parseAttributes attr = "value" 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 4, - "name": "attr", - "start": 0, - }, - "value": Object { - "end": 16, - "quote": "\\"", - "start": 9, - "value": "value", - }, + "end": 16, + "metadata": null, + "name": "attr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "value", + "end": 15, + "parent": null, + "raw": "value", + "start": 10, + "type": "Text", + }, + ], }, ], "index": 16, @@ -143,12 +166,13 @@ exports[`parseAttributes attr 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 4, - "name": "attr", - "start": 0, - }, - "value": null, + "end": 4, + "metadata": null, + "name": "attr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": true, }, ], "index": 4, @@ -159,17 +183,22 @@ exports[`parseAttributes attr="value" 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 4, - "name": "attr", - "start": 0, - }, - "value": Object { - "end": 12, - "quote": "\\"", - "start": 5, - "value": "value", - }, + "end": 12, + "metadata": null, + "name": "attr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "value", + "end": 11, + "parent": null, + "raw": "value", + "start": 6, + "type": "Text", + }, + ], }, ], "index": 12, @@ -180,17 +209,22 @@ exports[`parseAttributes empty="" 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 5, - "name": "empty", - "start": 0, - }, - "value": Object { - "end": 8, - "quote": "\\"", - "start": 6, - "value": "", - }, + "end": 8, + "metadata": null, + "name": "empty", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "", + "end": 7, + "parent": null, + "raw": "", + "start": 7, + "type": "Text", + }, + ], }, ], "index": 10, @@ -201,38 +235,300 @@ exports[`parseAttributes empty='' 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 5, - "name": "empty", - "start": 0, - }, - "value": Object { - "end": 8, - "quote": "'", - "start": 6, - "value": "", - }, + "end": 8, + "metadata": null, + "name": "empty", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "", + "end": 7, + "parent": null, + "raw": "", + "start": 7, + "type": "Text", + }, + ], }, ], "index": 10, } `; +exports[`parseAttributes expr="{true}" 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 13, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 13, + "expression": Object { + "end": 11, + "leadingComments": Array [], + "raw": "true", + "start": 7, + "type": "Literal", + "value": true, + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 15, +} +`; + +exports[`parseAttributes expr='{true}' 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 13, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 13, + "expression": Object { + "end": 11, + "leadingComments": Array [], + "raw": "true", + "start": 7, + "type": "Literal", + "value": true, + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 15, +} +`; + +exports[`parseAttributes expr={"}"} 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 10, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 10, + "expression": Object { + "end": 9, + "leadingComments": Array [], + "raw": "\\"}\\"", + "start": 6, + "type": "Literal", + "value": "}", + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 12, +} +`; + +exports[`parseAttributes expr={"s"} 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 10, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 10, + "expression": Object { + "end": 9, + "leadingComments": Array [], + "raw": "\\"s\\"", + "start": 6, + "type": "Literal", + "value": "s", + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 12, +} +`; + +exports[`parseAttributes expr={/*}*/"}"} 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 15, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 15, + "expression": Object { + "end": 14, + "leadingComments": Array [ + Object { + "end": 11, + "start": 6, + "type": "Block", + "value": "}", + }, + ], + "raw": "\\"}\\"", + "start": 11, + "type": "Literal", + "value": "}", + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 17, +} +`; + +exports[`parseAttributes expr={/*}*///} +"}"} 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 19, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 19, + "expression": Object { + "end": 18, + "leadingComments": Array [ + Object { + "end": 11, + "start": 6, + "type": "Block", + "value": "}", + }, + Object { + "end": 15, + "start": 11, + "type": "Line", + "value": "}", + }, + ], + "raw": "\\"}\\"", + "start": 15, + "type": "Literal", + "value": "}", + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 21, +} +`; + +exports[`parseAttributes expr={true} 1`] = ` +Object { + "attributes": Array [ + Object { + "end": 11, + "metadata": null, + "name": "expr", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "end": 11, + "expression": Object { + "end": 10, + "leadingComments": Array [], + "raw": "true", + "start": 6, + "type": "Literal", + "value": true, + }, + "metadata": null, + "parent": null, + "start": 5, + "type": "ExpressionTag", + }, + ], + }, + ], + "index": 13, +} +`; + exports[`parseAttributes quote="'" 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 5, - "name": "quote", - "start": 0, - }, - "value": Object { - "end": 9, - "quote": "\\"", - "start": 6, - "value": "'", - }, + "end": 9, + "metadata": null, + "name": "quote", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "'", + "end": 8, + "parent": null, + "raw": "'", + "start": 7, + "type": "Text", + }, + ], }, ], "index": 11, @@ -243,17 +539,22 @@ exports[`parseAttributes quote='"' 1`] = ` Object { "attributes": Array [ Object { - "key": Object { - "end": 5, - "name": "quote", - "start": 0, - }, - "value": Object { - "end": 9, - "quote": "'", - "start": 6, - "value": "\\"", - }, + "end": 9, + "metadata": null, + "name": "quote", + "parent": null, + "start": 0, + "type": "Attribute", + "value": Array [ + Object { + "data": "\\"", + "end": 8, + "parent": null, + "raw": "\\"", + "start": 7, + "type": "Text", + }, + ], }, ], "index": 11, diff --git a/tests/src/parser/html.ts b/tests/src/parser/html.ts index 0107b105..5ec9c309 100644 --- a/tests/src/parser/html.ts +++ b/tests/src/parser/html.ts @@ -53,6 +53,27 @@ describe("parseAttributes", () => { { input: `quote='"' `, }, + { + input: `expr={true} `, + }, + { + input: `expr="{true}" `, + }, + { + input: `expr='{true}' `, + }, + { + input: `expr={"s"} `, + }, + { + input: `expr={"}"} `, + }, + { + input: `expr={/*}*/"}"} `, + }, + { + input: `expr={/*}*///}\n"}"} `, + }, ]; for (const { input, index } of testCases) { it(input || "(empty)", () => { diff --git a/tests/src/parser/test-utils.ts b/tests/src/parser/test-utils.ts index 1b94d3a7..0aa969ad 100644 --- a/tests/src/parser/test-utils.ts +++ b/tests/src/parser/test-utils.ts @@ -157,6 +157,10 @@ function writeScopeFile( /input\.svelte(?:\.[jt]s)?$/u, "scope-output.json", ); + const scopeFileNameSvelte5 = inputFileName.replace( + /input\.svelte(?:\.[jt]s)?$/u, + "scope-output-svelte5.json", + ); if (!svelteVersion.gte(5)) { // v4 if (isSvelte5Only) return; @@ -167,12 +171,11 @@ function writeScopeFile( // v5 if (isSvelte5Only) { fs.writeFileSync(scopeFileName, json, "utf8"); + if (fs.existsSync(scopeFileNameSvelte5)) { + fs.unlinkSync(scopeFileNameSvelte5); + } return; } - const scopeFileNameSvelte5 = inputFileName.replace( - /input\.svelte(?:\.[jt]s)?$/u, - "scope-output-svelte5.json", - ); if (!fs.existsSync(scopeFileName)) { fs.writeFileSync(scopeFileNameSvelte5, json, "utf8"); return; @@ -185,6 +188,9 @@ function writeScopeFile( variables: SVELTE5_SCOPE_VARIABLES_BASE, }) === JSON.stringify(scope) ) { + if (fs.existsSync(scopeFileNameSvelte5)) { + fs.unlinkSync(scopeFileNameSvelte5); + } return; } diff --git a/tests/src/parser/typescript/assert-result.ts b/tests/src/parser/typescript/assert-result.ts index 30269bc5..ae743f57 100644 --- a/tests/src/parser/typescript/assert-result.ts +++ b/tests/src/parser/typescript/assert-result.ts @@ -1,4 +1,3 @@ -/* eslint complexity: 0 -- debug */ /** Assert equals */ export function assertResult( aRoot: unknown,