Skip to content

Commit

Permalink
feat: support type annotations in {@const ...} tag (#9609)
Browse files Browse the repository at this point in the history
* support type for const tag

* use expression directly

* lint

* format

* format

* revert

* legacy mode

* format

* revert and update .prettierignore
  • Loading branch information
baseballyama authored Nov 27, 2023
1 parent 075c268 commit da1aa7c
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-ravens-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': minor
---

feat: support type definition in {@const}
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ sites/svelte.dev/src/lib/generated
.changeset
pnpm-lock.yaml
pnpm-workspace.yaml

# Temporarily ignore this file to avoid merge conflicts.
# see: https://github.com/sveltejs/svelte/pull/9609
documentation/docs/05-misc/03-typescript.md
27 changes: 27 additions & 0 deletions packages/svelte/src/compiler/legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,33 @@ export function convert(source, ast) {
};
},
// @ts-ignore
ConstTag(node) {
if (
/** @type {import('./types/legacy-nodes.js').LegacyConstTag} */ (node).expression !==
undefined
) {
return node;
}

const modern_node = /** @type {import('#compiler').ConstTag} */ (node);
const { id: left } = { ...modern_node.declaration.declarations[0] };
// @ts-ignore
delete left.typeAnnotation;
return {
type: 'ConstTag',
start: modern_node.start,
end: node.end,
expression: {
type: 'AssignmentExpression',
start: (modern_node.declaration.start ?? 0) + 'const '.length,
end: modern_node.declaration.end ?? 0,
operator: '=',
left,
right: modern_node.declaration.declarations[0].init
}
};
},
// @ts-ignore
KeyBlock(node, { visit }) {
remove_surrounding_whitespace_nodes(node.fragment.nodes);
return {
Expand Down
43 changes: 38 additions & 5 deletions packages/svelte/src/compiler/phases/1-parse/state/tag.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import read_context from '../read/context.js';
import read_expression from '../read/expression.js';
import { error } from '../../../errors.js';
import { create_fragment } from '../utils/create.js';
import { parse_expression_at } from '../acorn.js';
import { walk } from 'zimmerframe';
import { parse } from '../acorn.js';

const regex_whitespace_with_closing_curly_brace = /^\s*}/;

Expand Down Expand Up @@ -532,21 +532,54 @@ function special(parser) {
// {@const a = b}
parser.require_whitespace();

const expression = read_expression(parser);
const CONST_LENGTH = 'const '.length;
parser.index = parser.index - CONST_LENGTH;

let end_index = parser.index;
/** @type {import('estree').VariableDeclaration | undefined} */
let declaration = undefined;

if (!(expression.type === 'AssignmentExpression' && expression.operator === '=')) {
const dummy_spaces = parser.template.substring(0, parser.index).replace(/[^\n]/g, ' ');
while (true) {
end_index = parser.template.indexOf('}', end_index + 1);
if (end_index === -1) break;
try {
const node = parse(
dummy_spaces + parser.template.substring(parser.index, end_index),
parser.ts
).body[0];
if (node?.type === 'VariableDeclaration') {
declaration = node;
break;
}
} catch (e) {
continue;
}
}

if (
declaration === undefined ||
declaration.declarations.length !== 1 ||
declaration.declarations[0].init === undefined
) {
error(start, 'invalid-const');
}

parser.allow_whitespace();
parser.index = end_index;
parser.eat('}', true);

const id = declaration.declarations[0].id;
if (id.type === 'Identifier') {
// Tidy up some stuff left behind by acorn-typescript
id.end = (id.start ?? 0) + id.name.length;
}

parser.append(
/** @type {import('#compiler').ConstTag} */ ({
type: 'ConstTag',
start,
end: parser.index,
expression
declaration
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1653,19 +1653,20 @@ export const template_visitors = {
);
},
ConstTag(node, { state, visit }) {
const declaration = node.declaration.declarations[0];
// TODO we can almost certainly share some code with $derived(...)
if (node.expression.left.type === 'Identifier') {
if (declaration.id.type === 'Identifier') {
state.init.push(
b.const(
node.expression.left,
declaration.id,
b.call(
'$.derived',
b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression.right)))
b.thunk(/** @type {import('estree').Expression} */ (visit(declaration.init)))
)
)
);
} else {
const identifiers = extract_identifiers(node.expression.left);
const identifiers = extract_identifiers(declaration.id);
const tmp = b.id(state.scope.generate('computed_const'));

// Make all identifiers that are declared within the following computed regular
Expand All @@ -1681,8 +1682,8 @@ export const template_visitors = {
[],
b.block([
b.const(
/** @type {import('estree').Pattern} */ (visit(node.expression.left)),
/** @type {import('estree').Expression} */ (visit(node.expression.right))
/** @type {import('estree').Pattern} */ (visit(declaration.id)),
/** @type {import('estree').Expression} */ (visit(declaration.init))
),
b.return(b.object(identifiers.map((node) => b.prop('init', node, node))))
])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1080,8 +1080,9 @@ const template_visitors = {
state.template.push(t_expression(id));
},
ConstTag(node, { state, visit }) {
const pattern = /** @type {import('estree').Pattern} */ (visit(node.expression.left));
const init = /** @type {import('estree').Expression} */ (visit(node.expression.right));
const declaration = node.declaration.declarations[0];
const pattern = /** @type {import('estree').Pattern} */ (visit(declaration.id));
const init = /** @type {import('estree').Expression} */ (visit(declaration.init));
state.init.push(b.declaration('const', pattern, init));
},
DebugTag(node, { state, visit }) {
Expand Down
13 changes: 10 additions & 3 deletions packages/svelte/src/compiler/phases/scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,15 +437,21 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
next();
},

VariableDeclaration(node, { state, next }) {
VariableDeclaration(node, { state, path, next }) {
const is_parent_const_tag = path.at(-1)?.type === 'ConstTag';
for (const declarator of node.declarations) {
/** @type {import('#compiler').Binding[]} */
const bindings = [];

state.scope.declarators.set(declarator, bindings);

for (const id of extract_identifiers(declarator.id)) {
const binding = state.scope.declare(id, 'normal', node.kind, declarator.init);
const binding = state.scope.declare(
id,
is_parent_const_tag ? 'derived' : 'normal',
node.kind,
declarator.init
);
bindings.push(binding);
}
}
Expand Down Expand Up @@ -593,7 +599,8 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
},

ConstTag(node, { state, next }) {
for (const identifier of extract_identifiers(node.expression.left)) {
const declaration = node.declaration.declarations[0];
for (const identifier of extract_identifiers(declaration.id)) {
state.scope.declare(
/** @type {import('estree').Identifier} */ (identifier),
'derived',
Expand Down
7 changes: 7 additions & 0 deletions packages/svelte/src/compiler/types/legacy-nodes.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { StyleDirective as LegacyStyleDirective, Text } from '#compiler';
import type {
ArrayExpression,
AssignmentExpression,
Expression,
Identifier,
MemberExpression,
Expand Down Expand Up @@ -168,6 +169,11 @@ export interface LegacyTitle extends BaseElement {
name: 'title';
}

export interface LegacyConstTag extends BaseNode {
type: 'ConstTag';
expression: AssignmentExpression;
}

export interface LegacyTransition extends BaseNode {
type: 'Transition';
/** The 'x' in `transition:x` */
Expand Down Expand Up @@ -215,6 +221,7 @@ export type LegacyElementLike =
| LegacyWindow;

export type LegacySvelteNode =
| LegacyConstTag
| LegacyElementLike
| LegacyAttributeLike
| LegacyAttributeShorthand
Expand Down
7 changes: 5 additions & 2 deletions packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { Binding } from '#compiler';
import type {
ArrayExpression,
ArrowFunctionExpression,
AssignmentExpression,
VariableDeclaration,
VariableDeclarator,
Expression,
FunctionDeclaration,
FunctionExpression,
Expand Down Expand Up @@ -130,7 +131,9 @@ export interface Comment extends BaseNode {
/** A `{@const ...}` tag */
export interface ConstTag extends BaseNode {
type: 'ConstTag';
expression: AssignmentExpression;
declaration: VariableDeclaration & {
declarations: [VariableDeclarator & { id: Identifier; init: Expression }];
};
}

/** A `{@debug ...}` tag */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { test } from '../../test';

export default test({
html: '<p>10 * 10 = 100</p><p>20 * 20 = 400</p>'
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script lang="ts">
const boxes = [ { width: 10, height: 10 }, { width: 20, height: 20 } ];
</script>

{#each boxes as box}
{@const area: number = box.width * box.height}
<p>{box.width} * {box.height} = {area}</p>
{/each}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { test } from '../../test';

export default test({
html: '<p>{}</p>'
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script lang="ts">
</script>

{@const name: string = "{}"}
<p>{name}</p>

1 comment on commit da1aa7c

@vercel
Copy link

@vercel vercel bot commented on da1aa7c Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

svelte-5-preview – ./sites/svelte-5-preview

svelte-octane.vercel.app
svelte-5-preview-svelte.vercel.app
svelte-5-preview-git-main-svelte.vercel.app
svelte-5-preview.vercel.app

Please sign in to comment.