Skip to content

Commit 501c1b4

Browse files
authored
fix: crash with plain this attribute. (#316)
1 parent 8d6eefe commit 501c1b4

File tree

7 files changed

+1397
-37
lines changed

7 files changed

+1397
-37
lines changed

.changeset/itchy-toes-tell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"svelte-eslint-parser": patch
3+
---
4+
5+
fix: crash with plain `this` attribute.

src/parser/converts/common.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import type ESTree from "estree";
22
/** indexOf */
33
export function indexOf(
44
str: string,
5-
search: (c: string) => boolean,
6-
start: number
5+
search: (c: string, index: number) => boolean,
6+
start: number,
7+
end?: number
78
): number {
8-
for (let index = start; index < str.length; index++) {
9+
const endIndex = end ?? str.length;
10+
for (let index = start; index < endIndex; index++) {
911
const c = str[index];
10-
if (search(c)) {
12+
if (search(c, index)) {
1113
return index;
1214
}
1315
}

src/parser/converts/element.ts

+154-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
SvelteAttribute,
23
SvelteAwaitBlock,
34
SvelteAwaitCatchBlock,
45
SvelteAwaitPendingBlock,
@@ -45,6 +46,7 @@ import { convertAttributes } from "./attr";
4546
import { convertConstTag } from "./const";
4647
import { sortNodes } from "../sort";
4748
import type { ScriptLetBlockParam } from "../../context/script-let";
49+
import { ParseError } from "../..";
4850

4951
/* eslint-disable complexity -- X */
5052
/** Convert for Fragment or Element or ... */
@@ -397,59 +399,181 @@ function convertSpecialElement(
397399
node.expression) ||
398400
(node.type === "Element" && elementName === "svelte:element" && node.tag);
399401
if (thisExpression) {
400-
const eqIndex = ctx.code.lastIndexOf("=", getWithLoc(thisExpression).start);
402+
processThisAttribute(node, thisExpression, element, ctx);
403+
}
404+
405+
extractElementTags(element, ctx, {
406+
buildNameNode: (openTokenRange) => {
407+
ctx.addToken("HTMLIdentifier", openTokenRange);
408+
const name: SvelteName = {
409+
type: "SvelteName",
410+
name: elementName,
411+
parent: element,
412+
...ctx.getConvertLocation(openTokenRange),
413+
};
414+
return name;
415+
},
416+
});
417+
418+
return element;
419+
}
420+
421+
/** process `this=` */
422+
function processThisAttribute(
423+
node: SvAST.SvelteElement | SvAST.InlineSvelteComponent,
424+
thisValue: string | ESTree.Expression,
425+
element: SvelteSpecialElement,
426+
ctx: Context
427+
) {
428+
let thisNode: SvelteSpecialDirective | SvelteAttribute;
429+
if (typeof thisValue === "string") {
430+
// this="..."
431+
const startIndex = findStartIndexOfThis(node, ctx);
432+
const eqIndex = ctx.code.indexOf("=", startIndex + 4 /* t,h,i,s */);
433+
const valueStartIndex = indexOf(
434+
ctx.code,
435+
(c) => Boolean(c.trim()),
436+
eqIndex + 1
437+
);
438+
const quote = ctx.code.startsWith(thisValue, valueStartIndex)
439+
? null
440+
: ctx.code[valueStartIndex];
441+
const literalStartIndex = quote
442+
? valueStartIndex + quote.length
443+
: valueStartIndex;
444+
const literalEndIndex = literalStartIndex + thisValue.length;
445+
const endIndex = quote ? literalEndIndex + quote.length : literalEndIndex;
446+
const thisAttr: SvelteAttribute = {
447+
type: "SvelteAttribute",
448+
key: null as any,
449+
boolean: false,
450+
value: [],
451+
parent: element.startTag,
452+
...ctx.getConvertLocation({ start: startIndex, end: endIndex }),
453+
};
454+
thisAttr.key = {
455+
type: "SvelteName",
456+
name: "this",
457+
parent: thisAttr,
458+
...ctx.getConvertLocation({ start: startIndex, end: eqIndex }),
459+
};
460+
thisAttr.value.push({
461+
type: "SvelteLiteral",
462+
value: thisValue,
463+
parent: thisAttr,
464+
...ctx.getConvertLocation({
465+
start: literalStartIndex,
466+
end: literalEndIndex,
467+
}),
468+
});
469+
// this
470+
ctx.addToken("HTMLIdentifier", {
471+
start: startIndex,
472+
end: startIndex + 4,
473+
});
474+
// =
475+
ctx.addToken("Punctuator", {
476+
start: eqIndex,
477+
end: eqIndex + 1,
478+
});
479+
if (quote) {
480+
// "
481+
ctx.addToken("Punctuator", {
482+
start: valueStartIndex,
483+
end: literalStartIndex,
484+
});
485+
}
486+
ctx.addToken("HTMLText", {
487+
start: literalStartIndex,
488+
end: literalEndIndex,
489+
});
490+
if (quote) {
491+
// "
492+
ctx.addToken("Punctuator", {
493+
start: literalEndIndex,
494+
end: endIndex,
495+
});
496+
}
497+
thisNode = thisAttr;
498+
} else {
499+
// this={...}
500+
const eqIndex = ctx.code.lastIndexOf("=", getWithLoc(thisValue).start);
401501
const startIndex = ctx.code.lastIndexOf("this", eqIndex);
402-
const closeIndex = ctx.code.indexOf("}", getWithLoc(thisExpression).end);
502+
const closeIndex = ctx.code.indexOf("}", getWithLoc(thisValue).end);
403503
const endIndex = indexOf(
404504
ctx.code,
405505
(c) => c === ">" || !c.trim(),
406506
closeIndex
407507
);
408-
const thisAttr: SvelteSpecialDirective = {
508+
const thisDir: SvelteSpecialDirective = {
409509
type: "SvelteSpecialDirective",
410510
kind: "this",
411511
key: null as any,
412512
expression: null as any,
413513
parent: element.startTag,
414514
...ctx.getConvertLocation({ start: startIndex, end: endIndex }),
415515
};
416-
thisAttr.key = {
516+
thisDir.key = {
417517
type: "SvelteSpecialDirectiveKey",
418-
parent: thisAttr,
518+
parent: thisDir,
419519
...ctx.getConvertLocation({ start: startIndex, end: eqIndex }),
420520
};
521+
// this
421522
ctx.addToken("HTMLIdentifier", {
422523
start: startIndex,
423-
end: eqIndex,
524+
end: startIndex + 4,
424525
});
425-
ctx.scriptLet.addExpression(thisExpression, thisAttr, null, (es) => {
426-
thisAttr.expression = es;
526+
// =
527+
ctx.addToken("Punctuator", {
528+
start: eqIndex,
529+
end: eqIndex + 1,
427530
});
428-
429-
const targetIndex = element.startTag.attributes.findIndex(
430-
(attr) => thisAttr.range[1] <= attr.range[0]
431-
);
432-
if (targetIndex === -1) {
433-
element.startTag.attributes.push(thisAttr);
434-
} else {
435-
element.startTag.attributes.splice(targetIndex, 0, thisAttr);
436-
}
531+
ctx.scriptLet.addExpression(thisValue, thisDir, null, (es) => {
532+
thisDir.expression = es;
533+
});
534+
thisNode = thisDir;
437535
}
438536

439-
extractElementTags(element, ctx, {
440-
buildNameNode: (openTokenRange) => {
441-
ctx.addToken("HTMLIdentifier", openTokenRange);
442-
const name: SvelteName = {
443-
type: "SvelteName",
444-
name: elementName,
445-
parent: element,
446-
...ctx.getConvertLocation(openTokenRange),
447-
};
448-
return name;
449-
},
450-
});
537+
const targetIndex = element.startTag.attributes.findIndex(
538+
(attr) => thisNode.range[1] <= attr.range[0]
539+
);
540+
if (targetIndex === -1) {
541+
element.startTag.attributes.push(thisNode);
542+
} else {
543+
element.startTag.attributes.splice(targetIndex, 0, thisNode);
544+
}
545+
}
451546

452-
return element;
547+
/** Find the start index of `this` */
548+
function findStartIndexOfThis(
549+
node: SvAST.SvelteElement | SvAST.InlineSvelteComponent,
550+
ctx: Context
551+
) {
552+
// Get the end index of `svelte:element`
553+
const startIndex = ctx.code.indexOf(node.name, node.start) + node.name.length;
554+
const sortedAttrs = [...node.attributes].sort((a, b) => a.start - b.start);
555+
// Find the start index of `this` from the end index of `svelte:element`.
556+
// However, it only seeks to the start index of the first attribute (or the end index of element node).
557+
let thisIndex = indexOf(
558+
ctx.code,
559+
(_c, index) => ctx.code.startsWith("this", index),
560+
startIndex,
561+
sortedAttrs[0]?.start ?? node.end
562+
);
563+
while (thisIndex < 0) {
564+
if (sortedAttrs.length === 0)
565+
throw new ParseError("Cannot resolved `this` attribute.", thisIndex, ctx);
566+
// Step3: Find the start index of `this` from the end index of attribute.
567+
// However, it only seeks to the start index of the first attribute (or the end index of element node).
568+
const nextStartIndex = sortedAttrs.shift()!.end;
569+
thisIndex = indexOf(
570+
ctx.code,
571+
(_c, index) => ctx.code.startsWith("this", index),
572+
nextStartIndex,
573+
sortedAttrs[0]?.start ?? node.end
574+
);
575+
}
576+
return thisIndex;
453577
}
454578

455579
/** Convert for ComponentElement */

src/parser/svelte-ast-types.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,11 @@ export interface BaseElement extends BaseNode {
117117
export interface BasicElement extends BaseElement {
118118
tag?: undefined;
119119
}
120-
export interface SvelteComponent extends BaseElement {
120+
export interface SvelteElement extends BaseElement {
121121
name: "svelte:element";
122-
tag: ESTree.Expression;
122+
tag: ESTree.Expression | string;
123123
}
124-
export type Element = BasicElement | SvelteComponent;
124+
export type Element = BasicElement | SvelteElement;
125125

126126
export interface BaseInlineComponent extends BaseNode {
127127
type: "InlineComponent";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<svelte:element class="foo" this="input" type="number"/>
2+
<svelte:element class="foo" this={"input"} type="number"/>

0 commit comments

Comments
 (0)