Merge remote-tracking branch 'origin/main' into svelte-custom-renderer

svelte-custom-renderer
paoloricciuti 1 week ago
commit 17e37a51bc

@ -0,0 +1,5 @@
---
'svelte': patch
---
perf: speed up parser interactions with Acorn or avoid them where possible

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: serialize input default values during server rendering

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: throw `set_context_after_init` when `setContext` is called after an `await` during SSR

@ -0,0 +1,5 @@
---
'svelte': patch
---
perf: avoid regex matching in parser where possible

@ -231,14 +231,6 @@ Rest element properties of `$props()` such as `%property%` are readonly
The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
``` ```
### set_context_after_init
```
`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
```
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
### snippet_renderer_mismatch ### snippet_renderer_mismatch
``` ```

@ -80,6 +80,14 @@ Context was not set in the current component or any of its ancestors
The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors.
### set_context_after_init
```
`setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
```
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
### snippet_without_render_tag ### snippet_without_render_tag
``` ```

@ -175,12 +175,6 @@ This can happen if you render a hydratable on the client that was not rendered o
> The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files > The `%rune%` rune is only available inside `.svelte` and `.svelte.js/ts` files
## set_context_after_init
> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
## snippet_renderer_mismatch ## snippet_renderer_mismatch
> A snippet created in a component with a custom renderer cannot be rendered by a different renderer > A snippet created in a component with a custom renderer cannot be rendered by a different renderer

