mirror of
https://github.com/sveltejs/svelte.git
synced 2024-11-21 19:38:58 +01:00
feat: support type annotations in {@const ...}
tag (#9609)
* support type for const tag * use expression directly * lint * format * format * revert * legacy mode * format * revert and update .prettierignore
This commit is contained in:
parent
075c268f42
commit
da1aa7c4a8
5
.changeset/seven-ravens-check.md
Normal file
5
.changeset/seven-ravens-check.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': minor
|
||||
---
|
||||
|
||||
feat: support type definition in {@const}
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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*}/;
|
||||
|
||||
@ -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;
|
||||
|
||||
if (!(expression.type === 'AssignmentExpression' && expression.operator === '=')) {
|
||||
let end_index = parser.index;
|
||||
/** @type {import('estree').VariableDeclaration | undefined} */
|
||||
let declaration = undefined;
|
||||
|
||||
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
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
@ -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))))
|
||||
])
|
||||
|
@ -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 }) {
|
||||
|
@ -437,7 +437,8 @@ 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 = [];
|
||||
@ -445,7 +446,12 @@ export function create_scopes(ast, root, allow_reactive_declarations, parent) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { StyleDirective as LegacyStyleDirective, Text } from '#compiler';
|
||||
import type {
|
||||
ArrayExpression,
|
||||
AssignmentExpression,
|
||||
Expression,
|
||||
Identifier,
|
||||
MemberExpression,
|
||||
@ -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` */
|
||||
@ -215,6 +221,7 @@ export type LegacyElementLike =
|
||||
| LegacyWindow;
|
||||
|
||||
export type LegacySvelteNode =
|
||||
| LegacyConstTag
|
||||
| LegacyElementLike
|
||||
| LegacyAttributeLike
|
||||
| LegacyAttributeShorthand
|
||||
|
@ -2,7 +2,8 @@ import type { Binding } from '#compiler';
|
||||
import type {
|
||||
ArrayExpression,
|
||||
ArrowFunctionExpression,
|
||||
AssignmentExpression,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
Expression,
|
||||
FunctionDeclaration,
|
||||
FunctionExpression,
|
||||
@ -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 */
|
||||
|
@ -0,0 +1,5 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: '<p>10 * 10 = 100</p><p>20 * 20 = 400</p>'
|
||||
});
|
@ -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}
|
@ -0,0 +1,5 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: '<p>{}</p>'
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
{@const name: string = "{}"}
|
||||
<p>{name}</p>
|
Loading…
Reference in New Issue
Block a user