Skip to content

Commit

Permalink
feat: (experimental) partial support for Svelte v5 parser (#421)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Nov 14, 2023
1 parent d3047a4 commit 59fc0e9
Show file tree
Hide file tree
Showing 23 changed files with 1,803 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/friendly-hats-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte-eslint-parser": minor
---

feat: (experimental) partial support for Svelte v5 parser
15 changes: 15 additions & 0 deletions .github/workflows/NodeCI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ jobs:
run: pnpm install
- name: Test
run: pnpm run test
test-for-svelte-v4:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- name: Use Node.js
uses: actions/setup-node@v4
- name: Install Svelte v4
run: |+
pnpm install -D svelte@4
rm -rf node_modules
- name: Install Packages
run: pnpm install
- name: Test
run: pnpm run test
test-for-svelte-v3:
runs-on: ubuntu-latest
strategy:
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"version:ci": "env-cmd -e version-ci pnpm run build:meta && changeset version"
},
"peerDependencies": {
"svelte": "^3.37.0 || ^4.0.0"
"svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.2"
},
"peerDependenciesMeta": {
"svelte": {
Expand Down Expand Up @@ -103,8 +103,8 @@
"prettier-plugin-svelte": "^3.0.0",
"rimraf": "^5.0.1",
"semver": "^7.5.1",
"svelte": "^4.2.0",
"svelte2tsx": "^0.6.20",
"svelte": "^5.0.0-next.2",
"svelte2tsx": "^0.6.25",
"typescript": "~5.1.3",
"typescript-eslint-parser-for-extra-files": "^0.5.0"
},
Expand Down
180 changes: 112 additions & 68 deletions src/parser/converts/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { ParseError } from "../..";
/** Convert for Fragment or Element or ... */
export function* convertChildren(
/* eslint-enable complexity -- X */
fragment: { children: SvAST.TemplateNode[] },
fragment: { children?: SvAST.TemplateNode[] },
parent:
| SvelteProgram
| SvelteElement
Expand All @@ -76,6 +76,7 @@ export function* convertChildren(
| SvelteKeyBlock
| SvelteHTMLComment
> {
if (!fragment.children) return;
for (const child of fragment.children) {
if (child.type === "Comment") {
yield convertComment(child, parent, ctx);
Expand Down Expand Up @@ -199,8 +200,9 @@ function extractLetDirectives(fragment: {

/** Check if children needs a scope. */
function needScopeByChildren(fragment: {
children: SvAST.TemplateNode[];
children?: SvAST.TemplateNode[];
}): boolean {
if (!fragment.children) return false;
for (const child of fragment.children) {
if (child.type === "ConstTag") {
return true;
Expand Down Expand Up @@ -437,66 +439,92 @@ function processThisAttribute(
(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,
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],
});
}
ctx.addToken("HTMLText", {
start: literalStartIndex,
end: literalEndIndex,
});
if (quote) {
// "
} 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: literalEndIndex,
end: endIndex,
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 = thisAttr;
} else {
// this={...}
const eqIndex = ctx.code.lastIndexOf("=", getWithLoc(thisValue).start);
Expand All @@ -507,6 +535,30 @@ function processThisAttribute(
(c) => c === ">" || !c.trim(),
closeIndex,
);
thisNode = createSvelteSpecialDirective(
startIndex,
endIndex,
eqIndex,
thisValue,
);
}

const targetIndex = element.startTag.attributes.findIndex(
(attr) => thisNode.range[1] <= attr.range[0],
);
if (targetIndex === -1) {
element.startTag.attributes.push(thisNode);
} else {
element.startTag.attributes.splice(targetIndex, 0, thisNode);
}

/** Create SvelteSpecialDirective */
function createSvelteSpecialDirective(
startIndex: number,
endIndex: number,
eqIndex: number,
expression: ESTree.Expression,
): SvelteSpecialDirective {
const thisDir: SvelteSpecialDirective = {
type: "SvelteSpecialDirective",
kind: "this",
Expand All @@ -530,19 +582,11 @@ function processThisAttribute(
start: eqIndex,
end: eqIndex + 1,
});
ctx.scriptLet.addExpression(thisValue, thisDir, null, (es) => {
ctx.scriptLet.addExpression(expression, thisDir, null, (es) => {
thisDir.expression = es;
});
thisNode = thisDir;
}

const targetIndex = element.startTag.attributes.findIndex(
(attr) => thisNode.range[1] <= attr.range[0],
);
if (targetIndex === -1) {
element.startTag.attributes.push(thisNode);
} else {
element.startTag.attributes.splice(targetIndex, 0, thisNode);
return thisDir;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/parser/svelte-ast-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export interface Title extends BaseNode {
export interface Options extends BaseNode {
type: "Options";
name: "svelte:options";
children: TemplateNode[];
children?: TemplateNode[]; // This property does not exist in Svelte v5.
attributes: AttributeOrDirective[];
}
export interface SlotTemplate extends BaseNode {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function parseTemplate(
try {
const svelteAst = parse(code, {
filename: parserOptions.filePath,
}) as SvAST.Ast;
}) as never as SvAST.Ast;
const ast = convertSvelteRoot(svelteAst, ctx);
sortNodes(ast.body);

Expand Down
6 changes: 6 additions & 0 deletions tests/fixtures/parser/ast/await03-requirements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"FIXME": "As of now, Svelte v5 gives an error with single-line await blocks.",
"parse": {
"svelte": "^4 || ^3"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"parse": {
"svelte": "^4 || ^3"
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<svelte:options tag="my-custom-element"/>
<svelte:options customElement="my-custom-element"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<svelte:options tag="my-custom-element"/>

0 comments on commit 59fc0e9

Please sign in to comment.