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 */
function collapse_timings(timings) {
const result = {};
timings.forEach(
/** @param {any} timing */ (timing) => {
timings.forEach((timing) => {
result[timing.label] = Object.assign(
{
total: timing.end - timing.start
},
timing.children && collapse_timings(timing.children)
);
}
);
});
return result;
}

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

@ -23,7 +23,7 @@ export default {
},
module_script_variable_reactive_declaration: /** @param {string[]} names */ (names) => ({
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'
} declared in a module script and will not be reactive`
}),
@ -175,7 +175,7 @@ export default {
*/ (role, props) => ({
code: 'a11y-role-has-required-aria-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(', ')}`
}),
a11y_role_supports_aria_props: /**

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

@ -57,16 +57,14 @@ function validate_options(options, warnings) {
}
const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
Object.keys(options).forEach(
/** @param {any} key */ (key) => {
Object.keys(options).forEach((key) => {
if (!valid_options.includes(key)) {
const match = fuzzymatch(key, valid_options);
let message = `Unrecognized option '${key}'`;
if (match) message += ` (did you mean '${match}'?)`;
throw new Error(message);
}
}
);
});
if (name && !regex_valid_identifier.test(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.chunks = this.is_true
? []
: info.value.map(
/** @param {any} node */ (node) => {
: info.value.map((node) => {
if (node.type === 'Text') return node;
this.is_static = false;
const expression = new Expression(component, this, scope, node.expression);
add_to_set(this.dependencies, expression.dependencies);
return expression;
}
);
});
}
if (this.dependencies.size > 0) {
@ -88,13 +86,11 @@ export default class Attribute extends Node {
/** @type {Set<string>} */
const dependencies = new Set();
this.chunks.forEach(
/** @param {any} chunk */ (chunk) => {
this.chunks.forEach((chunk) => {
if (chunk.type === 'Expression') {
add_to_set(dependencies, chunk.dynamic_dependencies());
}
}
);
});
return Array.from(dependencies);
}
@ -114,12 +110,7 @@ export default class Attribute extends Node {
/** @param {any} chunk */ (chunk) =>
chunk.type === 'Text' ? string_literal(chunk.data) : chunk.manipulate(block)
)
.reduce(
/**
* @param {any} lhs
* @param {any} rhs
*/ (lhs, rhs) => x`${lhs} + ${rhs}`
);
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
if (this.chunks[0].type !== 'Text') {
expression = x`"" + ${expression}`;
}

@ -54,8 +54,8 @@ export default class Binding extends Node {
this.expression = new Expression(component, this, scope, info.expression);
this.raw_expression = clone(info.expression);
const { name } = get_object(this.expression.node);
this.is_contextual = Array.from(this.expression.references).some(
/** @param {any} name */ (name) => scope.names.has(name)
this.is_contextual = Array.from(this.expression.references).some((name) =>
scope.names.has(name)
);
if (this.is_contextual) this.validate_binding_rest_properties(scope);
// make sure we track this as a mutable ref
@ -70,14 +70,12 @@ export default class Binding extends Node {
if (scope.is_const(name)) {
component.error(this, compiler_errors.invalid_binding_const);
}
scope.dependencies_for_name.get(name).forEach(
/** @param {any} name */ (name) => {
scope.dependencies_for_name.get(name).forEach((name) => {
const variable = component.var_lookup.get(name);
if (variable) {
variable.mutated = true;
}
}
);
});
} else {
const variable = component.var_lookup.get(name);
if (!variable || variable.global) {
@ -110,8 +108,7 @@ export default class Binding extends Node {
/** @param {import('./shared/TemplateScope.js').default} scope */
validate_binding_rest_properties(scope) {
this.expression.references.forEach(
/** @param {any} name */ (name) => {
this.expression.references.forEach((name) => {
const each_block = scope.get_owner(name);
if (each_block && each_block.type === 'EachBlock') {
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) {
super(component, parent, scope, info);
info.attributes.forEach(
/** @param {any} node */ (node) => {
info.attributes.forEach((node) => {
if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Action') {
@ -27,7 +26,6 @@ export default class Body extends Node {
} else {
// TODO there shouldn't be anything else here...
}
}
);
});
}
}

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

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

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

@ -71,12 +71,10 @@ export default class EachBlock extends AbstractBlock {
component,
context_rest_properties: this.context_rest_properties
});
this.contexts.forEach(
/** @param {any} context */ (context) => {
this.contexts.forEach((context) => {
if (context.type !== 'DestructuredVariable') return;
this.scope.add(context.key.name, this.expression.dependencies, this);
}
);
});
if (this.index) {
// index can only change if this is a keyed each block
const dependencies = info.key ? this.expression.dependencies : new Set([]);
@ -86,13 +84,10 @@ export default class EachBlock extends AbstractBlock {
this.has_animation = false;
[this.const_tags, this.children] = get_const_tags(info.children, component, this, this);
if (this.has_animation) {
this.children = this.children.filter(
/** @param {any} child */ (child) => !isEmptyNode(child) && !isCommentNode(child)
);
this.children = this.children.filter((child) => !isEmptyNode(child) && !isCommentNode(child));
if (this.children.length !== 1) {
const child = this.children.find(
/** @param {any} child */ (child) =>
!!(/** @type {import('./Element.js').default} */ (child).animation)
(child) => !!(/** @type {import('./Element.js').default} */ (child).animation)
);
component.error(
/** @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
return (
typeof value === 'string' &&
value
.split(regex_any_repeated_whitespaces)
.every(/** @param {any} id */ (id) => typeof id === 'string')
value.split(regex_any_repeated_whitespaces).every((id) => typeof id === 'string')
);
case 'tokenlist': // if list of tokens, split each
return (
typeof value === 'string' &&
value
.split(regex_any_repeated_whitespaces)
.every(
/** @param {any} token */ (token) =>
(schema.values || []).indexOf(token.toLowerCase()) > -1
)
.every((token) => (schema.values || []).indexOf(token.toLowerCase()) > -1)
);
default:
return false;
@ -393,7 +388,7 @@ export default class Element extends Node {
* @param {import('../Component.js').default} component
* @param {import('./shared/Node.js').default} parent
* @param {import('./shared/TemplateScope.js').default} scope
* @param {any} info undefined
* @param {any} info
*/
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
@ -435,9 +430,7 @@ export default class Element extends Node {
}
if (this.name === 'textarea') {
if (info.children.length > 0) {
const value_attribute = info.attributes.find(
/** @param {any} node */ (node) => node.name === 'value'
);
const value_attribute = info.attributes.find((node) => node.name === 'value');
if (value_attribute) {
component.error(value_attribute, compiler_errors.textarea_duplicate_value);
return;
@ -456,9 +449,7 @@ export default class Element extends Node {
// Special case — treat these the same way:
// <option>{foo}</option>
// <option value={foo}>{foo}</option>
const value_attribute = info.attributes.find(
/** @param {any} attribute */ (attribute) => attribute.name === 'value'
);
const value_attribute = info.attributes.find((attribute) => attribute.name === 'value');
if (!value_attribute) {
info.attributes.push({
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) {
scope = scope.child();
}
// Binding relies on Attribute, defer its evaluation
const order = ['Binding']; // everything else is -1
info.attributes.sort(
/**
* @param {any} a
* @param {any} b
*/ (a, b) => order.indexOf(a.type) - order.indexOf(b.type)
);
info.attributes.forEach(
/** @param {any} node */ (node) => {
info.attributes.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type));
info.attributes.forEach((node) => {
switch (node.type) {
case 'Action':
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);
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach(
/** @param {any} name */ (name) => {
l.names.forEach((name) => {
scope.add(name, dependencies, this);
}
);
});
break;
}
case 'Transition': {
@ -528,8 +511,7 @@ export default class Element extends Node {
default:
throw new Error(`Not implemented: ${node.type}`);
}
}
);
});
this.scope = scope;
this.children = map_children(component, this, this.scope, info.children);
this.validate();
@ -578,8 +560,7 @@ export default class Element extends Node {
}
validate_attributes() {
const { component, parent } = this;
this.attributes.forEach(
/** @param {any} attribute */ (attribute) => {
this.attributes.forEach((attribute) => {
if (attribute.is_spread) return;
const name = attribute.name.toLowerCase();
// Errors
@ -614,21 +595,15 @@ export default class Element extends Node {
);
}
}
}
);
});
}
validate_attributes_a11y() {
const { component, attributes, handlers } = this;
const attribute_map = new Map();
const handlers_map = new Map();
attributes.forEach(
/** @param {any} attribute */ (attribute) => attribute_map.set(attribute.name, attribute)
);
handlers.forEach(
/** @param {any} handler */ (handler) => handlers_map.set(handler.name, handler)
);
attributes.forEach(
/** @param {any} attribute */ (attribute) => {
attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
handlers.forEach((handler) => handlers_map.set(handler.name, handler));
attributes.forEach((attribute) => {
if (attribute.is_spread) return;
const name = attribute.name.toLowerCase();
// aria-props
@ -688,10 +663,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_no_abstract_role(current_role));
} else if (current_role && !aria_role_set.has(current_role)) {
const match = fuzzymatch(current_role, aria_roles);
component.warn(
attribute,
compiler_warnings.a11y_unknown_role(current_role, match)
);
component.warn(attribute, compiler_warnings.a11y_unknown_role(current_role, match));
}
// no-redundant-roles
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', 'ol', 'li'].includes(this.name)
) {
component.warn(
attribute,
compiler_warnings.a11y_no_redundant_roles(current_role)
);
component.warn(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.
const is_parent_section_or_article = is_parent(this.parent, ['section', 'article']);
@ -725,8 +694,7 @@ export default class Element extends Node {
if (role) {
const required_role_props = Object.keys(role.requiredProps);
const has_missing_props = required_role_props.some(
/** @param {any} prop */
(prop) => !attributes.find(/** @param {any} a */ (a) => a.name === prop)
(prop) => !attributes.find((a) => a.name === prop)
);
if (has_missing_props) {
component.warn(
@ -748,8 +716,7 @@ export default class Element extends Node {
is_static_element(this.name, attribute_map) &&
!attribute_map.get('tabindex')
) {
const has_interactive_handlers = handlers.some(
/** @param {any} handler */ (handler) =>
const has_interactive_handlers = handlers.some((handler) =>
a11y_interactive_handlers.has(handler.name)
);
if (has_interactive_handlers) {
@ -812,8 +779,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_positive_tabindex);
}
}
}
);
});
// click-events-have-key-events
if (handlers_map.has('click')) {
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) &&
(!role || is_non_presentation_role) &&
!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 =
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
if (typeof role_value === 'string' && roles.has(role_value)) {
const { props } = roles.get(role_value);
const invalid_aria_props = new Set(
aria.keys().filter(/** @param {any} attribute */ (attribute) => !(attribute in props))
);
const invalid_aria_props = new Set(aria.keys().filter((attribute) => !(attribute in props)));
const is_implicit = role_value && role === undefined;
attributes
.filter(/** @param {any} prop */ (prop) => prop.type !== 'Spread')
.forEach(
/** @param {any} prop */ (prop) => {
.filter((prop) => prop.type !== 'Spread')
.forEach((prop) => {
if (
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
if (
@ -890,8 +852,7 @@ export default class Element extends Node {
is_non_interactive_roles(role_static_value)) ||
(is_non_interactive_element(this.name, attribute_map) && !role))
) {
const has_interactive_handlers = handlers.some(
/** @param {any} handler */ (handler) =>
const has_interactive_handlers = handlers.some((handler) =>
a11y_recommended_interactive_handlers.has(handler.name)
);
if (has_interactive_handlers) {
@ -914,11 +875,8 @@ export default class Element extends Node {
!is_abstract_role(role_static_value)
) {
const interactive_handlers = handlers
.map(/** @param {any} handler */ (handler) => handler.name)
.filter(
/** @param {any} handlerName */ (handlerName) =>
a11y_interactive_handlers.has(handlerName)
);
.map((handler) => handler.name)
.filter((handlerName) => a11y_interactive_handlers.has(handlerName));
if (interactive_handlers.length > 0) {
component.warn(
this,
@ -931,12 +889,8 @@ export default class Element extends Node {
const { component, attributes, handlers } = this;
const attribute_map = new Map();
const handlers_map = new Map();
attributes.forEach(
/** @param {any} attribute */ (attribute) => attribute_map.set(attribute.name, attribute)
);
handlers.forEach(
/** @param {any} handler */ (handler) => handlers_map.set(handler.name, handler)
);
attributes.forEach((attribute) => attribute_map.set(attribute.name, attribute));
handlers.forEach((handler) => handlers_map.set(handler.name, handler));
if (this.name === 'a') {
const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
const id_attribute = attribute_map.get('id');
@ -995,9 +949,7 @@ export default class Element extends Node {
} else {
const required_attributes = a11y_required_attributes[this.name];
if (required_attributes) {
const has_attribute = required_attributes.some(
/** @param {any} name */ (name) => attribute_map.has(name)
);
const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) {
should_have_attribute(this, required_attributes);
}
@ -1007,9 +959,7 @@ export default class Element extends Node {
const type = attribute_map.get('type');
if (type && type.get_static_value() === 'image') {
const required_attributes = ['alt', 'aria-label', 'aria-labelledby'];
const has_attribute = required_attributes.some(
/** @param {any} name */ (name) => attribute_map.has(name)
);
const has_attribute = required_attributes.some((name) => attribute_map.has(name));
if (!has_attribute) {
should_have_attribute(this, required_attributes, 'input type="image"');
}
@ -1043,7 +993,6 @@ export default class Element extends Node {
const has_input_child = (children) => {
if (
children.some(
/** @param {any} child */
(child) =>
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');
if (track) {
has_caption = track.attributes.find(
/** @param {any} a */
(a) => a.name === 'kind' && a.get_static_value() === 'captions'
);
}
@ -1104,15 +1052,13 @@ export default class Element extends Node {
}
}
if (this.name === 'figure') {
const children = this.children.filter(
/** @param {any} node */ (node) => {
const children = this.children.filter((node) => {
if (node.type === 'Comment') return false;
if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true;
}
);
});
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) {
component.warn(children[index], compiler_warnings.a11y_structure_first_or_last);
@ -1129,16 +1075,11 @@ export default class Element extends Node {
}
}
validate_bindings_foreign() {
this.bindings.forEach(
/** @param {any} binding */ (binding) => {
this.bindings.forEach((binding) => {
if (binding.name !== 'this') {
return this.component.error(
binding,
compiler_errors.invalid_binding_foreign(binding.name)
);
return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name));
}
}
);
});
}
validate_bindings() {
const { component } = this;
@ -1157,8 +1098,7 @@ export default class Element extends Node {
}
return value;
};
this.bindings.forEach(
/** @param {any} binding */ (binding) => {
this.bindings.forEach((binding) => {
const { name } = binding;
if (name === 'value') {
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)) {
return component.error(binding, compiler_errors.invalid_binding(binding.name));
}
}
);
});
}
validate_content() {
if (!a11y_required_content.has(this.name)) return;
if (this.contains_a11y_label) return;
if (
this.bindings.some(
/** @param {any} binding */ (binding) => ['textContent', 'innerHTML'].includes(binding.name)
)
)
if (this.bindings.some((binding) => ['textContent', 'innerHTML'].includes(binding.name)))
return;
if (this.children.length === 0) {
this.component.warn(this, compiler_warnings.a11y_missing_content(this.name));
@ -1318,8 +1253,7 @@ export default class Element extends Node {
}
validate_event_handlers() {
const { component } = this;
this.handlers.forEach(
/** @param {any} handler */ (handler) => {
this.handlers.forEach((handler) => {
if (handler.modifiers.has('passive') && handler.modifiers.has('preventDefault')) {
return component.error(
handler,
@ -1332,8 +1266,7 @@ export default class Element extends Node {
compiler_errors.invalid_event_modifier_combination('passive', 'nonpassive')
);
}
handler.modifiers.forEach(
/** @param {any} modifier */ (modifier) => {
handler.modifiers.forEach((modifier) => {
if (!valid_modifiers.has(modifier)) {
return component.error(
handler,
@ -1349,19 +1282,12 @@ export default class Element extends Node {
component.warn(handler, compiler_warnings.redundant_event_modifier_passive);
}
}
if (
component.compile_options.legacy &&
(modifier === 'once' || modifier === 'passive')
) {
if (component.compile_options.legacy && (modifier === 'once' || modifier === 'passive')) {
// TODO this could be supported, but it would need a few changes to
// how event listeners work
return component.error(
handler,
compiler_errors.invalid_event_modifier_legacy(modifier)
);
}
return component.error(handler, compiler_errors.invalid_event_modifier_legacy(modifier));
}
);
});
if (
passive_events.has(handler.name) &&
handler.can_make_passive &&
@ -1371,19 +1297,18 @@ export default class Element extends Node {
// touch/wheel events should be passive by default
handler.modifiers.add('passive');
}
}
);
});
}
is_media_node() {
return this.name === 'audio' || this.name === 'video';
}
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;
return;
}
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.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') {
/** @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() {
return /** @type {string} */ (
this.attributes
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
.get_static_value()
this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
);
}
optimise() {
attributes_to_compact_whitespace.forEach(
/** @param {any} attribute_name */ (attribute_name) => {
const attribute = this.attributes.find(
/** @param {any} a */ (a) => a.name === attribute_name
);
attributes_to_compact_whitespace.forEach((attribute_name) => {
const attribute = this.attributes.find((a) => a.name === attribute_name);
if (attribute && !attribute.is_true) {
attribute.chunks.forEach(
/**
* @param {any} chunk
* @param {any} index
*/ (chunk, index) => {
attribute.chunks.forEach((chunk, index) => {
if (chunk.type === 'Text') {
let data = chunk.data.replace(regex_any_repeated_whitespaces, ' ');
if (index === 0) {
@ -1444,18 +1360,14 @@ export default class Element extends Node {
}
chunk.data = data;
}
});
}
);
}
}
);
});
}
get can_use_textcontent() {
return (
this.is_static_content &&
this.children.every(
/** @param {any} node */ (node) => node.type === 'Text' || node.type === 'MustacheTag'
)
this.children.every((node) => node.type === 'Text' || node.type === 'MustacheTag')
);
}
get can_optimise_to_html_string() {

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

@ -29,11 +29,9 @@ export default class Head extends Node {
component,
parent,
scope,
info.children.filter(
/** @param {any} child */ (child) => {
info.children.filter((child) => {
return child.type !== 'Text' || regex_non_whitespace_character.test(child.data);
}
)
})
);
if (this.children.length > 0) {
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.handlers.forEach(
/** @param {any} handler */ (handler) => {
handler.modifiers.forEach(
/** @param {any} modifier */ (modifier) => {
this.handlers.forEach((handler) => {
handler.modifiers.forEach((modifier) => {
if (modifier !== 'once') {
return component.error(handler, compiler_errors.invalid_event_modifier_component);
}
}
);
}
);
});
});
const children = [];
for (let i = info.children.length - 1; i >= 0; i--) {
const child = info.children[i];
@ -120,9 +116,7 @@ export default class InlineComponent extends Node {
info.children.splice(i, 1);
} else if (
(child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') &&
child.attributes.find(
/** @param {any} attribute */ (attribute) => attribute.name === 'slot'
)
child.attributes.find((attribute) => attribute.name === 'slot')
) {
const slot_template = {
start: child.start,
@ -156,7 +150,7 @@ export default class InlineComponent extends Node {
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({
start: info.start,
end: info.end,
@ -182,9 +176,7 @@ export default class InlineComponent extends Node {
}
get slot_template_name() {
return /** @type {string} */ (
this.attributes
.find(/** @param {any} attribute */ (attribute) => attribute.name === 'slot')
.get_static_value()
this.attributes.find((attribute) => attribute.name === 'slot').get_static_value()
);
}
}

@ -22,8 +22,7 @@ export default class Slot extends Element {
*/
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
info.attributes.forEach(
/** @param {any} attr */ (attr) => {
info.attributes.forEach((attr) => {
if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
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));
}
);
});
if (!this.slot_name) this.slot_name = 'default';
component.slots.set(this.slot_name, this);

@ -34,18 +34,15 @@ export default class SlotTemplate extends Node {
super(component, parent, scope, info);
this.validate_slot_template_placement();
scope = scope.child();
info.attributes.forEach(
/** @param {any} node */ (node) => {
info.attributes.forEach((node) => {
switch (node.type) {
case 'Let': {
const l = new Let(component, this, scope, node);
this.lets.push(l);
const dependencies = new Set([l.name.name]);
l.names.forEach(
/** @param {any} name */ (name) => {
l.names.forEach((name) => {
scope.add(name, dependencies, this);
}
);
});
break;
}
case 'Attribute': {
@ -66,8 +63,7 @@ export default class SlotTemplate extends Node {
default:
throw new Error(`Not implemented: ${node.type}`);
}
}
);
});
this.scope = scope;
[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')) {
const identifier =
info.value === true
? /** @type {any} */ ({
? {
type: 'Identifier',
start: info.end - info.name.length,
end: info.end,
name: info.name
})
}
: info.value[0].expression;
this.expression = new Expression(component, this, scope, identifier);
this.should_cache = false;

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

@ -23,13 +23,11 @@ export default class Title extends Node {
component.error(info.attributes[0], compiler_errors.illegal_attribute_title);
return;
}
info.children.forEach(
/** @param {any} child */ (child) => {
info.children.forEach((child) => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
return component.error(child, compiler_errors.illegal_structure_title);
}
}
);
});
this.should_cache =
info.children.length === 1
? 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) {
super(component, parent, scope, info);
info.attributes.forEach(
/** @param {any} node */ (node) => {
info.attributes.forEach((node) => {
if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node));
} else if (node.type === 'Binding') {
@ -80,7 +79,6 @@ export default class Window extends Node {
} else {
// TODO there shouldn't be anything else here...
}
}
);
});
}
}

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

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

@ -23,11 +23,9 @@ export default function get_const_tags(children, component, node, parent) {
others.push(child);
}
}
const consts_nodes = const_tags.map(
/** @param {any} tag */ (tag) => new ConstTag(component, node, node.scope, tag)
);
const consts_nodes = const_tags.map((tag) => new ConstTag(component, node, node.scope, tag));
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);
return [
sorted_consts_nodes,
@ -46,49 +44,33 @@ function sort_consts_nodes(consts_nodes, component) {
const sorted_consts_nodes = [];
/** @type {ConstNode[]} */
const unsorted_consts_nodes = consts_nodes.map(
/** @param {any} node */ (node) => {
const unsorted_consts_nodes = consts_nodes.map((node) => {
return {
assignees: node.assignees,
dependencies: node.dependencies,
node
};
}
);
});
const lookup = new Map();
unsorted_consts_nodes.forEach(
/** @param {any} node */ (node) => {
node.assignees.forEach(
/** @param {any} name */ (name) => {
unsorted_consts_nodes.forEach((node) => {
node.assignees.forEach((name) => {
if (!lookup.has(name)) {
lookup.set(name, []);
}
lookup.get(name).push(node);
}
);
}
);
});
});
const cycle = check_graph_for_cycles(
unsorted_consts_nodes.reduce(
/**
* @param {any} acc
* @param {any} node
*/ (acc, node) => {
node.assignees.forEach(
/** @param {any} v */ (v) => {
node.dependencies.forEach(
/** @param {any} w */ (w) => {
unsorted_consts_nodes.reduce((acc, node) => {
node.assignees.forEach((v) => {
node.dependencies.forEach((w) => {
if (!node.assignees.has(w)) {
acc.push([v, w]);
}
}
);
}
);
});
});
return acc;
},
[]
)
}, [])
);
if (cycle && cycle.length) {
const nodeList = lookup.get(cycle[0]);
@ -99,17 +81,15 @@ function sort_consts_nodes(consts_nodes, component) {
/** @param {ConstNode} node */
const add_node = (node) => {
if (sorted_consts_nodes.includes(node)) return;
node.dependencies.forEach(
/** @param {any} name */ (name) => {
node.dependencies.forEach((name) => {
if (node.assignees.has(name)) return;
const earlier_nodes = lookup.get(name);
if (earlier_nodes) {
earlier_nodes.forEach(add_node);
}
}
);
});
sorted_consts_nodes.push(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) {
let last = null;
let ignores = [];
return children.map(
/** @param {any} child */ (child) => {
return children.map((child) => {
const constructor = get_constructor(child.type);
const use_ignores = child.type !== 'Text' && child.type !== 'Comment' && ignores.length;
if (use_ignores) component.push_ignores(ignores);
@ -93,6 +92,5 @@ export default function map_children(component, parent, scope, children) {
node.prev = last;
last = node;
return node;
}
);
});
}

@ -73,9 +73,7 @@ export default function read_context(parser) {
space_with_newline =
space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);
return /** @type {any} */ (
parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1)
).left;
return parse_expression_at(`${space_with_newline}(${pattern_string} = 1)`, start - 1).left;
} catch (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) {
return value === '' ? null : +value;
}
/**
* @returns {any[]} */
/** @returns {any[]} */
export function time_ranges_to_array(ranges) {
const array = [];
for (let i = 0; i < ranges.length; i += 1) {

Loading…
Cancel
Save