@ -66,6 +66,12 @@ Certain lifecycle methods can only be used during component initialisation. To f
The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors. The [`createContext()`](svelte#createContext) utility returns a `[get, set, has]` triplet of functions. `get` will throw an error if `set` was not used to set the context in the current component or any of its ancestors.
## set_context_after_init
> `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6.
## snippet_without_render_tag ## snippet_without_render_tag
> Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. > Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`.

@ -178,17 +178,17 @@
"web-features": "^3.29.0" "web-features": "^3.29.0"
}, },
"dependencies": { "dependencies": {
"@jridgewell/remapping": "^2.3.4", "@jridgewell/remapping": "^2.3.5",
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.6.0",
"@sveltejs/acorn-typescript": "^1.0.10", "@sveltejs/acorn-typescript": "^1.0.13",
"@types/estree": "^1.0.5", "@types/estree": "^1.0.9",
"acorn": "^8.12.1", "acorn": "^8.18.0",
"aria-query": "5.3.1", "aria-query": "5.3.1",
"axobject-query": "^4.1.0", "axobject-query": "^4.1.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"devalue": "^5.8.1", "devalue": "^5.9.2",
"esm-env": "^1.2.1", "esm-env": "^1.2.1",
"esrap": "^2.2.12", "esrap": "^2.3.6",
"is-reference": "^3.0.3", "is-reference": "^3.0.3",
"locate-character": "^3.0.0", "locate-character": "^3.0.0",
"magic-string": "^0.30.11", "magic-string": "^0.30.11",

@ -5,6 +5,7 @@ import * as acorn from 'acorn';
import { walk } from 'zimmerframe'; import { walk } from 'zimmerframe';
import { tsPlugin } from '@sveltejs/acorn-typescript'; import { tsPlugin } from '@sveltejs/acorn-typescript';
import * as e from '../../errors.js'; import * as e from '../../errors.js';
import { locator } from '../../state.js';
const JSParser = acorn.Parser; const JSParser = acorn.Parser;
const TSParser = JSParser.extend(tsPlugin()); const TSParser = JSParser.extend(tsPlugin());
@ -87,7 +88,8 @@ export function parse_expression_at(parser, source, index) {
sourceType: 'module', sourceType: 'module',
ecmaVersion: 16, ecmaVersion: 16,
locations: true, locations: true,
preserveParens: true preserveParens: true,
startLocation: start_location(parser, index)
}); });
add_comments(ast); add_comments(ast);
@ -112,7 +114,13 @@ export function parse_statement_at(parser, source, index) {
try { try {
// This is like parseExpressionAt but for statements // This is like parseExpressionAt but for statements
const p = new acorn( const p = new acorn(
{ onComment, sourceType: 'module', ecmaVersion: 16, locations: true }, {
onComment,
sourceType: 'module',
ecmaVersion: 16,
locations: true,
startLocation: start_location(parser, index)
},
source, source,
index index
); );
@ -128,6 +136,32 @@ export function parse_statement_at(parser, source, index) {
} }
} }
const regex_non_lf_line_break = /\r(?!\n)|[\u2028\u2029]/;
let last_template = '';
let lf_only = true;
/**
* Without `startLocation`, acorn counts the lines before `index` on every call
* @param {Parser} parser
* @param {number} index
*/
function start_location(parser, index) {
return has_lf_line_breaks_only(parser) ? locator(index) : undefined;
}
/**
* acorn breaks lines on bare `\r`, `\u2028` and `\u2029`, which the locator doesn't
* @param {Parser} parser
*/
export function has_lf_line_breaks_only(parser) {
if (parser.template !== last_template) {
last_template = parser.template;
lf_only = !regex_non_lf_line_break.test(last_template);
}
return lf_only;
}
const regex_position_indicator = / \(\d+:\d+\)$/; const regex_position_indicator = / \(\d+:\d+\)$/;
/** /**

@ -10,25 +10,7 @@ import read_options from './read/options.js';
import { is_reserved } from '../../../utils.js'; import { is_reserved } from '../../../utils.js';
import { disallow_children } from '../2-analyze/visitors/shared/special-element.js'; import { disallow_children } from '../2-analyze/visitors/shared/special-element.js';
import * as state from '../../state.js'; import * as state from '../../state.js';
import { is_whitespace } from './utils/whitespace.js';
/** @param {number} cc */
function is_whitespace(cc) {
// fast path for common whitespace
if (cc === 32 || (cc <= 13 && cc >= 9)) return true;
// rare whitespace — \u00a0, \u1680, \u2000-\u200a, \u2028, \u2029, \u202f, \u205f, \u3000, \ufeff
if (cc < 160) return false;
return (
cc === 160 ||
cc === 5760 ||
(cc >= 8192 && cc <= 8202) ||
cc === 8232 ||
cc === 8233 ||
cc === 8239 ||
cc === 8287 ||
cc === 12288 ||
cc === 65279
);
}
const regex_lang_attribute = const regex_lang_attribute =
/<!--[^]*?-->|<script\s+(?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g; /<!--[^]*?-->|<script\s+(?:[^>]*|(?:[^=>'"/]+=(?:"[^"]*"|'[^']*'|[^>\s]+)\s+)*)lang=(["'])?([^"' >]+)\1[^>]*>/g;
@ -278,8 +260,27 @@ export class Parser {
}; };
} }
/** @param {string} delimiter */
read_until(delimiter) {
if (this.index >= this.template.length) {
if (this.loose) return '';
e.unexpected_eof(this.template.length);
}
const start = this.index;
const index = this.template.indexOf(delimiter, start);
if (index !== -1) {
this.index = index;
return this.template.slice(start, this.index);
}
this.index = this.template.length;
return this.template.slice(start);
}
/** @param {RegExp} pattern */ /** @param {RegExp} pattern */
read_until(pattern) { read_until_regex(pattern) {
if (this.index >= this.template.length) { if (this.index >= this.template.length) {
if (this.loose) return ''; if (this.loose) return '';
e.unexpected_eof(this.template.length); e.unexpected_eof(this.template.length);
@ -302,6 +303,7 @@ export class Parser {
e.expected_whitespace(this.index); e.expected_whitespace(this.index);
} }
this.index++;
this.allow_whitespace(); this.allow_whitespace();
} }

@ -2,7 +2,6 @@
/** @import { Parser } from '../index.js' */ /** @import { Parser } from '../index.js' */
import { match_bracket } from '../utils/bracket.js'; import { match_bracket } from '../utils/bracket.js';
import { parse_expression_at, remove_parens } from '../acorn.js'; import { parse_expression_at, remove_parens } from '../acorn.js';
import { regex_not_newline_characters } from '../../patterns.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
/** /**
@ -33,24 +32,10 @@ export default function read_pattern(parser) {
i = match_bracket(parser, start); i = match_bracket(parser, start);
parser.index = i; parser.index = i;
const pattern_string = parser.template.slice(start, i); // acorn never reads before `start`, so the template itself can serve as the prefix
// the length of the `space_with_newline` has to be start - 1
// because we added a `(` in front of the pattern_string,
// which shifted the entire string to right by 1
// so we offset it by removing 1 character in the `space_with_newline`
// to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node
let space_with_newline = parser.template
.slice(0, start)
.replace(regex_not_newline_characters, ' ');
const first_space = space_with_newline.indexOf(' ');
space_with_newline =
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
/** @type {any} */ /** @type {any} */
let expression = remove_parens( let expression = remove_parens(
parse_expression_at(parser, `${space_with_newline}(${pattern_string} = 1)`, start - 1) parse_expression_at(parser, parser.template.slice(0, i) + ' = 1', start)
); );
expression = expression.left; expression = expression.left;
@ -80,7 +65,7 @@ function read_type_annotation(parser) {
const insert = '_ as '; const insert = '_ as ';
let a = parser.index - insert.length; let a = parser.index - insert.length;
const template = const template =
parser.template.slice(0, a).replace(/[^\n]/g, ' ') + parser.template.slice(0, a) +
insert + insert +
// If this is a type annotation for a function parameter, Acorn-TS will treat subsequent // If this is a type annotation for a function parameter, Acorn-TS will treat subsequent
// parameters as part of a sequence expression instead, and will then error on optional // parameters as part of a sequence expression instead, and will then error on optional

@ -1,9 +1,13 @@
/** @import { Expression } from 'estree' */ /** @import { Expression, Identifier } from 'estree' */
/** @import { Parser } from '../index.js' */ /** @import { Parser } from '../index.js' */
import { parse_expression_at, remove_parens } from '../acorn.js'; // @ts-expect-error acorn type definitions are borked in the release we use
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import { has_lf_line_breaks_only, parse_expression_at, remove_parens } from '../acorn.js';
import { regex_whitespace } from '../../patterns.js'; import { regex_whitespace } from '../../patterns.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import { find_matching_bracket } from '../utils/bracket.js'; import { find_matching_bracket } from '../utils/bracket.js';
import { is_reserved } from '../../../../utils.js';
import { locator } from '../../../state.js';
/** /**
* @param {Parser} parser * @param {Parser} parser
@ -33,6 +37,9 @@ export function get_loose_identifier(parser, opening_token) {
* @returns {Expression} * @returns {Expression}
*/ */
export default function read_expression(parser, opening_token, disallow_loose) { export default function read_expression(parser, opening_token, disallow_loose) {
const simple = read_simple_expression(parser);
if (simple) return simple;
try { try {
const node = parse_expression_at(parser, parser.template, parser.index); const node = parse_expression_at(parser, parser.template, parser.index);
@ -57,3 +64,104 @@ export default function read_expression(parser, opening_token, disallow_loose) {
throw err; throw err;
} }
} }
/**
* Most template expressions are an identifier or a `a.b.c` member chain followed by `}`.
* Those are built directly for better parse performance, with the same shape acorn would produce; anything else goes to acorn
* @param {Parser} parser
* @returns {Expression | null}
*/
function read_simple_expression(parser) {
if (!has_lf_line_breaks_only(parser)) return null;
const template = parser.template;
const index = parser.index;
parser.allow_whitespace();
const start = parser.index;
let end = read_word(template, start);
if (end === -1 || is_reserved(template.slice(start, end))) {
parser.index = index;
return null;
}
/** @type {Expression} */
let node = identifier(template, start, end);
while (template[end] === '.') {
const property_end = read_word(template, end + 1);
if (property_end === -1) {
parser.index = index;
return null;
}
node = {
type: 'MemberExpression',
start,
end: property_end,
loc: { start: position(start), end: position(property_end) },
object: node,
property: identifier(template, end + 1, property_end),
computed: false,
optional: false
};
end = property_end;
}
parser.index = end;
parser.allow_whitespace();
if (!parser.match('}')) {
parser.index = index;
return null;
}
parser.index = end;
return node;
}
/**
* @param {string} template
* @param {number} start
* @returns {number} the end of the identifier starting at `start`, or -1
*/
function read_word(template, start) {
if (start >= template.length) return -1;
const code = /** @type {number} */ (template.codePointAt(start));
if (!isIdentifierStart(code, true)) return -1;
let end = start + (code <= 0xffff ? 1 : 2);
while (end < template.length) {
const code = /** @type {number} */ (template.codePointAt(end));
if (!isIdentifierChar(code, true)) break;
end += code <= 0xffff ? 1 : 2;
}
return end;
}
/**
* @param {string} template
* @param {number} start
* @param {number} end
* @returns {Identifier}
*/
function identifier(template, start, end) {
return {
type: 'Identifier',
start,
end,
loc: { start: position(start), end: position(end) },
name: template.slice(start, end)
};
}
/** @param {number} index */
function position(index) {
const { line, column } = locator(index);
return { line, column };
}

@ -22,7 +22,7 @@ const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
*/ */
export function read_script(parser, start, attributes) { export function read_script(parser, start, attributes) {
const script_start = parser.index; const script_start = parser.index;
const data = parser.read_until(regex_closing_script_tag); const data = parser.read_until_regex(regex_closing_script_tag);
if (parser.index >= parser.template.length) { if (parser.index >= parser.template.length) {
e.element_unclosed(parser.template.length, 'script'); e.element_unclosed(parser.template.length, 'script');
} }

@ -16,8 +16,6 @@ const REGEX_WHITESPACE_OR_COLON = /[\s:]/;
const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/y; const REGEX_LEADING_HYPHEN_OR_DIGIT = /-?\d/y;
const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/; const REGEX_VALID_IDENTIFIER_CHAR = /[a-zA-Z0-9_-]/;
const REGEX_UNICODE_SEQUENCE = /\\[0-9a-fA-F]{1,6}(\r\n|\s)?/y; const REGEX_UNICODE_SEQUENCE = /\\[0-9a-fA-F]{1,6}(\r\n|\s)?/y;
const REGEX_COMMENT_CLOSE = /\*\//;
const REGEX_HTML_COMMENT_CLOSE = /-->/;
/** /**
* @param {Parser} parser * @param {Parser} parser
@ -478,7 +476,7 @@ function read_block_item(parser) {
function read_declaration(parser) { function read_declaration(parser) {
const start = parser.index; const start = parser.index;
const property = parser.read_until(REGEX_WHITESPACE_OR_COLON); const property = parser.read_until_regex(REGEX_WHITESPACE_OR_COLON);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat(':'); parser.eat(':');
let index = parser.index; let index = parser.index;
@ -661,7 +659,7 @@ function allow_comment_or_whitespace(parser, capture_comments = true) {
} }
if (parser.eat('<!--')) { if (parser.eat('<!--')) {
parser.read_until(REGEX_HTML_COMMENT_CLOSE); parser.read_until('-->');
parser.eat('-->', true); parser.eat('-->', true);
} }
@ -676,7 +674,7 @@ function allow_comment_or_whitespace(parser, capture_comments = true) {
function read_comment(parser) { function read_comment(parser) {
const start = parser.index; const start = parser.index;
parser.eat('/*', true); parser.eat('/*', true);
const value = parser.read_until(REGEX_COMMENT_CLOSE); const value = parser.read_until('*/');
parser.eat('*/', true); parser.eat('*/', true);
const end = parser.index; const end = parser.index;

@ -15,13 +15,10 @@ import { get_attribute_expression, is_expression_attribute } from '../../../util
import { closing_tag_omitted } from '../../../../html-tree-validation.js'; import { closing_tag_omitted } from '../../../../html-tree-validation.js';
import { list } from '../../../utils/string.js'; import { list } from '../../../utils/string.js';
import { locator } from '../../../state.js'; import { locator } from '../../../state.js';
import * as b from '#compiler/builders'; import { is_whitespace } from '../utils/whitespace.js';
const regex_invalid_unquoted_attribute_value = /(\/>|[\s"'=<>`])/y; const regex_invalid_unquoted_attribute_value = /(\/>|[\s"'=<>`])/y;
const regex_closing_textarea_tag = /<\/textarea(\s[^>]*)?>/iy; const regex_closing_textarea_tag = /<\/textarea(\s[^>]*)?>/iy;
const regex_closing_comment = /-->/;
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
const regex_token_ending_character = /[\s=/>"']/;
const regex_starts_with_quote_characters = /["']/y; const regex_starts_with_quote_characters = /["']/y;
const regex_attribute_value = /(?:"([^"]*)"|'([^'])*'|([^>\s]+))/y; const regex_attribute_value = /(?:"([^"]*)"|'([^'])*'|([^>\s]+))/y;
const regex_doctype_name = /^![a-zA-Z]+$/; const regex_doctype_name = /^![a-zA-Z]+$/;
@ -67,7 +64,7 @@ export default function element(parser) {
let parent = parser.current(); let parent = parser.current();
if (parser.eat('!--')) { if (parser.eat('!--')) {
const data = parser.read_until(regex_closing_comment); const data = parser.read_until('-->');
parser.eat('-->', true); parser.eat('-->', true);
parser.append({ parser.append({
@ -81,7 +78,7 @@ export default function element(parser) {
} }
if (parser.eat('/')) { if (parser.eat('/')) {
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag); const name = read_tag_name(parser);
parser.allow_whitespace(); parser.allow_whitespace();
parser.eat('>', true); parser.eat('>', true);
@ -137,7 +134,7 @@ export default function element(parser) {
return; return;
} }
const tag = read_tag(parser, regex_whitespace_or_slash_or_closing_tag); const tag = read_tag(parser);
if (tag.name.startsWith('svelte:') && !meta_tags.has(tag.name)) { if (tag.name.startsWith('svelte:') && !meta_tags.has(tag.name)) {
const bounds = { start: start + 1, end: start + 1 + tag.name.length }; const bounds = { start: start + 1, end: start + 1 + tag.name.length };
@ -475,7 +472,7 @@ function parent_is_shadowroot_template(stack) {
function read_static_attribute(parser) { function read_static_attribute(parser) {
const start = parser.index; const start = parser.index;
const tag = read_tag(parser, regex_token_ending_character); const tag = read_tag(parser, true);
if (!tag.name) return null; if (!tag.name) return null;
/** @type {true | Array<AST.Text | AST.ExpressionTag>} */ /** @type {true | Array<AST.Text | AST.ExpressionTag>} */
@ -607,7 +604,7 @@ function read_attribute(parser) {
} }
} }
const tag = read_tag(parser, regex_token_ending_character); const tag = read_tag(parser, true);
if (!tag.name) return null; if (!tag.name) return null;
@ -731,7 +728,7 @@ function read_comment(parser) {
const start = parser.index; const start = parser.index;
if (parser.eat('//')) { if (parser.eat('//')) {
const value = parser.read_until(/\n/); const value = parser.read_until('\n');
const end = parser.index; const end = parser.index;
return { return {
@ -747,7 +744,7 @@ function read_comment(parser) {
} }
if (parser.eat('/*')) { if (parser.eat('/*')) {
const value = parser.read_until(/\*\//); const value = parser.read_until('*/');
parser.eat('*/'); parser.eat('*/');
const end = parser.index; const end = parser.index;
@ -847,25 +844,21 @@ function read_attribute_value(parser) {
* @returns {any[]} * @returns {any[]}
*/ */
function read_sequence(parser, done, location) { function read_sequence(parser, done, location) {
/** @type {AST.Text} */
let current_chunk = {
start: parser.index,
end: -1,
type: 'Text',
raw: '',
data: ''
};
/** @type {Array<AST.Text | AST.ExpressionTag>} */ /** @type {Array<AST.Text | AST.ExpressionTag>} */
const chunks = []; const chunks = [];
let chunk_start = parser.index;
/** @param {number} end */ /** @param {number} end */
function flush(end) { function flush(end) {
if (end > current_chunk.start) { if (end > chunk_start) {
current_chunk.raw = parser.template.slice(current_chunk.start, end); const raw = parser.template.slice(chunk_start, end);
current_chunk.data = decode_character_references(current_chunk.raw, true); chunks.push({
current_chunk.end = end; start: chunk_start,
chunks.push(current_chunk); end,
type: 'Text',
raw,
data: decode_character_references(raw, true)
});
} }
} }
@ -879,12 +872,14 @@ function read_sequence(parser, done, location) {
if (parser.match('#')) { if (parser.match('#')) {
const index = parser.index - 1; const index = parser.index - 1;
parser.eat('#'); parser.eat('#');
const name = parser.read_until(/[^a-z]/); // const name = parser.read_until_regex(/[^a-z]/);
const name = read_lowercase_name(parser);
e.block_invalid_placement(index, name, location); e.block_invalid_placement(index, name, location);
} else if (parser.match('@')) { } else if (parser.match('@')) {
const index = parser.index - 1; const index = parser.index - 1;
parser.eat('@'); parser.eat('@');
const name = parser.read_until(/[^a-z]/); // const name = parser.read_until_regex(/[^a-z]/);
const name = read_lowercase_name(parser);
e.tag_invalid_placement(index, name, location); e.tag_invalid_placement(index, name, location);
} }
@ -907,14 +902,7 @@ function read_sequence(parser, done, location) {
}; };
chunks.push(chunk); chunks.push(chunk);
chunk_start = parser.index;
current_chunk = {
start: parser.index,
end: -1,
type: 'Text',
raw: '',
data: ''
};
} else { } else {
parser.index++; parser.index++;
} }
@ -929,12 +917,36 @@ function read_sequence(parser, done, location) {
/** /**
* @param {Parser} parser * @param {Parser} parser
* @param {RegExp} regex * @param {boolean} [attribute]
*/
function read_tag_name(parser, attribute = false) {
const start = parser.index;
if (start >= parser.template.length && !parser.loose) e.unexpected_eof(parser.template.length);
while (parser.index < parser.template.length) {
const cc = parser.template.charCodeAt(parser.index);
if (
is_whitespace(cc) ||
cc === 47 || // /
cc === 62 || // >
(attribute && (cc === 34 || cc === 39 || cc === 61)) // " ' =
) {
break;
}
parser.index += 1;
}
return parser.template.slice(start, parser.index);
}
/**
* @param {Parser} parser
* @param {boolean} [attribute]
* @returns {Identifier & { start: number, end: number, loc: SourceLocation }} * @returns {Identifier & { start: number, end: number, loc: SourceLocation }}
*/ */
function read_tag(parser, regex) { function read_tag(parser, attribute = false) {
const start = parser.index; const start = parser.index;
const name = parser.read_until(regex); const name = read_tag_name(parser, attribute);
const end = parser.index; const end = parser.index;
return { return {
@ -948,3 +960,15 @@ function read_tag(parser, regex) {
} }
}; };
} }
/** @param {Parser} parser */
function read_lowercase_name(parser) {
const start = parser.index;
while (parser.index < parser.template.length) {
const cc = parser.template.charCodeAt(parser.index);
// a-z
if (cc < 97 || cc > 122) break;
parser.index += 1;
}
return parser.template.slice(start, parser.index);
}

@ -488,12 +488,13 @@ function open(parser) {
parser.eat(')', true); parser.eat(')', true);
} }
const prelude = parser.template.slice(0, params_start).replace(/\S/g, ' ');
const params = parser.template.slice(params_start, parser.index);
let function_expression = matched let function_expression = matched
? /** @type {ArrowFunctionExpression} */ ( ? /** @type {ArrowFunctionExpression} */ (
parse_expression_at(parser, prelude + `${params} => {}`, params_start) parse_expression_at(
parser,
parser.template.slice(0, parser.index) + ' => {}',
params_start
)
) )
: { params: [] }; : { params: [] };

@ -38,6 +38,8 @@ const entity_pattern_attr_value = get_entity_pattern(true);
* @param {boolean} is_attribute_value * @param {boolean} is_attribute_value
*/ */
export function decode_character_references(html, is_attribute_value) { export function decode_character_references(html, is_attribute_value) {
if (html.indexOf('&') === -1) return html; // fast path
const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content; const entity_pattern = is_attribute_value ? entity_pattern_attr_value : entity_pattern_content;
return html.replace( return html.replace(
entity_pattern, entity_pattern,

@ -0,0 +1,18 @@
/** @param {number} cc */
export function is_whitespace(cc) {
// fast path for common whitespace
if (cc === 32 || (cc <= 13 && cc >= 9)) return true;
// rare whitespace — \u00a0, \u1680, \u2000-\u200a, \u2028, \u2029, \u202f, \u205f, \u3000, \ufeff
if (cc < 160) return false;
return (
cc === 160 ||
cc === 5760 ||
(cc >= 8192 && cc <= 8202) ||
cc === 8232 ||
cc === 8233 ||
cc === 8239 ||
cc === 8287 ||
cc === 12288 ||
cc === 65279
);
}

@ -80,6 +80,14 @@ export function build_element_attributes(node, context, transform) {
) { ) {
events_to_capture.add(attribute.name); events_to_capture.add(attribute.name);
} }
} else if (
node.type === 'RegularElement' &&
node.name === 'input' &&
(attribute.name === 'defaultValue' || attribute.name === 'defaultChecked')
) {
attributes.push(attribute);
// deopt to spread at runtime, where we can handle interaction of value/defaultValue etc
has_spread = true;
// the defaultValue/defaultChecked properties don't exist as attributes // the defaultValue/defaultChecked properties don't exist as attributes
} else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') { } else if (attribute.name !== 'defaultValue' && attribute.name !== 'defaultChecked') {
if (attribute.name === 'class') { if (attribute.name === 'class') {

@ -33,10 +33,14 @@ export let component_name = '<unknown>';
export let source; export let source;
/** /**
* The source code split into lines (set by `set_source`) * The source code split into lines, initialized when a diagnostic needs a code frame
* @type {string[]} * @type {string[] | undefined}
*/ */
export let source_lines = []; let source_lines;
export function get_source_lines() {
return (source_lines ??= source.split('\n'));
}
/** /**
* True if compiling with `dev: true` * True if compiling with `dev: true`
@ -61,7 +65,7 @@ export let locator;
/** @param {string} value */ /** @param {string} value */
export function set_source(value) { export function set_source(value) {
source = value; source = value;
source_lines = source.split('\n'); source_lines = undefined;
const l = getLocator(source, { offsetLine: 1 }); const l = getLocator(source, { offsetLine: 1 });
@ -151,7 +155,7 @@ export function reset(state) {
custom_renderer = undefined; custom_renderer = undefined;
component_name = UNKNOWN_FILENAME; component_name = UNKNOWN_FILENAME;
source = ''; source = '';
source_lines = []; source_lines = undefined;
filename = (state.filename ?? UNKNOWN_FILENAME).replace(/\\/g, '/'); filename = (state.filename ?? UNKNOWN_FILENAME).replace(/\\/g, '/');
warning_filter = state.warning ?? (() => true); warning_filter = state.warning ?? (() => true);
warnings = []; warnings = [];

@ -286,7 +286,7 @@ function _extract_paths(paths, inserts, param, expression, update_expression, ha
const props = []; const props = [];
for (const p of param.properties) { for (const p of param.properties) {
if (p.type === 'Property' && p.key.type !== 'PrivateIdentifier') { if (p.type === 'Property') {
if (p.key.type === 'Identifier' && !p.computed) { if (p.key.type === 'Identifier' && !p.computed) {
props.push(b.literal(p.key.name)); props.push(b.literal(p.key.name));
} else if (p.key.type === 'Literal') { } else if (p.key.type === 'Literal') {
@ -548,10 +548,7 @@ export function is_expression_async(expression) {
if (property.type === 'SpreadElement') { if (property.type === 'SpreadElement') {
return is_expression_async(property.argument); return is_expression_async(property.argument);
} else if (property.type === 'Property') { } else if (property.type === 'Property') {
return ( return is_expression_async(property.key) || is_expression_async(property.value);
(property.key.type !== 'PrivateIdentifier' && is_expression_async(property.key)) ||
is_expression_async(property.value)
);
} }
}); });
} }

@ -15,7 +15,7 @@ function tabs_to_spaces(str) {
* @param {number} column * @param {number} column
*/ */
function get_code_frame(line, column) { function get_code_frame(line, column) {
const lines = state.source_lines; const lines = state.get_source_lines();
const frame_start = Math.max(0, line - 2); const frame_start = Math.max(0, line - 2);
const frame_end = Math.min(line + 3, lines.length); const frame_end = Math.min(line + 3, lines.length);
const digits = String(frame_end + 1).length; const digits = String(frame_end + 1).length;

@ -445,22 +445,6 @@ export function rune_outside_svelte(rune) {
} }
} }
/**
* `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
* @returns {never}
*/
export function set_context_after_init() {
if (DEV) {
const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/set_context_after_init`);
}
}
/** /**
* A snippet created in a component with a custom renderer cannot be rendered by a different renderer * A snippet created in a component with a custom renderer cannot be rendered by a different renderer
* @returns {never} * @returns {never}

@ -1,6 +1,7 @@
/** @import { SSRContext } from '#server' */ /** @import { SSRContext } from '#server' */
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { create_context, get_or_init_context_map } from '../shared/context.js'; import { create_context, get_or_init_context_map } from '../shared/context.js';
import * as e from './errors.js';
/** @type {SSRContext | null} */ /** @type {SSRContext | null} */
export var ssr_context = null; export var ssr_context = null;
@ -40,7 +41,13 @@ export function getContext(key) {
* @returns {T} * @returns {T}
*/ */
export function setContext(key, context) { export function setContext(key, context) {
get_or_init_context_map(ssr_context, 'setContext').set(key, context); const context_map = get_or_init_context_map(ssr_context, 'setContext');
if (/** @type {SSRContext} */ (ssr_context).i) {
e.set_context_after_init();
}
context_map.set(key, context);
return context; return context;
} }
@ -61,7 +68,7 @@ export function getAllContexts() {
* @param {Function} [fn] * @param {Function} [fn]
*/ */
export function push(fn) { export function push(fn) {
ssr_context = { p: ssr_context, c: null, r: null }; ssr_context = { p: ssr_context, c: null, r: null, i: false };
if (DEV) { if (DEV) {
ssr_context.function = fn; ssr_context.function = fn;

@ -146,8 +146,10 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0; const is_html = (flags & ELEMENT_IS_NAMESPACED) === 0;
const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0; const lowercase = (flags & ELEMENT_PRESERVE_ATTRIBUTE_CASE) === 0;
const is_input = (flags & ELEMENT_IS_INPUT) !== 0; const is_input = (flags & ELEMENT_IS_INPUT) !== 0;
const names = Object.keys(attrs);
for (name of Object.keys(attrs)) { outer: for (let i = 0; i < names.length; i++) {
name = names[i];
// omit functions, internal svelte properties and invalid attribute names // omit functions, internal svelte properties and invalid attribute names
if (typeof attrs[name] === 'function') continue; if (typeof attrs[name] === 'function') continue;
if (name[0] === '$' && name[1] === '$') continue; // faster than name.startsWith('$$') if (name[0] === '$' && name[1] === '$') continue; // faster than name.startsWith('$$')
@ -163,8 +165,13 @@ export function attributes(attrs, css_hash, classes, styles, flags = 0) {
if (is_input) { if (is_input) {
if (name === 'defaultvalue' || name === 'defaultchecked') { if (name === 'defaultvalue' || name === 'defaultchecked') {
// value/checked takes precedence over defaultValue/defaultChecked
name = name === 'defaultvalue' ? 'value' : 'checked'; name = name === 'defaultvalue' ? 'value' : 'checked';
if (attrs[name]) continue; if (name in attrs) continue;
// We're checking prior entries aswell because "name in attrs" is not enough as the attributes may have different casing
for (let j = 0; j < names.length; j++) {
if (names[j].toLowerCase() === name) continue outer;
}
} }
} }

@ -162,6 +162,11 @@ export class Renderer {
let promise = Promise.resolve(thunks[0]()); let promise = Promise.resolve(thunks[0]());
const promises = [promise]; const promises = [promise];
if (context !== null && thunks.length > 1) {
// the remaining thunks run after an `await`, by which point it is too late to set context
context.i = true;
}
for (const fn of thunks.slice(1)) { for (const fn of thunks.slice(1)) {
promise = promise.then(() => { promise = promise.then(() => {
const previous_context = ssr_context; const previous_context = ssr_context;
@ -209,7 +214,8 @@ export class Renderer {
...ssr_context, ...ssr_context,
p: parent, p: parent,
c: null, c: null,
r: child r: child,
i: ssr_context?.i ?? false
}); });
const result = fn(child); const result = fn(child);
@ -260,7 +266,8 @@ export class Renderer {
...ssr_context, ...ssr_context,
p: parent_context, p: parent_context,
c: null, c: null,
r: child r: child,
i: ssr_context?.i ?? false
}); });
try { try {
@ -859,7 +866,7 @@ export class Renderer {
try { try {
/** @type {SSRContext} */ /** @type {SSRContext} */
const context = { p: null, c: options.context ?? null, r: renderer }; const context = { p: null, c: options.context ?? null, r: renderer, i: false };
set_ssr_context(context); set_ssr_context(context);
renderer.push(BLOCK_OPEN); renderer.push(BLOCK_OPEN);

@ -9,6 +9,8 @@ export interface SSRContext {
c: null | Map<unknown, unknown>; c: null | Map<unknown, unknown>;
/** renderer */ /** renderer */
r: null | Renderer; r: null | Renderer;
/** True if initialized, i.e. an `await` was reached */
i: boolean;
/** dev mode only: the current component function */ /** dev mode only: the current component function */
function?: any; function?: any;
/** dev mode only: the current element */ /** dev mode only: the current element */

@ -101,6 +101,22 @@ export function missing_context() {
} }
} }
/**
* `setContext` must be called when a component first initializes, not in a subsequent effect or after an `await` expression
* @returns {never}
*/
export function set_context_after_init() {
if (DEV) {
const error = new Error(`set_context_after_init\n\`setContext\` must be called when a component first initializes, not in a subsequent effect or after an \`await\` expression\nhttps://svelte.dev/e/set_context_after_init`);
error.name = 'Svelte error';
throw error;
} else {
throw new Error(`https://svelte.dev/e/set_context_after_init`);
}
}
/** /**
* Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`. * Attempted to render a snippet without a `{@render}` block. This would cause the snippet code to be stringified instead of its content being rendered to the DOM. To fix this, change `{snippet}` to `{@render snippet()}`.
* @returns {never} * @returns {never}

@ -1,2 +1,8 @@
/** Anything except a function */ /** Anything except a function */
export type NotFunction<T> = T extends Function ? never : T; export type NotFunction<T> = T extends Function ? never : T;
declare global {
// @ts-ignore devalue has it in its types, but it's not part of the standard lib at the version our runtime is.
// We're not actually doing anything with it so we silence the error this ay
type Float16Array = any;
}

@ -82,11 +82,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 19 "column": 18
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 40 "column": 39
} }
}, },
"elements": [ "elements": [
@ -97,11 +97,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 23 "column": 22
} }
}, },
"name": "key" "name": "key"
@ -113,11 +113,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 25 "column": 24
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 30 "column": 29
} }
}, },
"name": "value" "name": "value"
@ -129,11 +129,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 32 "column": 31
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 39 "column": 38
} }
}, },
"argument": { "argument": {
@ -143,11 +143,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 35 "column": 34
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 39 "column": 38
} }
}, },
"name": "rest" "name": "rest"

@ -170,11 +170,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 13 "column": 12
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 24 "column": 23
} }
}, },
"properties": [ "properties": [
@ -185,11 +185,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 22 "column": 21
} }
}, },
"method": false, "method": false,
@ -202,11 +202,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -218,11 +218,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 22 "column": 21
} }
}, },
"left": { "left": {
@ -232,11 +232,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -248,11 +248,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 3, "line": 3,
"column": 19 "column": 18
}, },
"end": { "end": {
"line": 3, "line": 3,
"column": 22 "column": 21
} }
}, },
"value": "{", "value": "{",
@ -302,11 +302,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 13 "column": 12
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 24 "column": 23
} }
}, },
"properties": [ "properties": [
@ -317,11 +317,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 22 "column": 21
} }
}, },
"method": false, "method": false,
@ -334,11 +334,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -350,11 +350,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 22 "column": 21
} }
}, },
"left": { "left": {
@ -364,11 +364,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -380,11 +380,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 19 "column": 18
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 22 "column": 21
} }
}, },
"value": "]", "value": "]",
@ -434,11 +434,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 13 "column": 12
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 29 "column": 28
} }
}, },
"properties": [ "properties": [
@ -449,11 +449,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 27 "column": 26
} }
}, },
"method": false, "method": false,
@ -466,11 +466,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -482,11 +482,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 27 "column": 26
} }
}, },
"left": { "left": {
@ -496,11 +496,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -512,11 +512,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 19 "column": 18
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 27 "column": 26
} }
}, },
"expressions": [ "expressions": [
@ -527,11 +527,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 22 "column": 21
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 25 "column": 24
} }
}, },
"expressions": [], "expressions": [],
@ -543,11 +543,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 23 "column": 22
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 24 "column": 23
} }
}, },
"value": { "value": {
@ -567,11 +567,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 20 "column": 19
} }
}, },
"value": { "value": {
@ -587,11 +587,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 7, "line": 7,
"column": 26 "column": 25
}, },
"end": { "end": {
"line": 7, "line": 7,
"column": 26 "column": 25
} }
}, },
"value": { "value": {
@ -646,11 +646,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 13 "column": 12
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 32 "column": 31
} }
}, },
"properties": [ "properties": [
@ -661,11 +661,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 30 "column": 29
} }
}, },
"method": false, "method": false,
@ -678,11 +678,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -694,11 +694,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 30 "column": 29
} }
}, },
"left": { "left": {
@ -708,11 +708,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 16 "column": 15
} }
}, },
"name": "y" "name": "y"
@ -724,11 +724,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 19 "column": 18
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 30 "column": 29
} }
}, },
"expressions": [ "expressions": [
@ -739,11 +739,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 22 "column": 21
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 28 "column": 27
} }
}, },
"expressions": [], "expressions": [],
@ -755,11 +755,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 23 "column": 22
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 27 "column": 26
} }
}, },
"value": { "value": {
@ -779,11 +779,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 20 "column": 19
} }
}, },
"value": { "value": {
@ -799,11 +799,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 29 "column": 28
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 29 "column": 28
} }
}, },
"value": { "value": {
@ -822,5 +822,6 @@
} }
] ]
}, },
"options": null "options": null,
"comments": []
} }

