Skip to content

Commit a510468

Browse files
authored
Support for svelte v3.46 syntax (#128)
* Support for svelte 3.46 syntax * style dir * fix
1 parent fe6d555 commit a510468

31 files changed

+10712
-25
lines changed

src/ast.ts

+24-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ export type SvelteNode =
6060
| SvelteLiteral
6161
| SvelteMustacheTag
6262
| SvelteDebugTag
63+
| SvelteConstTag
6364
| SvelteIfBlock
6465
| SvelteElseBlock
6566
| SvelteEachBlock
@@ -215,6 +216,7 @@ type Child =
215216
| SvelteText
216217
| SvelteMustacheTag
217218
| SvelteDebugTag
219+
| SvelteConstTag
218220
| SvelteIfBlockAlone
219221
| SvelteEachBlock
220222
| SvelteAwaitBlock
@@ -241,7 +243,7 @@ export interface SvelteText extends BaseNode {
241243
export interface SvelteLiteral extends BaseNode {
242244
type: "SvelteLiteral"
243245
value: string
244-
parent: SvelteAttribute
246+
parent: SvelteAttribute | SvelteStyleDirective
245247
}
246248

247249
/** Node of mustache tag. e.g. `{...}`, `{@html ...}`. Like JSXExpressionContainer */
@@ -286,6 +288,22 @@ export interface SvelteDebugTag extends BaseNode {
286288
| SvelteKeyBlock
287289
| SvelteAttribute
288290
}
291+
/** Node of const tag. e.g. `{@const}` */
292+
export interface SvelteConstTag extends BaseNode {
293+
type: "SvelteConstTag"
294+
declaration: ESTree.VariableDeclarator
295+
parent:
296+
| SvelteProgram
297+
| SvelteElement
298+
| SvelteIfBlock
299+
| SvelteElseBlockAlone
300+
| SvelteEachBlock
301+
| SvelteAwaitPendingBlock
302+
| SvelteAwaitThenBlock
303+
| SvelteAwaitCatchBlock
304+
| SvelteKeyBlock
305+
| SvelteAttribute
306+
}
289307
/** Node of if block. e.g. `{#if}` */
290308
export type SvelteIfBlock = SvelteIfBlockAlone | SvelteIfBlockElseIf
291309
interface BaseSvelteIfBlock extends BaseNode {
@@ -511,6 +529,7 @@ export type SvelteDirective =
511529
| SvelteAnimationDirective
512530
| SvelteBindingDirective
513531
| SvelteClassDirective
532+
| SvelteStyleDirective
514533
| SvelteEventHandlerDirective
515534
| SvelteLetDirective
516535
| SvelteRefDirective
@@ -544,6 +563,10 @@ export interface SvelteClassDirective extends BaseSvelteDirective {
544563
kind: "Class"
545564
expression: null | ESTree.Expression
546565
}
566+
export interface SvelteStyleDirective extends BaseSvelteDirective {
567+
kind: "Style"
568+
expression: null | ESTree.Expression | SvelteLiteral
569+
}
547570
export interface SvelteEventHandlerDirective extends BaseSvelteDirective {
548571
kind: "EventHandler"
549572
expression: null | ESTree.Expression

src/context/script-let.ts

+38
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,44 @@ export class ScriptLetContext {
180180
return callbacks
181181
}
182182

183+
public addVariableDeclarator(
184+
expression: ESTree.AssignmentExpression,
185+
parent: SvelteNode,
186+
...callbacks: ScriptLetCallback<ESTree.VariableDeclarator>[]
187+
): ScriptLetCallback<ESTree.VariableDeclarator>[] {
188+
const range = getNodeRange(expression)
189+
const part = this.ctx.code.slice(...range)
190+
this.appendScript(
191+
`const ${part};`,
192+
range[0] - 6,
193+
(st, tokens, _comments, result) => {
194+
const decl = st as ESTree.VariableDeclaration
195+
const node = decl.declarations[0]
196+
// Process for nodes
197+
for (const callback of callbacks) {
198+
callback(node, result)
199+
}
200+
;(node as any).parent = parent
201+
202+
const scope = result.getScope(decl)
203+
for (const variable of scope.variables) {
204+
for (const def of variable.defs) {
205+
if (def.parent === decl) {
206+
;(def as any).parent = parent
207+
}
208+
}
209+
}
210+
211+
tokens.shift() // const
212+
tokens.pop() // ;
213+
214+
// Disconnect the tree structure.
215+
decl.declarations = []
216+
},
217+
)
218+
return callbacks
219+
}
220+
183221
public nestIfBlock(
184222
expression: ESTree.Expression,
185223
ifBlock: SvelteIfBlock,

src/parser/converts/attr.ts

+79-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import type {
1212
SvelteTransitionDirective,
1313
SvelteStartTag,
1414
SvelteName,
15+
SvelteStyleDirective,
1516
} from "../../ast"
1617
import type ESTree from "estree"
1718
import type { Context } from "../../context"
1819
import type * as SvAST from "../svelte-ast-types"
1920
import { getWithLoc, indexOf } from "./common"
2021
import { convertMustacheTag } from "./mustache"
21-
import { convertTextToLiteral } from "./text"
22+
import { convertTemplateLiteralToLiteral, convertTextToLiteral } from "./text"
2223
import { ParseError } from "../../errors"
2324
import type { ScriptLetCallback } from "../../context/script-let"
2425

@@ -54,6 +55,10 @@ export function* convertAttributes(
5455
yield convertClassDirective(attr, parent, ctx)
5556
continue
5657
}
58+
if (attr.type === "Style") {
59+
yield convertStyleDirective(attr, parent, ctx)
60+
continue
61+
}
5762
if (attr.type === "Transition") {
5863
yield convertTransitionDirective(attr, parent, ctx)
5964
continue
@@ -284,6 +289,79 @@ function convertClassDirective(
284289
return directive
285290
}
286291

292+
/** Convert for Style Directive */
293+
function convertStyleDirective(
294+
node: SvAST.DirectiveForExpression,
295+
parent: SvelteDirective["parent"],
296+
ctx: Context,
297+
): SvelteStyleDirective {
298+
const directive: SvelteStyleDirective = {
299+
type: "SvelteDirective",
300+
kind: "Style",
301+
key: null as any,
302+
expression: null,
303+
parent,
304+
...ctx.getConvertLocation(node),
305+
}
306+
if (processStyleDirectiveValue(node, ctx)) {
307+
processDirective(node, directive, ctx, (expression) => {
308+
directive.expression = convertTemplateLiteralToLiteral(
309+
expression,
310+
directive,
311+
ctx,
312+
)
313+
return []
314+
})
315+
} else {
316+
processDirective(node, directive, ctx, (expression) => {
317+
return ctx.scriptLet.addExpression(expression, directive)
318+
})
319+
}
320+
321+
return directive
322+
}
323+
324+
/** Process plain value */
325+
function processStyleDirectiveValue(
326+
node: SvAST.DirectiveForExpression,
327+
ctx: Context,
328+
): node is SvAST.DirectiveForExpression & {
329+
expression: ESTree.TemplateLiteral
330+
} {
331+
const { expression } = node
332+
if (
333+
!expression ||
334+
expression.type !== "TemplateLiteral" ||
335+
expression.expressions.length !== 0
336+
) {
337+
return false
338+
}
339+
const quasi = expression.quasis[0]
340+
if (quasi.value.cooked != null) {
341+
return false
342+
}
343+
const eqIndex = ctx.code.indexOf("=", node.start)
344+
if (eqIndex < 0 || eqIndex >= node.end) {
345+
return false
346+
}
347+
const valueIndex = ctx.code.indexOf(quasi.value.raw, eqIndex + 1)
348+
if (valueIndex < 0 || valueIndex >= node.end) {
349+
return false
350+
}
351+
const maybeEnd = valueIndex + quasi.value.raw.length
352+
const maybeOpenQuote = ctx.code.slice(eqIndex + 1, valueIndex).trimStart()
353+
if (maybeOpenQuote && maybeOpenQuote !== '"' && maybeOpenQuote !== "'") {
354+
return false
355+
}
356+
const maybeCloseQuote = ctx.code.slice(maybeEnd, node.end).trimEnd()
357+
if (maybeCloseQuote !== maybeOpenQuote) {
358+
return false
359+
}
360+
getWithLoc(expression).start = valueIndex
361+
getWithLoc(expression).end = maybeEnd
362+
return true
363+
}
364+
287365
/** Convert for Transition Directive */
288366
function convertTransitionDirective(
289367
node: SvAST.TransitionDirective,

src/parser/converts/const.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { SvelteConstTag } from "../../ast"
2+
import type { Context } from "../../context"
3+
import type * as SvAST from "../svelte-ast-types"
4+
5+
/** Convert for ConstTag */
6+
export function convertConstTag(
7+
node: SvAST.ConstTag,
8+
parent: SvelteConstTag["parent"],
9+
ctx: Context,
10+
): SvelteConstTag {
11+
const mustache: SvelteConstTag = {
12+
type: "SvelteConstTag",
13+
declaration: null as any,
14+
parent,
15+
...ctx.getConvertLocation(node),
16+
}
17+
ctx.scriptLet.addVariableDeclarator(
18+
node.expression,
19+
mustache,
20+
(declaration) => {
21+
mustache.declaration = declaration
22+
},
23+
)
24+
const atConstStart = ctx.code.indexOf("@const", mustache.range[0])
25+
ctx.addToken("MustacheKeyword", {
26+
start: atConstStart,
27+
end: atConstStart + 6,
28+
})
29+
return mustache
30+
}

src/parser/converts/element.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
SvelteAwaitPendingBlock,
55
SvelteAwaitThenBlock,
66
SvelteComponentElement,
7+
SvelteConstTag,
78
SvelteDebugTag,
89
SvelteEachBlock,
910
SvelteElement,
@@ -41,6 +42,7 @@ import {
4142
} from "./mustache"
4243
import { convertText } from "./text"
4344
import { convertAttributes } from "./attr"
45+
import { convertConstTag } from "./const"
4446

4547
/* eslint-disable complexity -- X */
4648
/** Convert for Fragment or Element or ... */
@@ -63,6 +65,7 @@ export function* convertChildren(
6365
| SvelteElement
6466
| SvelteMustacheTag
6567
| SvelteDebugTag
68+
| SvelteConstTag
6669
| SvelteIfBlockAlone
6770
| SvelteEachBlock
6871
| SvelteAwaitBlock
@@ -150,11 +153,27 @@ export function* convertChildren(
150153
yield convertDebugTag(child, parent, ctx)
151154
continue
152155
}
156+
if (child.type === "ConstTag") {
157+
yield convertConstTag(child, parent, ctx)
158+
continue
159+
}
153160

154161
throw new Error(`Unknown type:${(child as any).type}`)
155162
}
156163
}
157164

165+
/** Check if children needs a scope. */
166+
function needScopeByChildren(fragment: {
167+
children: SvAST.TemplateNode[]
168+
}): boolean {
169+
for (const child of fragment.children) {
170+
if (child.type === "ConstTag") {
171+
return true
172+
}
173+
}
174+
return false
175+
}
176+
158177
/** Convert for HTML Comment */
159178
function convertComment(
160179
node: SvAST.Comment,
@@ -210,7 +229,7 @@ function convertHTMLElement(
210229
...convertAttributes(node.attributes, element.startTag, ctx),
211230
)
212231
const lets = ctx.letDirCollections.extract()
213-
if (lets.isEmpty()) {
232+
if (lets.isEmpty() && !needScopeByChildren(node)) {
214233
element.children.push(...convertChildren(node, element, ctx))
215234
} else {
216235
ctx.scriptLet.nestBlock(
@@ -297,7 +316,7 @@ function convertSpecialElement(
297316
...convertAttributes(node.attributes, element.startTag, ctx),
298317
)
299318
const lets = ctx.letDirCollections.extract()
300-
if (lets.isEmpty()) {
319+
if (lets.isEmpty() && !needScopeByChildren(node)) {
301320
element.children.push(...convertChildren(node, element, ctx))
302321
} else {
303322
ctx.scriptLet.nestBlock(
@@ -406,7 +425,7 @@ function convertComponentElement(
406425
...convertAttributes(node.attributes, element.startTag, ctx),
407426
)
408427
const lets = ctx.letDirCollections.extract()
409-
if (lets.isEmpty()) {
428+
if (lets.isEmpty() && !needScopeByChildren(node)) {
410429
element.children.push(...convertChildren(node, element, ctx))
411430
} else {
412431
ctx.scriptLet.nestBlock(

0 commit comments

Comments
 (0)