docs: add a couple internal JSDocs and cleanup from TS migration (#8940)

pull/8951/head
Ben McCann 2 years ago committed by GitHub
parent 895709c6a2
commit 8601195a85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,16 +3,14 @@ const now = () => performance.now();
/** @param {any} timings */ /** @param {any} timings */
function collapse_timings(timings) { function collapse_timings(timings) {
const result = {}; const result = {};
timings.forEach( timings.forEach((timing) => {
/** @param {any} timing */ (timing) => {
result[timing.label] = Object.assign( result[timing.label] = Object.assign(
{ {
total: timing.end - timing.start total: timing.end - timing.start
}, },
timing.children && collapse_timings(timing.children) timing.children && collapse_timings(timing.children)
); );
} });
);
return result; return result;
} }

@ -265,7 +265,7 @@ export default class Component {
); );
this.walk_instance_js_post_template(); this.walk_instance_js_post_template();
this.pop_ignores(); this.pop_ignores();
this.elements.forEach(/** @param {any} element */ (element) => this.stylesheet.apply(element)); this.elements.forEach((element) => this.stylesheet.apply(element));
this.stylesheet.reify(); this.stylesheet.reify();
this.stylesheet.warn_on_unused_selectors(this); this.stylesheet.warn_on_unused_selectors(this);
} }
@ -405,19 +405,15 @@ export default class Component {
}); });
const referenced_globals = Array.from( const referenced_globals = Array.from(
this.globals, this.globals,
/** @param {any}params_0 */
([name, alias]) => name !== alias.name && { name, alias } ([name, alias]) => name !== alias.name && { name, alias }
).filter(Boolean); ).filter(Boolean);
if (referenced_globals.length) { if (referenced_globals.length) {
this.helpers.set('globals', this.alias('globals')); this.helpers.set('globals', this.alias('globals'));
} }
const imported_helpers = Array.from( const imported_helpers = Array.from(this.helpers, ([name, alias]) => ({
this.helpers,
/** @param {any}params_0 */ ([name, alias]) => ({
name, name,
alias alias
}) }));
);
create_module( create_module(
program, program,
name, name,
@ -427,15 +423,11 @@ export default class Component {
referenced_globals, referenced_globals,
this.imports, this.imports,
this.vars this.vars
.filter( .filter((variable) => variable.module && variable.export_name)
/** @param {any} variable */ (variable) => variable.module && variable.export_name .map((variable) => ({
)
.map(
/** @param {any} variable */ (variable) => ({
name: variable.name, name: variable.name,
as: variable.export_name as: variable.export_name
}) })),
),
this.exports_from this.exports_from
); );
css = compile_options.customElement ? { code: null, map: null } : result.css; css = compile_options.customElement ? { code: null, map: null } : result.css;
@ -499,12 +491,7 @@ export default class Component {
} }
reserved.forEach(add); reserved.forEach(add);
internal_exports.forEach(add); internal_exports.forEach(add);
this.var_lookup.forEach( this.var_lookup.forEach((_value, key) => add(key));
/**
* @param {any} _value
* @param {any} key
*/ (_value, key) => add(key)
);
/** /**
* @param {string} name * @param {string} name
@ -534,9 +521,8 @@ export default class Component {
? [] ? []
: compile_options.varsReport === 'full' : compile_options.varsReport === 'full'
? vars ? vars
: vars.filter(/** @param {any} v */ (v) => !v.global && !v.internal); : vars.filter((v) => !v.global && !v.internal);
return vars_report.map( return vars_report.map((v) => ({
/** @param {any} v */ (v) => ({
name: v.name, name: v.name,
export_name: v.export_name || null, export_name: v.export_name || null,
injected: v.injected || false, injected: v.injected || false,
@ -546,8 +532,7 @@ export default class Component {
referenced: v.referenced || false, referenced: v.referenced || false,
writable: v.writable || false, writable: v.writable || false,
referenced_from_script: v.referenced_from_script || false referenced_from_script: v.referenced_from_script || false
}) }));
);
} }
/** /**
* @param {{ * @param {{
@ -639,10 +624,8 @@ export default class Component {
} }
if (node.declaration) { if (node.declaration) {
if (node.declaration.type === 'VariableDeclaration') { if (node.declaration.type === 'VariableDeclaration') {
node.declaration.declarations.forEach( node.declaration.declarations.forEach((declarator) => {
/** @param {any} declarator */ (declarator) => { extract_names(declarator.id).forEach((name) => {
extract_names(declarator.id).forEach(
/** @param {any} name */ (name) => {
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
variable.export_name = name; variable.export_name = name;
if ( if (
@ -654,21 +637,15 @@ export default class Component {
if ( if (
!module_script && !module_script &&
variable.writable && variable.writable &&
!( !(variable.referenced || variable.referenced_from_script || variable.subscribable)
variable.referenced ||
variable.referenced_from_script ||
variable.subscribable
)
) { ) {
this.warn( this.warn(
/** @type {any} */ (declarator), /** @type {any} */ (declarator),
compiler_warnings.unused_export_let(this.name.name, name) compiler_warnings.unused_export_let(this.name.name, name)
); );
} }
} });
); });
}
);
} else { } else {
const { name } = node.declaration.id; const { name } = node.declaration.id;
const variable = this.var_lookup.get(name); const variable = this.var_lookup.get(name);
@ -676,8 +653,7 @@ export default class Component {
} }
return node.declaration; return node.declaration;
} else { } else {
node.specifiers.forEach( node.specifiers.forEach((specifier) => {
/** @param {any} specifier */ (specifier) => {
const variable = this.var_lookup.get(specifier.local.name); const variable = this.var_lookup.get(specifier.local.name);
if (variable) { if (variable) {
variable.export_name = specifier.exported.name; variable.export_name = specifier.exported.name;
@ -692,8 +668,7 @@ export default class Component {
); );
} }
} }
} });
);
return null; return null;
} }
} }
@ -702,16 +677,14 @@ export default class Component {
/** @param {any} script */ /** @param {any} script */
extract_javascript(script) { extract_javascript(script) {
if (!script) return null; if (!script) return null;
return script.content.body.filter( return script.content.body.filter((node) => {
/** @param {any} node */ (node) => {
if (!node) return false; if (!node) return false;
if (this.hoistable_nodes.has(node)) return false; if (this.hoistable_nodes.has(node)) return false;
if (this.reactive_declaration_nodes.has(node)) return false; if (this.reactive_declaration_nodes.has(node)) return false;
if (node.type === 'ImportDeclaration') return false; if (node.type === 'ImportDeclaration') return false;
if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false; if (node.type === 'ExportDeclaration' && node.specifiers.length > 0) return false;
return true; return true;
} });
);
} }
walk_module_js() { walk_module_js() {
const component = this; const component = this;
@ -730,11 +703,7 @@ export default class Component {
}); });
const { scope, globals } = create_scopes(script.content); const { scope, globals } = create_scopes(script.content);
this.module_scope = scope; this.module_scope = scope;
scope.declarations.forEach( scope.declarations.forEach((node, name) => {
/**
* @param {any} node
* @param {any} name
*/ (node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration);
} }
@ -748,13 +717,8 @@ export default class Component {
writable, writable,
imported imported
}); });
} });
); globals.forEach((node, name) => {
globals.forEach(
/**
* @param {any} node
* @param {any} name
*/ (node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
return this.error(/** @type {any} */ (node), compiler_errors.illegal_subscription); return this.error(/** @type {any} */ (node), compiler_errors.illegal_subscription);
} else { } else {
@ -764,8 +728,7 @@ export default class Component {
hoistable: true hoistable: true
}); });
} }
} });
);
const { body } = script.content; const { body } = script.content;
let i = body.length; let i = body.length;
while (--i >= 0) { while (--i >= 0) {
@ -788,30 +751,22 @@ export default class Component {
const script = this.ast.instance; const script = this.ast.instance;
if (!script) return; if (!script) return;
// inject vars for reactive declarations // inject vars for reactive declarations
script.content.body.forEach( script.content.body.forEach((node) => {
/** @param {any} node */ (node) => {
if (node.type !== 'LabeledStatement') return; if (node.type !== 'LabeledStatement') return;
if (node.body.type !== 'ExpressionStatement') return; if (node.body.type !== 'ExpressionStatement') return;
const { expression } = node.body; const { expression } = node.body;
if (expression.type !== 'AssignmentExpression') return; if (expression.type !== 'AssignmentExpression') return;
if (expression.left.type === 'MemberExpression') return; if (expression.left.type === 'MemberExpression') return;
extract_names(expression.left).forEach( extract_names(expression.left).forEach((name) => {
/** @param {any} name */ (name) => {
if (!this.var_lookup.has(name) && name[0] !== '$') { if (!this.var_lookup.has(name) && name[0] !== '$') {
this.injected_reactive_declaration_vars.add(name); this.injected_reactive_declaration_vars.add(name);
} }
} });
); });
}
);
const { scope: instance_scope, map, globals } = create_scopes(script.content); const { scope: instance_scope, map, globals } = create_scopes(script.content);
this.instance_scope = instance_scope; this.instance_scope = instance_scope;
this.instance_scope_map = map; this.instance_scope_map = map;
instance_scope.declarations.forEach( instance_scope.declarations.forEach((node, name) => {
/**
* @param {any} node
* @param {any} name
*/ (node, name) => {
if (name[0] === '$') { if (name[0] === '$') {
return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration);
} }
@ -825,17 +780,15 @@ export default class Component {
imported imported
}); });
this.node_for_declaration.set(name, node); this.node_for_declaration.set(name, node);
} });
);
// NOTE: add store variable first, then only $store value // NOTE: add store variable first, then only $store value
// as `$store` will mark `store` variable as referenced and subscribable // as `$store` will mark `store` variable as referenced and subscribable
const global_keys = Array.from(globals.keys()); const global_keys = Array.from(globals.keys());
const sorted_globals = [ const sorted_globals = [
...global_keys.filter(/** @param {any} key */ (key) => key[0] !== '$'), ...global_keys.filter((key) => key[0] !== '$'),
...global_keys.filter(/** @param {any} key */ (key) => key[0] === '$') ...global_keys.filter((key) => key[0] === '$')
]; ];
sorted_globals.forEach( sorted_globals.forEach((name) => {
/** @param {any} name */ (name) => {
if (this.var_lookup.has(name)) return; if (this.var_lookup.has(name)) return;
const node = globals.get(name); const node = globals.get(name);
if (this.injected_reactive_declaration_vars.has(name)) { if (this.injected_reactive_declaration_vars.has(name)) {
@ -874,8 +827,7 @@ export default class Component {
hoistable: true hoistable: true
}); });
} }
} });
);
this.track_references_and_mutations(); this.track_references_and_mutations();
} }
walk_instance_js_post_template() { walk_instance_js_post_template() {
@ -909,12 +861,7 @@ export default class Component {
/** @type {import('estree').FunctionDeclaration | import('estree').FunctionExpression} */ /** @type {import('estree').FunctionDeclaration | import('estree').FunctionExpression} */
let current_function = null; let current_function = null;
walk(content, { walk(content, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {import('estree').Node} parent
* @param {any} prop
* @param {any} index
*/
enter(node, parent, prop, index) { enter(node, parent, prop, index) {
if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') { if (node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') {
current_function_stack.push((current_function = node)); current_function_stack.push((current_function = node));
@ -953,8 +900,7 @@ export default class Component {
names.push(name); names.push(name);
} }
if (names.length > 0) { if (names.length > 0) {
names.forEach( names.forEach((name) => {
/** @param {any} name */ (name) => {
let current_scope = scope; let current_scope = scope;
let declaration; let declaration;
while (current_scope) { while (current_scope) {
@ -964,14 +910,17 @@ export default class Component {
} }
current_scope = current_scope.parent; current_scope = current_scope.parent;
} }
if (declaration && /** @type {any} */ (declaration).kind === 'const' && !deep) { if (
declaration &&
/** @type {import('estree').VariableDeclaration} */ (declaration).kind === 'const' &&
!deep
) {
component.error(/** @type {any} */ (node), { component.error(/** @type {any} */ (node), {
code: 'assignment-to-const', code: 'assignment-to-const',
message: 'You are assigning to a const' message: 'You are assigning to a const'
}); });
} }
} });
);
} }
if (node.type === 'ImportDeclaration') { if (node.type === 'ImportDeclaration') {
component.extract_imports(node); component.extract_imports(node);
@ -1054,8 +1003,7 @@ export default class Component {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument; const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const names = extract_names(/** @type {import('estree').Node} */ (assignee)); const names = extract_names(/** @type {import('estree').Node} */ (assignee));
const deep = assignee.type === 'MemberExpression'; const deep = assignee.type === 'MemberExpression';
names.forEach( names.forEach((name) => {
/** @param {any} name */ (name) => {
const scope_owner = scope.find_owner(name); const scope_owner = scope.find_owner(name);
if ( if (
scope_owner !== null scope_owner !== null
@ -1065,8 +1013,7 @@ export default class Component {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
variable[deep ? 'mutated' : 'reassigned'] = true; variable[deep ? 'mutated' : 'reassigned'] = true;
} }
} });
);
} }
if (is_used_as_reference(node, parent)) { if (is_used_as_reference(node, parent)) {
const object = get_object(node); const object = get_object(node);
@ -1339,8 +1286,7 @@ export default class Component {
for (let i = 0; i < body.length; i += 1) { for (let i = 0; i < body.length; i += 1) {
const node = body[i]; const node = body[i];
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const all_hoistable = node.declarations.every( const all_hoistable = node.declarations.every((d) => {
/** @param {any} d */ (d) => {
if (!d.init) return false; if (!d.init) return false;
if (d.init.type !== 'Literal') return false; if (d.init.type !== 'Literal') return false;
// everything except const values can be changed by e.g. svelte devtools // everything except const values can be changed by e.g. svelte devtools
@ -1359,17 +1305,14 @@ export default class Component {
return false; return false;
} }
return true; return true;
} });
);
if (all_hoistable) { if (all_hoistable) {
node.declarations.forEach( node.declarations.forEach((d) => {
/** @param {any} d */ (d) => {
const variable = this.var_lookup.get( const variable = this.var_lookup.get(
/** @type {import('estree').Identifier} */ (d.id).name /** @type {import('estree').Identifier} */ (d.id).name
); );
variable.hoistable = true; variable.hoistable = true;
} });
);
hoistable_nodes.add(node); hoistable_nodes.add(node);
body.splice(i--, 1); body.splice(i--, 1);
this.fully_hoisted.push(node); this.fully_hoisted.push(node);
@ -1401,10 +1344,7 @@ export default class Component {
// handle cycles // handle cycles
walking.add(fn_declaration); walking.add(fn_declaration);
walk(fn_declaration, { walk(fn_declaration, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (!hoistable) return this.skip(); if (!hoistable) return this.skip();
if (map.has(node)) { if (map.has(node)) {
@ -1488,8 +1428,7 @@ export default class Component {
* }>} * }>}
*/ */
const unsorted_reactive_declarations = []; const unsorted_reactive_declarations = [];
this.ast.instance.content.body.forEach( this.ast.instance.content.body.forEach((node) => {
/** @param {any} node */ (node) => {
const ignores = extract_svelte_ignore_from_comments(node); const ignores = extract_svelte_ignore_from_comments(node);
if (ignores.length) this.push_ignores(ignores); if (ignores.length) this.push_ignores(ignores);
if (node.type === 'LabeledStatement' && node.label.name === '$') { if (node.type === 'LabeledStatement' && node.label.name === '$') {
@ -1502,10 +1441,7 @@ export default class Component {
const { declarations: outset_scope_decalarations } = this.instance_scope; const { declarations: outset_scope_decalarations } = this.instance_scope;
const map = this.instance_scope_map; const map = this.instance_scope_map;
walk(node.body, { walk(node.body, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (node.type === 'VariableDeclaration' && node.kind === 'var') { if (node.type === 'VariableDeclaration' && node.kind === 'var') {
const is_var_in_outset = node.declarations.some( const is_var_in_outset = node.declarations.some(
@ -1532,12 +1468,10 @@ export default class Component {
} }
if (node.type === 'AssignmentExpression') { if (node.type === 'AssignmentExpression') {
const left = get_object(node.left); const left = get_object(node.left);
extract_identifiers(left).forEach( extract_identifiers(left).forEach((node) => {
/** @param {any} node */ (node) => {
assignee_nodes.add(node); assignee_nodes.add(node);
assignees.add(node.name); assignees.add(node.name);
} });
);
if (node.operator !== '=') { if (node.operator !== '=') {
dependencies.add(left.name); dependencies.add(left.name);
} }
@ -1563,8 +1497,7 @@ export default class Component {
module_dependencies.add(name); module_dependencies.add(name);
} }
} }
const is_writable_or_mutated = const is_writable_or_mutated = variable && (variable.writable || variable.mutated);
variable && (variable.writable || variable.mutated);
if ( if (
should_add_as_dependency && should_add_as_dependency &&
(!owner || owner === component.instance_scope) && (!owner || owner === component.instance_scope) &&
@ -1603,44 +1536,29 @@ export default class Component {
}); });
} }
if (ignores.length) this.pop_ignores(); if (ignores.length) this.pop_ignores();
} });
);
const lookup = new Map(); const lookup = new Map();
unsorted_reactive_declarations.forEach( unsorted_reactive_declarations.forEach((declaration) => {
/** @param {any} declaration */ (declaration) => { declaration.assignees.forEach((name) => {
declaration.assignees.forEach(
/** @param {any} name */ (name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
// TODO warn or error if a name is assigned to in // TODO warn or error if a name is assigned to in
// multiple reactive declarations? // multiple reactive declarations?
lookup.get(name).push(declaration); lookup.get(name).push(declaration);
} });
); });
}
);
const cycle = check_graph_for_cycles( const cycle = check_graph_for_cycles(
unsorted_reactive_declarations.reduce( unsorted_reactive_declarations.reduce((acc, declaration) => {
/** declaration.assignees.forEach((v) => {
* @param {any} acc declaration.dependencies.forEach((w) => {
* @param {any} declaration
*/ (acc, declaration) => {
declaration.assignees.forEach(
/** @param {any} v */ (v) => {
declaration.dependencies.forEach(
/** @param {any} w */ (w) => {
if (!declaration.assignees.has(w)) { if (!declaration.assignees.has(w)) {
acc.push([v, w]); acc.push([v, w]);
} }
} });
); });
}
);
return acc; return acc;
}, }, [])
[]
)
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const declarationList = lookup.get(cycle[0]); const declarationList = lookup.get(cycle[0]);
@ -1651,15 +1569,13 @@ export default class Component {
/** @param {any} declaration */ /** @param {any} declaration */
const add_declaration = (declaration) => { const add_declaration = (declaration) => {
if (this.reactive_declarations.includes(declaration)) return; if (this.reactive_declarations.includes(declaration)) return;
declaration.dependencies.forEach( declaration.dependencies.forEach((name) => {
/** @param {any} name */ (name) => {
if (declaration.assignees.has(name)) return; if (declaration.assignees.has(name)) return;
const earlier_declarations = lookup.get(name); const earlier_declarations = lookup.get(name);
if (earlier_declarations) { if (earlier_declarations) {
earlier_declarations.forEach(add_declaration); earlier_declarations.forEach(add_declaration);
} }
} });
);
this.reactive_declarations.push(declaration); this.reactive_declarations.push(declaration);
}; };
unsorted_reactive_declarations.forEach(add_declaration); unsorted_reactive_declarations.forEach(add_declaration);
@ -1747,7 +1663,7 @@ function process_component_options(component, nodes) {
preserveWhitespace: !!component.compile_options.preserveWhitespace, preserveWhitespace: !!component.compile_options.preserveWhitespace,
namespace: component.compile_options.namespace namespace: component.compile_options.namespace
}; };
const node = nodes.find(/** @param {any} node */ (node) => node.name === 'svelte:options'); const node = nodes.find((node) => node.name === 'svelte:options');
/** /**
* @param {any} attribute * @param {any} attribute
@ -1767,8 +1683,7 @@ function process_component_options(component, nodes) {
return chunk.expression.value; return chunk.expression.value;
} }
if (node) { if (node) {
node.attributes.forEach( node.attributes.forEach((attribute) => {
/** @param {any} attribute */ (attribute) => {
if (attribute.type === 'Attribute') { if (attribute.type === 'Attribute') {
const { name } = attribute; const { name } = attribute;
@ -1809,18 +1724,13 @@ function process_component_options(component, nodes) {
} else if (value[0].expression.type !== 'ObjectExpression') { } else if (value[0].expression.type !== 'ObjectExpression') {
return component.error(attribute, compiler_errors.invalid_customElement_attribute); return component.error(attribute, compiler_errors.invalid_customElement_attribute);
} }
const tag = value[0].expression.properties.find( const tag = value[0].expression.properties.find((prop) => prop.key.name === 'tag');
/** @param {any} prop */ (prop) => prop.key.name === 'tag'
);
if (tag) { if (tag) {
parse_tag(tag, tag.value?.value); parse_tag(tag, tag.value?.value);
} else { } else {
return component.error(attribute, compiler_errors.invalid_customElement_attribute); return component.error(attribute, compiler_errors.invalid_customElement_attribute);
} }
const props = value[0].expression.properties.find( const props = value[0].expression.properties.find((prop) => prop.key.name === 'props');
/** @param {any} prop */
(prop) => prop.key.name === 'props'
);
if (props) { if (props) {
const error = () => const error = () =>
component.error(attribute, compiler_errors.invalid_props_attribute); component.error(attribute, compiler_errors.invalid_props_attribute);
@ -1828,9 +1738,8 @@ function process_component_options(component, nodes) {
return error(); return error();
} }
component_options.customElement.props = {}; component_options.customElement.props = {};
for (const property of /** @type {import('estree').ObjectExpression} */ ( for (const property of /** @type {import('estree').ObjectExpression} */ (props.value)
props.value .properties) {
).properties) {
if ( if (
property.type !== 'Property' || property.type !== 'Property' ||
property.computed || property.computed ||
@ -1866,7 +1775,6 @@ function process_component_options(component, nodes) {
} }
} }
const shadow = value[0].expression.properties.find( const shadow = value[0].expression.properties.find(
/** @param {any} prop */
(prop) => prop.key.name === 'shadow' (prop) => prop.key.name === 'shadow'
); );
if (shadow) { if (shadow) {
@ -1912,8 +1820,7 @@ function process_component_options(component, nodes) {
} else { } else {
return component.error(attribute, compiler_errors.invalid_options_attribute); return component.error(attribute, compiler_errors.invalid_options_attribute);
} }
} });
);
} }
return component_options; return component_options;
} }

@ -23,7 +23,7 @@ export default {
}, },
module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({ module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({
code: 'module-script-reactive-declaration', code: 'module-script-reactive-declaration',
message: `${names.map(/** @param {any} name */ (name) => `"${name}"`).join(', ')} ${ message: `${names.map((name) => `"${name}"`).join(', ')} ${
names.length > 1 ? 'are' : 'is' names.length > 1 ? 'are' : 'is'
} declared in a module script and will not be reactive` } declared in a module script and will not be reactive`
}), }),
@ -175,7 +175,7 @@ export default {
*/ (role, props) => ({ */ (role, props) => ({
code: 'a11y-role-has-required-aria-props', code: 'a11y-role-has-required-aria-props',
message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props message: `A11y: Elements with the ARIA role "${role}" must have the following attributes defined: ${props
.map(/** @param {any} name */ (name) => `"${name}"`) .map((name) => `"${name}"`)
.join(', ')}` .join(', ')}`
}), }),
a11y_role_supports_aria_props: /** a11y_role_supports_aria_props: /**

@ -23,18 +23,8 @@ export default function create_module(
exports_from exports_from
) { ) {
const internal_path = `${sveltePath}/internal`; const internal_path = `${sveltePath}/internal`;
helpers.sort( helpers.sort((a, b) => (a.name < b.name ? -1 : 1));
/** globals.sort((a, b) => (a.name < b.name ? -1 : 1));
* @param {any} a
* @param {any} b
*/ (a, b) => (a.name < b.name ? -1 : 1)
);
globals.sort(
/**
* @param {any} a
* @param {any} b
*/ (a, b) => (a.name < b.name ? -1 : 1)
);
return esm( return esm(
program, program,
name, name,
@ -73,8 +63,7 @@ function get_internal_globals(globals, helpers) {
type: 'VariableDeclarator', type: 'VariableDeclarator',
id: { id: {
type: 'ObjectPattern', type: 'ObjectPattern',
properties: globals.map( properties: globals.map((g) => ({
/** @param {any} g */ (g) => ({
type: 'Property', type: 'Property',
method: false, method: false,
shorthand: false, shorthand: false,
@ -82,10 +71,9 @@ function get_internal_globals(globals, helpers) {
key: { type: 'Identifier', name: g.name }, key: { type: 'Identifier', name: g.name },
value: g.alias, value: g.alias,
kind: 'init' kind: 'init'
}) }))
)
}, },
init: helpers.find(/** @param {any}params_0 */ ({ name }) => name === 'globals').alias init: helpers.find(({ name }) => name === 'globals').alias
} }
] ]
} }
@ -118,13 +106,11 @@ function esm(
) { ) {
const import_declaration = { const import_declaration = {
type: 'ImportDeclaration', type: 'ImportDeclaration',
specifiers: helpers.map( specifiers: helpers.map((h) => ({
/** @param {any} h */ (h) => ({
type: 'ImportSpecifier', type: 'ImportSpecifier',
local: h.alias, local: h.alias,
imported: { type: 'Identifier', name: h.name } imported: { type: 'Identifier', name: h.name }
}) })),
),
source: { type: 'Literal', value: internal_path } source: { type: 'Literal', value: internal_path }
}; };
const internal_globals = get_internal_globals(globals, helpers); const internal_globals = get_internal_globals(globals, helpers);
@ -142,13 +128,11 @@ function esm(
exports_from.forEach(rewrite_import); exports_from.forEach(rewrite_import);
const exports = module_exports.length > 0 && { const exports = module_exports.length > 0 && {
type: 'ExportNamedDeclaration', type: 'ExportNamedDeclaration',
specifiers: module_exports.map( specifiers: module_exports.map((x) => ({
/** @param {any} x */ (x) => ({
type: 'Specifier', type: 'Specifier',
local: { type: 'Identifier', name: x.name }, local: { type: 'Identifier', name: x.name },
exported: { type: 'Identifier', name: x.as } exported: { type: 'Identifier', name: x.as }
}) }))
)
}; };
program.body = b` program.body = b`
/* ${banner} */ /* ${banner} */

@ -57,16 +57,14 @@ function validate_options(options, warnings) {
} }
const { name, filename, loopGuardTimeout, dev, namespace, css } = options; const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
Object.keys(options).forEach( Object.keys(options).forEach((key) => {
/** @param {any} key */ (key) => {
if (!valid_options.includes(key)) { if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options); const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`; let message = `Unrecognized option '${key}'`;
if (match) message += ` (did you mean '${match}'?)`; if (match) message += ` (did you mean '${match}'?)`;
throw new Error(message); throw new Error(message);
} }
} });
);
if (name && !regex_valid_identifier.test(name)) { if (name && !regex_valid_identifier.test(name)) {
throw new Error(`options.name must be a valid identifier (got '${name}')`); throw new Error(`options.name must be a valid identifier (got '${name}')`);
} }

@ -56,15 +56,13 @@ export default class Attribute extends Node {
this.dependencies = new Set(); this.dependencies = new Set();
this.chunks = this.is_true this.chunks = this.is_true
? [] ? []
: info.value.map( : info.value.map((node) => {
/** @param {any} node */ (node) => {
if (node.type === 'Text') return node; if (node.type === 'Text') return node;
this.is_static = false; this.is_static = false;
const expression = new Expression(component, this, scope, node.expression); const expression = new Expression(component, this, scope, node.expression);
add_to_set(this.dependencies, expression.dependencies); add_to_set(this.dependencies, expression.dependencies);
return expression; return expression;
} });
);
} }
if (this.dependencies.size > 0) { if (this.dependencies.size > 0) {
@ -88,13 +86,11 @@ export default class Attribute extends Node {
/** @type {Set<string>} */ /** @type {Set<string>} */
const dependencies = new Set(); const dependencies = new Set();
this.chunks.forEach( this.chunks.forEach((chunk) => {
/** @param {any} chunk */ (chunk) => {
if (chunk.type === 'Expression') { if (chunk.type === 'Expression') {
add_to_set(dependencies, chunk.dynamic_dependencies()); add_to_set(dependencies, chunk.dynamic_dependencies());
} }
} });
);
return Array.from(dependencies); return Array.from(dependencies);
} }
@ -114,12 +110,7 @@ export default class Attribute extends Node {
/** @param {any} chunk */ (chunk) => /** @param {any} chunk */ (chunk) =>
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block) chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
) )
.reduce( .reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
/**
* @param {any} lhs
* @param {any} rhs
*/ (lhs, rhs) => x`${lhs} + ${rhs}`
);
if (this.chunks[0].type !== 'Text') { if (this.chunks[0].type !== 'Text') {
expression = x`"" + ${expression}`; expression = x`"" + ${expression}`;
} }

@ -54,8 +54,8 @@ export default class Binding extends Node {
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = clone(info.expression); this.raw_expression = clone(info.expression);
const { name } = get_object(this.expression.node); const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some( this.is_contextual = Array.from(this.expression.references).some((name) =>
/** @param {any} name */ (name) => scope.names.has(name) scope.names.has(name)
); );
if (this.is_contextual) this.validate_binding_rest_properties(scope); if (this.is_contextual) this.validate_binding_rest_properties(scope);
// make sure we track this as a mutable ref // make sure we track this as a mutable ref
@ -70,14 +70,12 @@ export default class Binding extends Node {
if (scope.is_const(name)) { if (scope.is_const(name)) {
component.error(this, compiler_errors.invalid_binding_const); component.error(this, compiler_errors.invalid_binding_const);
} }
scope.dependencies_for_name.get(name).forEach( scope.dependencies_for_name.get(name).forEach((name) => {
/** @param {any} name */ (name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) { if (variable) {
variable.mutated = true; variable.mutated = true;
} }
} });
);
} else { } else {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (!variable || variable.global) { if (!variable || variable.global) {
@ -110,8 +108,7 @@ export default class Binding extends Node {
/** @param {import('./shared/TemplateScope.js').default} scope */ /** @param {import('./shared/TemplateScope.js').default} scope */
validate_binding_rest_properties(scope) { validate_binding_rest_properties(scope) {
this.expression.references.forEach( this.expression.references.forEach((name) => {
/** @param {any} name */ (name) => {
const each_block = scope.get_owner(name); const each_block = scope.get_owner(name);
if (each_block && each_block.type === 'EachBlock') { if (each_block && each_block.type === 'EachBlock') {
const rest_node = each_block.context_rest_properties.get(name); const rest_node = each_block.context_rest_properties.get(name);
@ -122,8 +119,7 @@ export default class Binding extends Node {
); );
} }
} }
} });
);
} }
} }

@ -18,8 +18,7 @@ export default class Body extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Action') { } else if (node.type === 'Action') {
@ -27,7 +26,6 @@ export default class Body extends Node {
} else { } else {
// TODO there shouldn't be anything else here... // TODO there shouldn't be anything else here...
} }
} });
);
} }
} }

@ -19,12 +19,10 @@ export default class CatchBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.catch_node) { if (parent.catch_node) {
parent.catch_contexts.forEach( parent.catch_contexts.forEach((context) => {
/** @param {any} context */ (context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
} });
);
} }
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {

@ -54,19 +54,16 @@ export default class ConstTag extends Node {
this.node = info; this.node = info;
this.scope = scope; this.scope = scope;
const { assignees, dependencies } = this; const { assignees, dependencies } = this;
extract_identifiers(info.expression.left).forEach( extract_identifiers(info.expression.left).forEach(({ name }) => {
/** @param {any}params_0 */ ({ name }) => {
assignees.add(name); assignees.add(name);
const owner = this.scope.get_owner(name); const owner = this.scope.get_owner(name);
if (owner === parent) { if (owner === parent) {
component.error(info, compiler_errors.invalid_const_declaration(name)); component.error(info, compiler_errors.invalid_const_declaration(name));
} }
} });
);
walk(info.expression.right, { walk(info.expression.right, {
/** /**
* @param {any} node * @type {import('estree-walker').SyncHandler}
* @param {any} parent
*/ */
enter(node, parent) { enter(node, parent) {
if ( if (
@ -75,7 +72,7 @@ export default class ConstTag extends Node {
/** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent) /** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent)
) )
) { ) {
const identifier = get_object(/** @type {any} */ (node)); const identifier = get_object(node);
const { name } = identifier; const { name } = identifier;
dependencies.add(name); dependencies.add(name);
} }
@ -92,8 +89,7 @@ export default class ConstTag extends Node {
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.expression = new Expression(this.component, this, this.scope, this.node.expression.right); this.expression = new Expression(this.component, this, this.scope, this.node.expression.right);
this.contexts.forEach( this.contexts.forEach((context) => {
/** @param {any} context */ (context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
const owner = this.scope.get_owner(context.key.name); const owner = this.scope.get_owner(context.key.name);
if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) {
@ -103,7 +99,6 @@ export default class ConstTag extends Node {
); );
} }
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
} });
);
} }
} }

@ -28,8 +28,7 @@ export default class Document extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') { } else if (node.type === 'Binding') {
@ -61,15 +60,14 @@ export default class Document extends Node {
} else { } else {
// TODO there shouldn't be anything else here... // TODO there shouldn't be anything else here...
} }
} });
);
this.validate(); this.validate();
} }
/** @private */ /** @private */
validate() { validate() {
const handlers_map = new Set(); const handlers_map = new Set();
this.handlers.forEach(/** @param {any} handler */ (handler) => handlers_map.add(handler.name)); this.handlers.forEach((handler) => handlers_map.add(handler.name));
if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) { if (handlers_map.has('mouseenter') || handlers_map.has('mouseleave')) {
this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document); this.component.warn(this, compiler_warnings.avoid_mouse_events_on_document);
} }