@ -495,11 +495,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 18 "column": 17
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 57 "column": 56
} }
}, },
"properties": [ "properties": [
@ -510,11 +510,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 42 "column": 41
} }
}, },
"method": false, "method": false,
@ -527,11 +527,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 24 "column": 23
} }
}, },
"name": "name" "name": "name"
@ -543,11 +543,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 42 "column": 41
} }
}, },
"left": { "left": {
@ -557,11 +557,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 24 "column": 23
} }
}, },
"name": "name" "name": "name"
@ -573,11 +573,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 27 "column": 26
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 42 "column": 41
} }
}, },
"expressions": [ "expressions": [
@ -588,11 +588,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 35 "column": 34
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 40 "column": 39
} }
}, },
"value": "Doe", "value": "Doe",
@ -607,11 +607,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 28 "column": 27
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 33 "column": 32
} }
}, },
"value": { "value": {
@ -627,11 +627,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 41 "column": 40
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 41 "column": 40
} }
}, },
"value": { "value": {
@ -652,11 +652,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 44 "column": 43
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 55 "column": 54
} }
}, },
"method": false, "method": false,
@ -669,11 +669,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 44 "column": 43
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 48 "column": 47
} }
}, },
"name": "cool" "name": "cool"
@ -685,11 +685,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 44 "column": 43
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 55 "column": 54
} }
}, },
"left": { "left": {
@ -699,11 +699,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 44 "column": 43
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 48 "column": 47
} }
}, },
"name": "cool" "name": "cool"
@ -715,11 +715,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 51 "column": 50
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 55 "column": 54
} }
}, },
"value": true, "value": true,
@ -906,11 +906,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 18 "column": 17
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 79 "column": 78
} }
}, },
"properties": [ "properties": [
@ -921,11 +921,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 64 "column": 63
} }
}, },
"method": false, "method": false,
@ -938,11 +938,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 24 "column": 23
} }
}, },
"name": "name" "name": "name"
@ -954,11 +954,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 64 "column": 63
} }
}, },
"left": { "left": {
@ -968,11 +968,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 20 "column": 19
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 24 "column": 23
} }
}, },
"name": "name" "name": "name"
@ -984,11 +984,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 27 "column": 26
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 64 "column": 63
} }
}, },
"callee": { "callee": {
@ -998,11 +998,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 28 "column": 27
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 61 "column": 60
} }
}, },
"id": null, "id": null,
@ -1017,11 +1017,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 34 "column": 33
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 61 "column": 60
} }
}, },
"body": [ "body": [
@ -1032,11 +1032,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 36 "column": 35
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 59 "column": 58
} }
}, },
"argument": { "argument": {
@ -1046,11 +1046,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 43 "column": 42
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 58 "column": 57
} }
}, },
"expressions": [ "expressions": [
@ -1061,11 +1061,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 51 "column": 50
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 56 "column": 55
} }
}, },
"value": "Doe", "value": "Doe",
@ -1080,11 +1080,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 44 "column": 43
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 49 "column": 48
} }
}, },
"value": { "value": {
@ -1100,11 +1100,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 57 "column": 56
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 57 "column": 56
} }
}, },
"value": { "value": {
@ -1132,11 +1132,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 66 "column": 65
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 77 "column": 76
} }
}, },
"method": false, "method": false,
@ -1149,11 +1149,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 66 "column": 65
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 70 "column": 69
} }
}, },
"name": "cool" "name": "cool"
@ -1165,11 +1165,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 66 "column": 65
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 77 "column": 76
} }
}, },
"left": { "left": {
@ -1179,11 +1179,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 66 "column": 65
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 70 "column": 69
} }
}, },
"name": "cool" "name": "cool"
@ -1195,11 +1195,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 9, "line": 9,
"column": 73 "column": 72
}, },
"end": { "end": {
"line": 9, "line": 9,
"column": 77 "column": 76
} }
}, },
"value": true, "value": true,
@ -1213,5 +1213,6 @@
} }
] ]
}, },
"options": null "options": null,
"comments": []
} }

