Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (experimental) partial support for Svelte v5 parser #421

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
@@ -1,4 +1,4 @@
import type {} from "svelte"; // FIXME: Workaround to get type information for "svelte/compiler"

Check warning on line 1 in src/parser/template.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'fixme' comment: 'FIXME: Workaround to get type...'
import { parse } from "svelte/compiler";
import type * as SvAST from "./svelte-ast-types";
import type { Context } from "../context";
Expand All @@ -21,7 +21,7 @@
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"/>