@ -71,12 +71,10 @@ export default class EachBlock extends AbstractBlock {
component, component,
context_rest_properties: this.context_rest_properties context_rest_properties: this.context_rest_properties
}); });
this.contexts.forEach( this.contexts.forEach((context) => {
/** @param {any} context */ (context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, this.expression.dependencies, this); this.scope.add(context.key.name, this.expression.dependencies, this);
} });
);
if (this.index) { if (this.index) {
// index can only change if this is a keyed each block // index can only change if this is a keyed each block
const dependencies = info.key ? this.expression.dependencies : new Set([]); const dependencies = info.key ? this.expression.dependencies : new Set([]);
@ -86,13 +84,10 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false; this.has_animation = false;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) { if (this.has_animation) {
this.children = this.children.filter( this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
/** @param {any} child */ (child) => !isEmptyNode(child) && !isCommentNode(child)
);
if (this.children.length !== 1) { if (this.children.length !== 1) {
const child = this.children.find( const child = this.children.find(
/** @param {any} child */ (child) => (child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
!!(/** @type {import('./Element.js').default} */ (child).animation)
); );
component.error( component.error(
/** @type {import('./Element.js').default} */ (child).animation, /** @type {import('./Element.js').default} */ (child).animation,

@ -314,19 +314,14 @@ function is_valid_aria_attribute_value(schema, value) {
case 'idlist': // if list of ids, split each case 'idlist': // if list of ids, split each
return ( return (
typeof value === 'string' && typeof value === 'string' &&
value value.split(regex_any_repeated_whitespaces).every((id) => typeof id === 'string')
.split(regex_any_repeated_whitespaces)
.every(/** @param {any} id */ (id) => typeof id === 'string')
); );
case 'tokenlist': // if list of tokens, split each case 'tokenlist': // if list of tokens, split each
return ( return (
typeof value === 'string' && typeof value === 'string' &&
value value
.split(regex_any_repeated_whitespaces) .split(regex_any_repeated_whitespaces)
.every( .every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1)
/** @param {any} token */ (token) =>
(schema.values || []).indexOf(token.toLowerCase()) > -1
)
); );
default: default:
return false; return false;
@ -393,7 +388,7 @@ export default class Element extends Node {
* @param {import('../Component.js').default} component * @param {import('../Component.js').default} component
* @param {import('./shared/Node.js').default} parent * @param {import('./shared/Node.js').default} parent
* @param {import('./shared/TemplateScope.js').default} scope * @param {import('./shared/TemplateScope.js').default} scope
* @param {any} info undefined * @param {any} info
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
@ -435,9 +430,7 @@ export default class Element extends Node {
} }
if (this.name === 'textarea') { if (this.name === 'textarea') {
if (info.children.length > 0) { if (info.children.length > 0) {
const value_attribute = info.attributes.find( const value_attribute = info.attributes.find((node) => node.name === 'value');
/** @param {any} node */ (node) => node.name === 'value'
);
if (value_attribute) { if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value); component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return; return;
@ -456,9 +449,7 @@ export default class Element extends Node {
// Special case — treat these the same way: // Special case — treat these the same way:
// <option>{foo}</option> // <option>{foo}</option>
// <option value={foo}>{foo}</option> // <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find( const value_attribute = info.attributes.find((attribute) => attribute.name === 'value');
/** @param {any} attribute */ (attribute) => attribute.name === 'value'
);
if (!value_attribute) { if (!value_attribute) {
info.attributes.push({ info.attributes.push({
type: 'Attribute', type: 'Attribute',
@ -469,20 +460,14 @@ export default class Element extends Node {
} }
} }
} }
const has_let = info.attributes.some(/** @param {any} node */ (node) => node.type === 'Let'); const has_let = info.attributes.some((node) => node.type === 'Let');
if (has_let) { if (has_let) {
scope = scope.child(); scope = scope.child();
} }
// Binding relies on Attribute, defer its evaluation // Binding relies on Attribute, defer its evaluation
const order = ['Binding']; // everything else is -1 const order = ['Binding']; // everything else is -1
info.attributes.sort( info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
/** info.attributes.forEach((node) => {
* @param {any} a
* @param {any} b
*/ (a, b) => order.indexOf(a.type) - order.indexOf(b.type)
);
info.attributes.forEach(
/** @param {any} node */ (node) => {
switch (node.type) { switch (node.type) {
case 'Action': case 'Action':
this.actions.push(new Action(component, this, scope, node)); this.actions.push(new Action(component, this, scope, node));
@ -509,11 +494,9 @@ export default class Element extends Node {
const l = new Let(component, this, scope, node); const l = new Let(component, this, scope, node);
this.lets.push(l); this.lets.push(l);
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach( l.names.forEach((name) => {
/** @param {any} name */ (name) => {
scope.add(name, dependencies, this); scope.add(name, dependencies, this);
} });
);
break; break;
} }
case 'Transition': { case 'Transition': {
@ -528,8 +511,7 @@ export default class Element extends Node {
default: default:
throw new Error(`Not implemented: ${node.type}`); throw new Error(`Not implemented: ${node.type}`);
} }
} });
);
this.scope = scope; this.scope = scope;
this.children = map_children(component, this, this.scope, info.children); this.children = map_children(component, this, this.scope, info.children);
this.validate(); this.validate();
@ -578,8 +560,7 @@ export default class Element extends Node {
} }
validate_attributes() { validate_attributes() {
const { component, parent } = this; const { component, parent } = this;
this.attributes.forEach( this.attributes.forEach((attribute) => {
/** @param {any} attribute */ (attribute) => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
// Errors // Errors
@ -614,21 +595,15 @@ export default class Element extends Node {
); );
} }
} }
} });
);
} }
validate_attributes_a11y() { validate_attributes_a11y() {
const { component, attributes, handlers } = this; const { component, attributes, handlers } = this;
const attribute_map = new Map(); const attribute_map = new Map();
const handlers_map = new Map(); const handlers_map = new Map();
attributes.forEach( attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
/** @param {any} attribute */ (attribute) => attribute_map.set(attribute.name, attribute) handlers.forEach((handler) => handlers_map.set(handler.name, handler));
); attributes.forEach((attribute) => {
handlers.forEach(
/** @param {any} handler */ (handler) => handlers_map.set(handler.name, handler)
);
attributes.forEach(
/** @param {any} attribute */ (attribute) => {
if (attribute.is_spread) return; if (attribute.is_spread) return;
const name = attribute.name.toLowerCase(); const name = attribute.name.toLowerCase();
// aria-props // aria-props
@ -688,10 +663,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role)); component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role));
} else if (current_role && !aria_role_set.has(current_role)) { } else if (current_role && !aria_role_set.has(current_role)) {
const match = fuzzymatch(current_role, aria_roles); const match = fuzzymatch(current_role, aria_roles);
component.warn( component.warn(attribute, compiler_warnings.a11y_unknown_role(current_role, match));
attribute,
compiler_warnings.a11y_unknown_role(current_role, match)
);
} }
// no-redundant-roles // no-redundant-roles
if ( if (
@ -699,10 +671,7 @@ export default class Element extends Node {
// <ul role="list"> is ok because CSS list-style:none removes the semantics and this is a way to bring them back // <ul role="list"> is ok because CSS list-style:none removes the semantics and this is a way to bring them back
!['ul', 'ol', 'li'].includes(this.name) !['ul', 'ol', 'li'].includes(this.name)
) { ) {
component.warn( component.warn(attribute, compiler_warnings.a11y_no_redundant_roles(current_role));
attribute,
compiler_warnings.a11y_no_redundant_roles(current_role)
);
} }
// Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles. // Footers and headers are special cases, and should not have redundant roles unless they are the children of sections or articles.
const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']); const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']);
@ -725,8 +694,7 @@ export default class Element extends Node {
if (role) { if (role) {
const required_role_props = Object.keys(role.requiredProps); const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some( const has_missing_props = required_role_props.some(
/** @param {any} prop */ (prop) => !attributes.find((a) => a.name === prop)
(prop) => !attributes.find(/** @param {any} a */ (a) => a.name === prop)
); );
if (has_missing_props) { if (has_missing_props) {
component.warn( component.warn(
@ -748,8 +716,7 @@ export default class Element extends Node {
is_static_element(this.name, attribute_map) && is_static_element(this.name, attribute_map) &&
!attribute_map.get('tabindex') !attribute_map.get('tabindex')
) { ) {
const has_interactive_handlers = handlers.some( const has_interactive_handlers = handlers.some((handler) =>
/** @param {any} handler */ (handler) =>
a11y_interactive_handlers.has(handler.name) a11y_interactive_handlers.has(handler.name)
); );
if (has_interactive_handlers) { if (has_interactive_handlers) {
@ -812,8 +779,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_positive_tabindex); component.warn(attribute, compiler_warnings.a11y_positive_tabindex);
} }
} }
} });
);
// click-events-have-key-events // click-events-have-key-events
if (handlers_map.has('click')) { if (handlers_map.has('click')) {
const role = attribute_map.get('role'); const role = attribute_map.get('role');
@ -827,7 +793,7 @@ export default class Element extends Node {
!is_hidden_from_screen_reader(this.name, attribute_map) && !is_hidden_from_screen_reader(this.name, attribute_map) &&
(!role || is_non_presentation_role) && (!role || is_non_presentation_role) &&
!is_interactive_element(this.name, attribute_map) && !is_interactive_element(this.name, attribute_map) &&
!this.attributes.find(/** @param {any} attr */ (attr) => attr.is_spread) !this.attributes.find((attr) => attr.is_spread)
) { ) {
const has_key_event = const has_key_event =
handlers_map.has('keydown') || handlers_map.has('keyup') || handlers_map.has('keypress'); handlers_map.has('keydown') || handlers_map.has('keyup') || handlers_map.has('keypress');
@ -857,14 +823,11 @@ export default class Element extends Node {
// role-supports-aria-props // role-supports-aria-props
if (typeof role_value === 'string' && roles.has(role_value)) { if (typeof role_value === 'string' && roles.has(role_value)) {
const { props } = roles.get(role_value); const { props } = roles.get(role_value);
const invalid_aria_props = new Set( const invalid_aria_props = new Set(aria.keys().filter((attribute) => !(attribute in props)));
aria.keys().filter(/** @param {any} attribute */ (attribute) => !(attribute in props))
);
const is_implicit = role_value && role === undefined; const is_implicit = role_value && role === undefined;
attributes attributes
.filter(/** @param {any} prop */ (prop) => prop.type !== 'Spread') .filter((prop) => prop.type !== 'Spread')
.forEach( .forEach((prop) => {
/** @param {any} prop */ (prop) => {
if ( if (
invalid_aria_props.has(/** @type {import('aria-query').ARIAProperty} */ (prop.name)) invalid_aria_props.has(/** @type {import('aria-query').ARIAProperty} */ (prop.name))
) { ) {
@ -878,8 +841,7 @@ export default class Element extends Node {
) )
); );
} }
} });
);
} }
// no-noninteractive-element-interactions // no-noninteractive-element-interactions
if ( if (
@ -890,8 +852,7 @@ export default class Element extends Node {
is_non_interactive_roles(role_static_value)) || is_non_interactive_roles(role_static_value)) ||
(is_non_interactive_element(this.name, attribute_map) && !role)) (is_non_interactive_element(this.name, attribute_map) && !role))
) { ) {
const has_interactive_handlers = handlers.some( const has_interactive_handlers = handlers.some((handler) =>
/** @param {any} handler */ (handler) =>
a11y_recommended_interactive_handlers.has(handler.name) a11y_recommended_interactive_handlers.has(handler.name)
); );
if (has_interactive_handlers) { if (has_interactive_handlers) {
@ -914,11 +875,8 @@ export default class Element extends Node {
!is_abstract_role(role_static_value) !is_abstract_role(role_static_value)
) { ) {
const interactive_handlers = handlers const interactive_handlers = handlers
.map(/** @param {any} handler */ (handler) => handler.name) .map((handler) => handler.name)
.filter( .filter((handlerName) => a11y_interactive_handlers.has(handlerName));
/** @param {any} handlerName */ (handlerName) =>
a11y_interactive_handlers.has(handlerName)
);
if (interactive_handlers.length > 0) { if (interactive_handlers.length > 0) {
component.warn( component.warn(
this, this,
@ -931,12 +889,8 @@ export default class Element extends Node {
const { component, attributes, handlers } = this; const { component, attributes, handlers } = this;
const attribute_map = new Map(); const attribute_map = new Map();
const handlers_map = new Map(); const handlers_map = new Map();
attributes.forEach( attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
/** @param {any} attribute */ (attribute) => attribute_map.set(attribute.name, attribute) handlers.forEach((handler) => handlers_map.set(handler.name, handler));
);
handlers.forEach(
/** @param {any} handler */ (handler) => handlers_map.set(handler.name, handler)
);
if (this.name === 'a') { if (this.name === 'a') {
const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
const id_attribute = attribute_map.get('id'); const id_attribute = attribute_map.get('id');
@ -995,9 +949,7 @@ export default class Element extends Node {
} else { } else {
const required_attributes = a11y_required_attributes[this.name]; const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) { if (required_attributes) {
const has_attribute = required_attributes.some( const has_attribute = required_attributes.some((name) => attribute_map.has(name));
/** @param {any} name */ (name) => attribute_map.has(name)
);
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes); should_have_attribute(this, required_attributes);
} }
@ -1007,9 +959,7 @@ export default class Element extends Node {
const type = attribute_map.get('type'); const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') { if (type && type.get_static_value() === 'image') {
const required_attributes = ['alt', 'aria-label', 'aria-labelledby']; const required_attributes = ['alt', 'aria-label', 'aria-labelledby'];
const has_attribute = required_attributes.some( const has_attribute = required_attributes.some((name) => attribute_map.has(name));
/** @param {any} name */ (name) => attribute_map.has(name)
);
if (!has_attribute) { if (!has_attribute) {
should_have_attribute(this, required_attributes, 'input type="image"'); should_have_attribute(this, required_attributes, 'input type="image"');
} }
@ -1043,7 +993,6 @@ export default class Element extends Node {
const has_input_child = (children) => { const has_input_child = (children) => {
if ( if (
children.some( children.some(
/** @param {any} child */
(child) => (child) =>
child instanceof Element && (a11y_labelable.has(child.name) || child.name === 'slot') child instanceof Element && (a11y_labelable.has(child.name) || child.name === 'slot')
) )
@ -1074,7 +1023,6 @@ export default class Element extends Node {
const track = this.children.find(/** @param {Element} i */ (i) => i.name === 'track'); const track = this.children.find(/** @param {Element} i */ (i) => i.name === 'track');
if (track) { if (track) {
has_caption = track.attributes.find( has_caption = track.attributes.find(
/** @param {any} a */
(a) => a.name === 'kind' && a.get_static_value() === 'captions' (a) => a.name === 'kind' && a.get_static_value() === 'captions'
); );
} }
@ -1104,15 +1052,13 @@ export default class Element extends Node {
} }
} }
if (this.name === 'figure') { if (this.name === 'figure') {
const children = this.children.filter( const children = this.children.filter((node) => {
/** @param {any} node */ (node) => {
if (node.type === 'Comment') return false; if (node.type === 'Comment') return false;
if (node.type === 'Text') return regex_non_whitespace_character.test(node.data); if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true; return true;
} });
);
const index = children.findIndex( const index = children.findIndex(
/** @param {any} child */ (child) => /** @type {Element} */ (child).name === 'figcaption' (child) => /** @type {Element} */ (child).name === 'figcaption'
); );
if (index !== -1 && index !== 0 && index !== children.length - 1) { if (index !== -1 && index !== 0 && index !== children.length - 1) {
component.warn(children[index], compiler_warnings.a11y_structure_first_or_last); component.warn(children[index], compiler_warnings.a11y_structure_first_or_last);
@ -1129,16 +1075,11 @@ export default class Element extends Node {
} }
} }
validate_bindings_foreign() { validate_bindings_foreign() {
this.bindings.forEach( this.bindings.forEach((binding) => {
/** @param {any} binding */ (binding) => {
if (binding.name !== 'this') { if (binding.name !== 'this') {
return this.component.error( return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name));
binding,
compiler_errors.invalid_binding_foreign(binding.name)
);
} }
} });
);
} }
validate_bindings() { validate_bindings() {
const { component } = this; const { component } = this;
@ -1157,8 +1098,7 @@ export default class Element extends Node {
} }
return value; return value;
}; };
this.bindings.forEach( this.bindings.forEach((binding) => {
/** @param {any} binding */ (binding) => {
const { name } = binding; const { name } = binding;
if (name === 'value') { if (name === 'value') {
if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') { if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') {
@ -1300,17 +1240,12 @@ export default class Element extends Node {
} else if (name !== 'this' && !regex_box_size.test(name)) { } else if (name !== 'this' && !regex_box_size.test(name)) {
return component.error(binding, compiler_errors.invalid_binding(binding.name)); return component.error(binding, compiler_errors.invalid_binding(binding.name));
} }
} });
);
} }
validate_content() { validate_content() {
if (!a11y_required_content.has(this.name)) return; if (!a11y_required_content.has(this.name)) return;
if (this.contains_a11y_label) return; if (this.contains_a11y_label) return;
if ( if (this.bindings.some((binding) => ['textContent', 'innerHTML'].includes(binding.name)))
this.bindings.some(
/** @param {any} binding */ (binding) => ['textContent', 'innerHTML'].includes(binding.name)
)
)
return; return;
if (this.children.length === 0) { if (this.children.length === 0) {
this.component.warn(this, compiler_warnings.a11y_missing_content(this.name)); this.component.warn(this, compiler_warnings.a11y_missing_content(this.name));
@ -1318,8 +1253,7 @@ export default class Element extends Node {
} }
validate_event_handlers() { validate_event_handlers() {
const { component } = this; const { component } = this;
this.handlers.forEach( this.handlers.forEach((handler) => {
/** @param {any} handler */ (handler) => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) { if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
return component.error( return component.error(
handler, handler,
@ -1332,8 +1266,7 @@ export default class Element extends Node {
compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive') compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive')
); );
} }
handler.modifiers.forEach( handler.modifiers.forEach((modifier) => {
/** @param {any} modifier */ (modifier) => {
if (!valid_modifiers.has(modifier)) { if (!valid_modifiers.has(modifier)) {
return component.error( return component.error(
handler, handler,
@ -1349,19 +1282,12 @@ export default class Element extends Node {
component.warn(handler, compiler_warnings.redundant_event_modifier_passive); component.warn(handler, compiler_warnings.redundant_event_modifier_passive);
} }
} }
if ( if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) {
component.compile_options.legacy &&
(modifier === 'once' || modifier === 'passive')
) {
// TODO this could be supported, but it would need a few changes to // TODO this could be supported, but it would need a few changes to
// how event listeners work // how event listeners work
return component.error( return component.error(handler, compiler_errors.invalid_event_modifier_legacy(modifier));
handler,
compiler_errors.invalid_event_modifier_legacy(modifier)
);
}
} }
); });
if ( if (
passive_events.has(handler.name) && passive_events.has(handler.name) &&
handler.can_make_passive && handler.can_make_passive &&
@ -1371,19 +1297,18 @@ export default class Element extends Node {
// touch/wheel events should be passive by default // touch/wheel events should be passive by default
handler.modifiers.add('passive'); handler.modifiers.add('passive');
} }
} });
);
} }
is_media_node() { is_media_node() {
return this.name === 'audio' || this.name === 'video'; return this.name === 'audio' || this.name === 'video';
} }
add_css_class() { add_css_class() {
if (this.attributes.some(/** @param {any} attr */ (attr) => attr.is_spread)) { if (this.attributes.some((attr) => attr.is_spread)) {
this.needs_manual_style_scoping = true; this.needs_manual_style_scoping = true;
return; return;
} }
const { id } = this.component.stylesheet; const { id } = this.component.stylesheet;
const class_attribute = this.attributes.find(/** @param {any} a */ (a) => a.name === 'class'); const class_attribute = this.attributes.find((a) => a.name === 'class');
if (class_attribute && !class_attribute.is_true) { if (class_attribute && !class_attribute.is_true) {
if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
/** @type {import('./Text.js').default} */ (class_attribute.chunks[0]).data += ` ${id}`; /** @type {import('./Text.js').default} */ (class_attribute.chunks[0]).data += ` ${id}`;
@ -1418,23 +1343,14 @@ export default class Element extends Node {
} }
get slot_template_name() { get slot_template_name() {
return /** @type {string} */ ( return /** @type {string} */ (
this.attributes this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
.get_static_value()
); );
} }
optimise() { optimise() {
attributes_to_compact_whitespace.forEach( attributes_to_compact_whitespace.forEach((attribute_name) => {
/** @param {any} attribute_name */ (attribute_name) => { const attribute = this.attributes.find((a) => a.name === attribute_name);
const attribute = this.attributes.find(
/** @param {any} a */ (a) => a.name === attribute_name
);
if (attribute && !attribute.is_true) { if (attribute && !attribute.is_true) {
attribute.chunks.forEach( attribute.chunks.forEach((chunk, index) => {
/**
* @param {any} chunk
* @param {any} index
*/ (chunk, index) => {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
let data = chunk.data.replace(regex_any_repeated_whitespaces, ' '); let data = chunk.data.replace(regex_any_repeated_whitespaces, ' ');
if (index === 0) { if (index === 0) {
@ -1444,18 +1360,14 @@ export default class Element extends Node {
} }
chunk.data = data; chunk.data = data;
} }
});
} }
); });
}
}
);
} }
get can_use_textcontent() { get can_use_textcontent() {
return ( return (
this.is_static_content && this.is_static_content &&
this.children.every( this.children.every((node) => node.type === 'Text' || node.type === 'MustacheTag')
/** @param {any} node */ (node) => node.type === 'Text' || node.type === 'MustacheTag'
)
); );
} }
get can_optimise_to_html_string() { get can_optimise_to_html_string() {

@ -48,7 +48,6 @@ export default class EventHandler extends Node {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
// for `const handleClick = () => {...}`, we want the [arrow] function expression node // for `const handleClick = () => {...}`, we want the [arrow] function expression node
const declarator = node.declarations.find( const declarator = node.declarations.find(
/** @param {any} d */
(d) => /** @type {import('estree').Identifier} */ (d.id).name === info.expression.name (d) => /** @type {import('estree').Identifier} */ (d.id).name === info.expression.name
); );
node = declarator && declarator.init; node = declarator && declarator.init;

@ -29,11 +29,9 @@ export default class Head extends Node {
component, component,
parent, parent,
scope, scope,
info.children.filter( info.children.filter((child) => {
/** @param {any} child */ (child) => {
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data); return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
} })
)
); );
if (this.children.length > 0) { if (this.children.length > 0) {
this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`; this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;

@ -101,17 +101,13 @@ export default class InlineComponent extends Node {
this.scope = scope; this.scope = scope;
this.handlers.forEach( this.handlers.forEach((handler) => {
/** @param {any} handler */ (handler) => { handler.modifiers.forEach((modifier) => {
handler.modifiers.forEach(
/** @param {any} modifier */ (modifier) => {
if (modifier !== 'once') { if (modifier !== 'once') {
return component.error(handler, compiler_errors.invalid_event_modifier_component); return component.error(handler, compiler_errors.invalid_event_modifier_component);
} }
} });
); });
}
);
const children = []; const children = [];
for (let i = info.children.length - 1; i >= 0; i--) { for (let i = info.children.length - 1; i >= 0; i--) {
const child = info.children[i]; const child = info.children[i];
@ -120,9 +116,7 @@ export default class InlineComponent extends Node {
info.children.splice(i, 1); info.children.splice(i, 1);
} else if ( } else if (
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && (child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
child.attributes.find( child.attributes.find((attribute) => attribute.name === 'slot')
/** @param {any} attribute */ (attribute) => attribute.name === 'slot'
)
) { ) {
const slot_template = { const slot_template = {
start: child.start, start: child.start,
@ -156,7 +150,7 @@ export default class InlineComponent extends Node {
children[children.length - 1].children.unshift(child); children[children.length - 1].children.unshift(child);
} }
} }
if (info.children.some(/** @param {any} node */ (node) => not_whitespace_text(node))) { if (info.children.some((node) => not_whitespace_text(node))) {
children.push({ children.push({
start: info.start, start: info.start,
end: info.end, end: info.end,
@ -182,9 +176,7 @@ export default class InlineComponent extends Node {
} }
get slot_template_name() { get slot_template_name() {
return /** @type {string} */ ( return /** @type {string} */ (
this.attributes this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
.get_static_value()
); );
} }
} }

@ -22,8 +22,7 @@ export default class Slot extends Element {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((attr) => {
/** @param {any} attr */ (attr) => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
return component.error(attr, compiler_errors.invalid_slot_directive); return component.error(attr, compiler_errors.invalid_slot_directive);
} }
@ -37,8 +36,7 @@ export default class Slot extends Element {
} }
} }
this.values.set(attr.name, new Attribute(component, this, scope, attr)); this.values.set(attr.name, new Attribute(component, this, scope, attr));
} });
);
if (!this.slot_name) this.slot_name = 'default'; if (!this.slot_name) this.slot_name = 'default';
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);

@ -34,18 +34,15 @@ export default class SlotTemplate extends Node {
super(component, parent, scope, info); super(component, parent, scope, info);
this.validate_slot_template_placement(); this.validate_slot_template_placement();
scope = scope.child(); scope = scope.child();
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => {
switch (node.type) { switch (node.type) {
case 'Let': { case 'Let': {
const l = new Let(component, this, scope, node); const l = new Let(component, this, scope, node);
this.lets.push(l); this.lets.push(l);
const dependencies = new Set([l.name.name]); const dependencies = new Set([l.name.name]);
l.names.forEach( l.names.forEach((name) => {
/** @param {any} name */ (name) => {
scope.add(name, dependencies, this); scope.add(name, dependencies, this);
} });
);
break; break;
} }
case 'Attribute': { case 'Attribute': {
@ -66,8 +63,7 @@ export default class SlotTemplate extends Node {
default: default:
throw new Error(`Not implemented: ${node.type}`); throw new Error(`Not implemented: ${node.type}`);
} }
} });
);
this.scope = scope; this.scope = scope;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this); [this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
} }

@ -43,12 +43,12 @@ export default class StyleDirective extends Node {
if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) { if (info.value === true || (info.value.length === 1 && info.value[0].type === 'MustacheTag')) {
const identifier = const identifier =
info.value === true info.value === true
? /** @type {any} */ ({ ? {
type: 'Identifier', type: 'Identifier',
start: info.end - info.name.length, start: info.end - info.name.length,
end: info.end, end: info.end,
name: info.name name: info.name
}) }
: info.value[0].expression; : info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier); this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false; this.should_cache = false;

@ -19,12 +19,10 @@ export default class ThenBlock extends AbstractBlock {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
if (parent.then_node) { if (parent.then_node) {
parent.then_contexts.forEach( parent.then_contexts.forEach((context) => {
/** @param {any} context */ (context) => {
if (context.type !== 'DestructuredVariable') return; if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, parent.expression.dependencies, this); this.scope.add(context.key.name, parent.expression.dependencies, this);
} });
);
} }
[this.const_tags, this.children] = get_const_tags(info.children, component, this, parent); [this.const_tags, this.children] = get_const_tags(info.children, component, this, parent);
if (!info.skip) { if (!info.skip) {

@ -23,13 +23,11 @@ export default class Title extends Node {
component.error(info.attributes[0], compiler_errors.illegal_attribute_title); component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
return; return;
} }
info.children.forEach( info.children.forEach((child) => {
/** @param {any} child */ (child) => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') { if (child.type !== 'Text' && child.type !== 'MustacheTag') {
return component.error(child, compiler_errors.illegal_structure_title); return component.error(child, compiler_errors.illegal_structure_title);
} }
} });
);
this.should_cache = this.should_cache =
info.children.length === 1 info.children.length === 1
? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name) ? info.children[0].type !== 'Identifier' || scope.names.has(info.children[0].name)

@ -37,8 +37,7 @@ export default class Window extends Node {
*/ */
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach( info.attributes.forEach((node) => {
/** @param {any} node */ (node) => {
if (node.type === 'EventHandler') { if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node)); this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') { } else if (node.type === 'Binding') {
@ -80,7 +79,6 @@ export default class Window extends Node {
} else { } else {
// TODO there shouldn't be anything else here... // TODO there shouldn't be anything else here...
} }
} });
);
} }
} }

@ -31,10 +31,16 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
references = new Set(); references = new Set();
/** @type {Set<string>} */ /**
* Dependencies declared in the script block
* @type {Set<string>}
*/
dependencies = new Set(); dependencies = new Set();
/** @type {Set<string>} */ /**
* Dependencies declared in the HTML-like template section
* @type {Set<string>}
*/
contextual_dependencies = new Set(); contextual_dependencies = new Set();
/** @type {import('./TemplateScope.js').default} */ /** @type {import('./TemplateScope.js').default} */
@ -82,12 +88,12 @@ export default class Expression {
walk(info, { walk(info, {
/** /**
* @param {any} node * @param {any} node
* @param {any} parent * @param {import('estree').Node} parent
* @param {string} key * @param {string} key
*/ */
enter(node, parent, key) { enter(node, parent, key) {
// don't manipulate shorthand props twice // don't manipulate shorthand props twice
if (key === 'key' && parent.shorthand) return; if (key === 'key' && /** @type {import('estree').Property} */ (parent).shorthand) return;
// don't manipulate `import.meta`, `new.target` // don't manipulate `import.meta`, `new.target`
if (node.type === 'MetaProperty') return this.skip(); if (node.type === 'MetaProperty') return this.skip();
if (map.has(node)) { if (map.has(node)) {
@ -119,7 +125,7 @@ export default class Expression {
if (!lazy || is_index) { if (!lazy || is_index) {
template_scope.dependencies_for_name template_scope.dependencies_for_name
.get(name) .get(name)
.forEach(/** @param {any} name */ (name) => dependencies.add(name)); .forEach((name) => dependencies.add(name));
} }
} else { } else {
if (!lazy) { if (!lazy) {
@ -143,18 +149,15 @@ export default class Expression {
} }
} }
if (names) { if (names) {
names.forEach( names.forEach((name) => {
/** @param {any} name */ (name) => {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
if (template_scope.is_const(name)) { if (template_scope.is_const(name)) {
component.error(node, compiler_errors.invalid_const_update(name)); component.error(node, compiler_errors.invalid_const_update(name));
} }
template_scope.dependencies_for_name.get(name).forEach( template_scope.dependencies_for_name.get(name).forEach((name) => {
/** @param {any} name */ (name) => {
const variable = component.var_lookup.get(name); const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true; if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
} });
);
const each_block = template_scope.get_owner(name); const each_block = template_scope.get_owner(name);
/** @type {import('../EachBlock.js').default} */ (each_block).has_binding = true; /** @type {import('../EachBlock.js').default} */ (each_block).has_binding = true;
} else { } else {
@ -164,10 +167,13 @@ export default class Expression {
variable[deep ? 'mutated' : 'reassigned'] = true; variable[deep ? 'mutated' : 'reassigned'] = true;
} }
/** @type {any} */
const declaration = scope.find_owner(name)?.declarations.get(name); const declaration = scope.find_owner(name)?.declarations.get(name);
if (declaration) { if (declaration) {
if (declaration.kind === 'const' && !deep) { if (
/** @type {import('estree').VariableDeclaration} */ (declaration).kind ===
'const' &&
!deep
) {
component.error(node, { component.error(node, {
code: 'assignment-to-const', code: 'assignment-to-const',
message: 'You are assigning to a const' message: 'You are assigning to a const'
@ -180,12 +186,11 @@ export default class Expression {
}); });
} }
} }
} });
);
} }
}, },
/** @param {import('estree').Node} node */ /** @type {import('estree-walker').SyncHandler} */
leave(node) { leave(node) {
if (map.has(node)) { if (map.has(node)) {
scope = scope.parent; scope = scope.parent;
@ -197,27 +202,22 @@ export default class Expression {
}); });
} }
dynamic_dependencies() { dynamic_dependencies() {
return Array.from(this.dependencies).filter( return Array.from(this.dependencies).filter((name) => {
/** @param {any} name */ (name) => {
if (this.template_scope.is_let(name)) return true; if (this.template_scope.is_let(name)) return true;
if (is_reserved_keyword(name)) return true; if (is_reserved_keyword(name)) return true;
const variable = this.component.var_lookup.get(name); const variable = this.component.var_lookup.get(name);
return is_dynamic(variable); return is_dynamic(variable);
} });
);
} }
dynamic_contextual_dependencies() { dynamic_contextual_dependencies() {
return Array.from(this.contextual_dependencies).filter( return Array.from(this.contextual_dependencies).filter((name) => {
/** @param {any} name */ (name) => {
return Array.from(this.template_scope.dependencies_for_name.get(name)).some( return Array.from(this.template_scope.dependencies_for_name.get(name)).some(
/** @param {any} variable_name */
(variable_name) => { (variable_name) => {
const variable = this.component.var_lookup.get(variable_name); const variable = this.component.var_lookup.get(variable_name);
return is_dynamic(variable); return is_dynamic(variable);
} }
); );
} });
);
} }
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
@ -239,10 +239,7 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
let contextual_dependencies; let contextual_dependencies;
const node = walk(this.node, { const node = walk(this.node, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {any} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (node.type === 'Property' && node.shorthand) { if (node.type === 'Property' && node.shorthand) {
node.value = clone(node.value); node.value = clone(node.value);
@ -257,11 +254,9 @@ export default class Expression {
if (function_expression) { if (function_expression) {
if (template_scope.names.has(name)) { if (template_scope.names.has(name)) {
contextual_dependencies.add(name); contextual_dependencies.add(name);
template_scope.dependencies_for_name.get(name).forEach( template_scope.dependencies_for_name.get(name).forEach((dependency) => {
/** @param {any} dependency */ (dependency) => {
dependencies.add(dependency); dependencies.add(dependency);
} });
);
} else { } else {
dependencies.add(name); dependencies.add(name);
component.add_reference(node, name); // TODO is this redundant/misplaced? component.add_reference(node, name); // TODO is this redundant/misplaced?
@ -284,10 +279,7 @@ export default class Expression {
} }
}, },
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {import('estree').Node} parent
*/
leave(node, parent) { leave(node, parent) {
if (map.has(node)) scope = scope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
@ -299,18 +291,15 @@ export default class Expression {
const has_args = function_expression.params.length > 0; const has_args = function_expression.params.length > 0;
function_expression.params = [ function_expression.params = [
...deps.map( ...deps.map(
/** @param {any} name */ (name) => (name) => /** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
/** @type {import('estree').Identifier} */ ({ type: 'Identifier', name })
), ),
...function_expression.params ...function_expression.params
]; ];
const context_args = deps.map( const context_args = deps.map((name) => block.renderer.reference(name, ctx));
/** @param {any} name */ (name) => block.renderer.reference(name, ctx)
);
component.partly_hoisted.push(declaration); component.partly_hoisted.push(declaration);
block.renderer.add_to_context(id.name); block.renderer.add_to_context(id.name);
const callee = block.renderer.reference(id); const callee = block.renderer.reference(id);
this.replace(/** @type {any} */ (id)); this.replace(id);
const func_declaration = has_args const func_declaration = has_args
? b`function ${id}(...args) { ? b`function ${id}(...args) {
return ${callee}(${context_args}, ...args); return ${callee}(${context_args}, ...args);
@ -325,10 +314,7 @@ export default class Expression {
if (contextual_dependencies.size === 0) { if (contextual_dependencies.size === 0) {
let child_scope = scope; let child_scope = scope;
walk(node, { walk(node, {
/** /** @type {import('estree-walker').SyncHandler} */
* @param {import('estree').Node} node
* @param {any} parent
*/
enter(node, parent) { enter(node, parent) {
if (map.has(node)) child_scope = map.get(node); if (map.has(node)) child_scope = map.get(node);
if (node.type === 'Identifier' && is_reference(node, parent)) { if (node.type === 'Identifier' && is_reference(node, parent)) {
@ -349,7 +335,7 @@ export default class Expression {
} else if (dependencies.size === 0 && contextual_dependencies.size === 0) { } else if (dependencies.size === 0 && contextual_dependencies.size === 0) {
// we can hoist this out of the component completely // we can hoist this out of the component completely
component.fully_hoisted.push(declaration); component.fully_hoisted.push(declaration);
this.replace(/** @type {any} */ (id)); this.replace(id);
component.add_var(node, { component.add_var(node, {
name: id.name, name: id.name,
internal: true, internal: true,
@ -366,9 +352,7 @@ export default class Expression {
const { deps, func_declaration } = extract_functions(); const { deps, func_declaration } = extract_functions();
if (owner.type === 'Attribute' && owner.parent.name === 'slot') { if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
/** @type {Set<import('../interfaces.js').INode>} */ /** @type {Set<import('../interfaces.js').INode>} */
const dep_scopes = new Set( const dep_scopes = new Set(deps.map((name) => template_scope.get_owner(name)));
deps.map(/** @param {any} name */ (name) => template_scope.get_owner(name))
);
// find the nearest scopes // find the nearest scopes
/** @type {import('../interfaces.js').INode} */ /** @type {import('../interfaces.js').INode} */
@ -402,7 +386,7 @@ export default class Expression {
type: 'DestructuredVariable', type: 'DestructuredVariable',
key: func_id, key: func_id,
modifier: () => func_expression, modifier: () => func_expression,
default_modifier: /** @param {any} node */ (node) => node default_modifier: (node) => node
}); });
this.replace(block.renderer.reference(func_id)); this.replace(block.renderer.reference(func_id));
} }
@ -429,16 +413,14 @@ export default class Expression {
/** @type {Set<string>} */ /** @type {Set<string>} */
const traced = new Set(); const traced = new Set();
names.forEach( names.forEach((name) => {
/** @param {any} name */ (name) => {
const dependencies = template_scope.dependencies_for_name.get(name); const dependencies = template_scope.dependencies_for_name.get(name);
if (dependencies) { if (dependencies) {
dependencies.forEach(/** @param {any} name */ (name) => traced.add(name)); dependencies.forEach((name) => traced.add(name));
} else { } else {
traced.add(name); traced.add(name);
} }
} });
);
const context = block.bindings.get(object_name); const context = block.bindings.get(object_name);
if (context) { if (context) {
// for `{#each array as item}` // for `{#each array as item}`
@ -463,19 +445,17 @@ export default class Expression {
if (declarations.length > 0) { if (declarations.length > 0) {
block.maintain_context = true; block.maintain_context = true;
declarations.forEach( declarations.forEach((declaration) => {
/** @param {any} declaration */ (declaration) => {
block.chunks.init.push(declaration); block.chunks.init.push(declaration);
} });
);
} }
return (this.manipulated = /** @type {import('estree').Node} */ (node)); return (this.manipulated = /** @type {import('estree').Node} */ (node));
} }
} }
/** /**
* @param {any} _node * @param {import('estree').Node} _node
* @param {any} parent * @param {import('../interfaces.js').INode} parent
*/ */
function get_function_name(_node, parent) { function get_function_name(_node, parent) {
if (parent.type === 'EventHandler') { if (parent.type === 'EventHandler') {

@ -1,3 +1,4 @@
/** The scope of constructs within the Svelte template */
export default class TemplateScope { export default class TemplateScope {
/** /**
* @typedef {import('../EachBlock').default * @typedef {import('../EachBlock').default

@ -23,11 +23,9 @@ export default function get_const_tags(children, component, node, parent) {
others.push(child); others.push(child);
} }
} }
const consts_nodes = const_tags.map( const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
/** @param {any} tag */ (tag) => new ConstTag(component, node, node.scope, tag)
);
const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component); const sorted_consts_nodes = sort_consts_nodes(consts_nodes, component);
sorted_consts_nodes.forEach(/** @param {any} node */ (node) => node.parse_expression()); sorted_consts_nodes.forEach((node) => node.parse_expression());
const children_nodes = map_children(component, parent, node.scope, others); const children_nodes = map_children(component, parent, node.scope, others);
return [ return [
sorted_consts_nodes, sorted_consts_nodes,
@ -46,49 +44,33 @@ function sort_consts_nodes(consts_nodes, component) {
const sorted_consts_nodes = []; const sorted_consts_nodes = [];
/** @type {ConstNode[]} */ /** @type {ConstNode[]} */
const unsorted_consts_nodes = consts_nodes.map( const unsorted_consts_nodes = consts_nodes.map((node) => {
/** @param {any} node */ (node) => {
return { return {
assignees: node.assignees, assignees: node.assignees,
dependencies: node.dependencies, dependencies: node.dependencies,
node node
}; };
} });
);
const lookup = new Map(); const lookup = new Map();
unsorted_consts_nodes.forEach( unsorted_consts_nodes.forEach((node) => {
/** @param {any} node */ (node) => { node.assignees.forEach((name) => {
node.assignees.forEach(
/** @param {any} name */ (name) => {
if (!lookup.has(name)) { if (!lookup.has(name)) {
lookup.set(name, []); lookup.set(name, []);
} }
lookup.get(name).push(node); lookup.get(name).push(node);
} });
); });
}
);
const cycle = check_graph_for_cycles( const cycle = check_graph_for_cycles(
unsorted_consts_nodes.reduce( unsorted_consts_nodes.reduce((acc, node) => {
/** node.assignees.forEach((v) => {
* @param {any} acc node.dependencies.forEach((w) => {
* @param {any} node
*/ (acc, node) => {
node.assignees.forEach(
/** @param {any} v */ (v) => {
node.dependencies.forEach(
/** @param {any} w */ (w) => {
if (!node.assignees.has(w)) { if (!node.assignees.has(w)) {
acc.push([v, w]); acc.push([v, w]);
} }
} });
); });
}
);
return acc; return acc;
}, }, [])
[]
)
); );
if (cycle && cycle.length) { if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]); const nodeList = lookup.get(cycle[0]);
@ -99,17 +81,15 @@ function sort_consts_nodes(consts_nodes, component) {
/** @param {ConstNode} node */ /** @param {ConstNode} node */
const add_node = (node) => { const add_node = (node) => {
if (sorted_consts_nodes.includes(node)) return; if (sorted_consts_nodes.includes(node)) return;
node.dependencies.forEach( node.dependencies.forEach((name) => {
/** @param {any} name */ (name) => {
if (node.assignees.has(name)) return; if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name); const earlier_nodes = lookup.get(name);
if (earlier_nodes) { if (earlier_nodes) {
earlier_nodes.forEach(add_node); earlier_nodes.forEach(add_node);
} }
} });
);
sorted_consts_nodes.push(node); sorted_consts_nodes.push(node);
}; };
unsorted_consts_nodes.forEach(add_node); unsorted_consts_nodes.forEach(add_node);
return sorted_consts_nodes.map(/** @param {any} node */ (node) => node.node); return sorted_consts_nodes.map((node) => node.node);
} }

@ -79,8 +79,7 @@ function get_constructor(type) {
export default function map_children(component, parent, scope, children) { export default function map_children(component, parent, scope, children) {
let last = null; let last = null;
let ignores = []; let ignores = [];
return children.map( return children.map((child) => {
/** @param {any} child */ (child) => {
const constructor = get_constructor(child.type); const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length; const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores); if (use_ignores) component.push_ignores(ignores);
@ -93,6 +92,5 @@ export default function map_children(component, parent, scope, children) {
node.prev = last; node.prev = last;
last = node; last = node;
return node; return node;
} });
);
} }

@ -73,9 +73,7 @@ export default function read_context(parser) {
space_with_newline = space_with_newline =
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1); space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
return /** @type {any} */ ( return parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1).left;
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1)
).left;
} catch (error) { } catch (error) {
parser.acorn_error(error); parser.acorn_error(error);
} }

@ -588,14 +588,12 @@ export function init_binding_group_dynamic(group, indexes) {
}; };
} }
/** /** @returns {number} */
* @returns {number} */
export function to_number(value) { export function to_number(value) {
return value === '' ? null : +value; return value === '' ? null : +value;
} }
/** /** @returns {any[]} */
* @returns {any[]} */
export function time_ranges_to_array(ranges) { export function time_ranges_to_array(ranges) {
const array = []; const array = [];
for (let i = 0; i < ranges.length; i += 1) { for (let i = 0; i < ranges.length; i += 1) {

Loading…
Cancel
Save