@ -133,11 +133,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 15 "column": 14
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 39 "column": 38
} }
}, },
"elements": [ "elements": [
@ -148,11 +148,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 16 "column": 15
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 19 "column": 18
} }
}, },
"name": "key" "name": "key"
@ -164,11 +164,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 21 "column": 20
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 38 "column": 37
} }
}, },
"left": { "left": {
@ -178,11 +178,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 21 "column": 20
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 26 "column": 25
} }
}, },
"name": "value" "name": "value"
@ -194,11 +194,11 @@
"loc": { "loc": {
"start": { "start": {
"line": 5, "line": 5,
"column": 29 "column": 28
}, },
"end": { "end": {
"line": 5, "line": 5,
"column": 38 "column": 37
} }
}, },
"value": "default", "value": "default",
@ -211,6 +211,7 @@
] ]
}, },
"options": null, "options": null,
"comments": [],
"instance": { "instance": {
"type": "Script", "type": "Script",
"start": 0, "start": 0,

@ -0,0 +1,9 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
test({ assert, target, logs }) {
flushSync(() => target.querySelector('button')?.click());
assert.deepEqual(logs, ['setup: two', 'cleanup: two']);
}
});

@ -0,0 +1,28 @@
<script>
let start = $state(false);
let enabled = $state(false);
let advance = $state(false);
let value = $state('one');
$effect(() => {
if (start) {
value = 'two';
enabled = true;
}
});
$effect(() => {
if (enabled) {
console.log(`setup: ${value}`);
advance = true;
return () => console.log(`cleanup: ${value}`);
}
});
$effect(() => {
if (advance) enabled = false;
});
</script>
<button onclick={() => (start = true)}>start</button>

@ -1,7 +1,6 @@
import { test } from '../../test'; import { test } from '../../test';
export default test({ export default test({
skip: true, // TODO it appears there might be an actual bug here; the promise isn't ever actually awaited in spite of being awaited in the component
mode: ['async'], mode: ['async'],
error: 'lifecycle_outside_component' error: 'set_context_after_init'
}); });

@ -0,0 +1,3 @@
import { test } from '../../test';
export default test({});

@ -0,0 +1,6 @@
<input value="hello" />
<input type="checkbox" checked />
<input value="spread" checked />
<input value="" />
<input value="" />
<input value="" />

@ -0,0 +1,10 @@
<script>
const props = { defaultValue: 'spread', defaultChecked: true };
</script>
<input defaultValue="hello" />
<input type="checkbox" defaultChecked />
<input {...props} />
<input {...props} value="" checked={false} />
<input value="" checked={false} {...props} />
<input {...props} {...{ VALUE: '', CHECKED: false }} />

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save