From 8601195a8566032ed638c402c2b3367714485751 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Mon, 10 Jul 2023 01:29:24 -0700 Subject: [PATCH] docs: add a couple internal JSDocs and cleanup from TS migration (#8940) --- packages/svelte/src/compiler/Stats.js | 18 +- .../svelte/src/compiler/compile/Component.js | 985 +++++++--------- .../src/compiler/compile/compiler_warnings.js | 4 +- .../src/compiler/compile/create_module.js | 60 +- packages/svelte/src/compiler/compile/index.js | 16 +- .../src/compiler/compile/nodes/Attribute.js | 33 +- .../src/compiler/compile/nodes/Binding.js | 38 +- .../svelte/src/compiler/compile/nodes/Body.js | 18 +- .../src/compiler/compile/nodes/CatchBlock.js | 10 +- .../src/compiler/compile/nodes/ConstTag.js | 41 +- .../src/compiler/compile/nodes/Document.js | 64 +- .../src/compiler/compile/nodes/EachBlock.js | 17 +- .../src/compiler/compile/nodes/Element.js | 1030 ++++++++--------- .../compiler/compile/nodes/EventHandler.js | 1 - .../svelte/src/compiler/compile/nodes/Head.js | 8 +- .../compiler/compile/nodes/InlineComponent.js | 28 +- .../svelte/src/compiler/compile/nodes/Slot.js | 26 +- .../compiler/compile/nodes/SlotTemplate.js | 56 +- .../compiler/compile/nodes/StyleDirective.js | 4 +- .../src/compiler/compile/nodes/ThenBlock.js | 10 +- .../src/compiler/compile/nodes/Title.js | 10 +- .../src/compiler/compile/nodes/Window.js | 82 +- .../compile/nodes/shared/Expression.js | 178 ++- .../compile/nodes/shared/TemplateScope.js | 1 + .../compile/nodes/shared/get_const_tags.js | 86 +- .../compile/nodes/shared/map_children.js | 28 +- .../svelte/src/compiler/parse/read/context.js | 4 +- packages/svelte/src/runtime/internal/dom.js | 6 +- 28 files changed, 1282 insertions(+), 1580 deletions(-) diff --git a/packages/svelte/src/compiler/Stats.js b/packages/svelte/src/compiler/Stats.js index c69aaf4f22..89c59d62a0 100644 --- a/packages/svelte/src/compiler/Stats.js +++ b/packages/svelte/src/compiler/Stats.js @@ -3,16 +3,14 @@ const now = () => performance.now(); /** @param {any} timings */ function collapse_timings(timings) { const result = {}; - timings.forEach( - /** @param {any} timing */ (timing) => { - result[timing.label] = Object.assign( - { - total: timing.end - timing.start - }, - timing.children && collapse_timings(timing.children) - ); - } - ); + timings.forEach((timing) => { + result[timing.label] = Object.assign( + { + total: timing.end - timing.start + }, + timing.children && collapse_timings(timing.children) + ); + }); return result; } diff --git a/packages/svelte/src/compiler/compile/Component.js b/packages/svelte/src/compiler/compile/Component.js index 03d2c2fad3..384bf06726 100644 --- a/packages/svelte/src/compiler/compile/Component.js +++ b/packages/svelte/src/compiler/compile/Component.js @@ -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]) => ({ - 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) => ({ - name: variable.name, - as: variable.export_name - }) - ), + .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,20 +521,18 @@ 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) => ({ - name: v.name, - export_name: v.export_name || null, - injected: v.injected || false, - module: v.module || false, - mutated: v.mutated || false, - reassigned: v.reassigned || false, - referenced: v.referenced || false, - writable: v.writable || false, - referenced_from_script: v.referenced_from_script || false - }) - ); + : 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, + module: v.module || false, + mutated: v.mutated || false, + reassigned: v.reassigned || false, + referenced: v.referenced || false, + writable: v.writable || false, + referenced_from_script: v.referenced_from_script || false + })); } /** * @param {{ @@ -639,61 +624,51 @@ 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) => { - const variable = this.var_lookup.get(name); - variable.export_name = name; - if ( - declarator.init?.type === 'Literal' && - typeof declarator.init.value === 'boolean' - ) { - variable.is_boolean = true; - } - if ( - !module_script && - variable.writable && - !( - 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); - variable.export_name = name; - } - return node.declaration; - } else { - node.specifiers.forEach( - /** @param {any} specifier */ (specifier) => { - const variable = this.var_lookup.get(specifier.local.name); - if (variable) { - variable.export_name = specifier.exported.name; + node.declaration.declarations.forEach((declarator) => { + extract_names(declarator.id).forEach((name) => { + const variable = this.var_lookup.get(name); + variable.export_name = name; + if ( + declarator.init?.type === 'Literal' && + typeof declarator.init.value === 'boolean' + ) { + variable.is_boolean = true; + } if ( !module_script && variable.writable && !(variable.referenced || variable.referenced_from_script || variable.subscribable) ) { this.warn( - /** @type {any} */ (specifier), - compiler_warnings.unused_export_let(this.name.name, specifier.exported.name) + /** @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); + variable.export_name = name; + } + return node.declaration; + } else { + node.specifiers.forEach((specifier) => { + const variable = this.var_lookup.get(specifier.local.name); + if (variable) { + variable.export_name = specifier.exported.name; + if ( + !module_script && + variable.writable && + !(variable.referenced || variable.referenced_from_script || variable.subscribable) + ) { + this.warn( + /** @type {any} */ (specifier), + compiler_warnings.unused_export_let(this.name.name, specifier.exported.name) + ); } } - ); + }); 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) => { - 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; - } - ); + 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,42 +703,32 @@ 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) => { - if (name[0] === '$') { - return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); - } - const writable = - node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); - const imported = node.type.startsWith('Import'); + scope.declarations.forEach((node, name) => { + if (name[0] === '$') { + return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); + } + const writable = + node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); + const imported = node.type.startsWith('Import'); + this.add_var(node, { + name, + module: true, + hoistable: true, + writable, + imported + }); + }); + globals.forEach((node, name) => { + if (name[0] === '$') { + return this.error(/** @type {any} */ (node), compiler_errors.illegal_subscription); + } else { this.add_var(node, { name, - module: true, - hoistable: true, - writable, - imported + global: true, + hoistable: true }); } - ); - globals.forEach( - /** - * @param {any} node - * @param {any} name - */ (node, name) => { - if (name[0] === '$') { - return this.error(/** @type {any} */ (node), compiler_errors.illegal_subscription); - } else { - this.add_var(node, { - name, - global: true, - hoistable: true - }); - } - } - ); + }); const { body } = script.content; let i = body.length; while (--i >= 0) { @@ -788,94 +751,83 @@ 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) => { - 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) => { - if (!this.var_lookup.has(name) && name[0] !== '$') { - this.injected_reactive_declaration_vars.add(name); - } - } - ); - } - ); + 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((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) => { - if (name[0] === '$') { - return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); - } - const writable = - node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); - const imported = node.type.startsWith('Import'); - this.add_var(node, { - name, - initialised: instance_scope.initialised_declarations.has(name), - writable, - imported - }); - this.node_for_declaration.set(name, node); + instance_scope.declarations.forEach((node, name) => { + if (name[0] === '$') { + return this.error(/** @type {any} */ (node), compiler_errors.illegal_declaration); } - ); + const writable = + node.type === 'VariableDeclaration' && (node.kind === 'var' || node.kind === 'let'); + const imported = node.type.startsWith('Import'); + this.add_var(node, { + name, + initialised: instance_scope.initialised_declarations.has(name), + writable, + 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) => { - if (this.var_lookup.has(name)) return; - const node = globals.get(name); - if (this.injected_reactive_declaration_vars.has(name)) { - this.add_var(node, { - name, - injected: true, - writable: true, - reassigned: true, - initialised: true - }); - } else if (is_reserved_keyword(name)) { - this.add_var(node, { - name, - injected: true - }); - } else if (name[0] === '$') { - if (name === '$' || name[1] === '$') { - return this.error(/** @type {any} */ (node), compiler_errors.illegal_global(name)); - } - this.add_var(node, { - name, - injected: true, - mutated: true, - writable: true - }); - this.add_reference(node, name.slice(1)); - const variable = this.var_lookup.get(name.slice(1)); - if (variable) { - variable.subscribable = true; - variable.referenced_from_script = true; - } - } else { - this.add_var(node, { - name, - global: true, - hoistable: true - }); + sorted_globals.forEach((name) => { + if (this.var_lookup.has(name)) return; + const node = globals.get(name); + if (this.injected_reactive_declaration_vars.has(name)) { + this.add_var(node, { + name, + injected: true, + writable: true, + reassigned: true, + initialised: true + }); + } else if (is_reserved_keyword(name)) { + this.add_var(node, { + name, + injected: true + }); + } else if (name[0] === '$') { + if (name === '$' || name[1] === '$') { + return this.error(/** @type {any} */ (node), compiler_errors.illegal_global(name)); + } + this.add_var(node, { + name, + injected: true, + mutated: true, + writable: true + }); + this.add_reference(node, name.slice(1)); + const variable = this.var_lookup.get(name.slice(1)); + if (variable) { + variable.subscribable = true; + variable.referenced_from_script = true; } + } else { + this.add_var(node, { + name, + global: true, + 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,25 +900,27 @@ export default class Component { names.push(name); } if (names.length > 0) { - names.forEach( - /** @param {any} name */ (name) => { - let current_scope = scope; - let declaration; - while (current_scope) { - if (current_scope.declarations.has(name)) { - declaration = current_scope.declarations.get(name); - break; - } - current_scope = current_scope.parent; - } - if (declaration && /** @type {any} */ (declaration).kind === 'const' && !deep) { - component.error(/** @type {any} */ (node), { - code: 'assignment-to-const', - message: 'You are assigning to a const' - }); + names.forEach((name) => { + let current_scope = scope; + let declaration; + while (current_scope) { + if (current_scope.declarations.has(name)) { + declaration = current_scope.declarations.get(name); + break; } + current_scope = current_scope.parent; } - ); + 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,19 +1003,17 @@ 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) => { - const scope_owner = scope.find_owner(name); - if ( - scope_owner !== null - ? scope_owner === instance_scope - : module_scope && module_scope.has(name) - ) { - const variable = component.var_lookup.get(name); - variable[deep ? 'mutated' : 'reassigned'] = true; - } + names.forEach((name) => { + const scope_owner = scope.find_owner(name); + if ( + scope_owner !== null + ? scope_owner === instance_scope + : module_scope && module_scope.has(name) + ) { + 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,37 +1286,33 @@ 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) => { - if (!d.init) return false; - if (d.init.type !== 'Literal') return false; - // everything except const values can be changed by e.g. svelte devtools - // which means we can't hoist it - if (node.kind !== 'const' && this.compile_options.dev) return false; - const { name } = /** @type {import('estree').Identifier} */ (d.id); - const v = this.var_lookup.get(name); - if (v.reassigned) return false; - if (v.export_name) return false; - if (this.var_lookup.get(name).reassigned) return false; - if ( - this.vars.find( - /** @param {any} variable */ (variable) => variable.name === name && variable.module - ) - ) { - return false; - } - return true; + 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 + // which means we can't hoist it + if (node.kind !== 'const' && this.compile_options.dev) return false; + const { name } = /** @type {import('estree').Identifier} */ (d.id); + const v = this.var_lookup.get(name); + if (v.reassigned) return false; + if (v.export_name) return false; + if (this.var_lookup.get(name).reassigned) return false; + if ( + this.vars.find( + /** @param {any} variable */ (variable) => variable.name === name && variable.module + ) + ) { + return false; } - ); + return true; + }); if (all_hoistable) { - node.declarations.forEach( - /** @param {any} d */ (d) => { - const variable = this.var_lookup.get( - /** @type {import('estree').Identifier} */ (d.id).name - ); - variable.hoistable = true; - } - ); + 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,159 +1428,137 @@ export default class Component { * }>} */ const unsorted_reactive_declarations = []; - this.ast.instance.content.body.forEach( - /** @param {any} node */ (node) => { - const ignores = extract_svelte_ignore_from_comments(node); - if (ignores.length) this.push_ignores(ignores); - if (node.type === 'LabeledStatement' && node.label.name === '$') { - this.reactive_declaration_nodes.add(node); - const assignees = new Set(); - const assignee_nodes = new Set(); - const dependencies = new Set(); - const module_dependencies = new Set(); - let scope = this.instance_scope; - 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 - */ - enter(node, parent) { - if (node.type === 'VariableDeclaration' && node.kind === 'var') { - const is_var_in_outset = node.declarations.some( - /** @param {import('estree').VariableDeclarator} declaration */ (declaration) => { - /** @type {string[]} */ - const names = extract_names(declaration.id); - return !!names.find( - /** @param {string} name */ (name) => { - const var_node = outset_scope_decalarations.get(name); - return var_node === node; - } - ); - } - ); - if (is_var_in_outset) { - return component.error( - /** @type {any} */ (node), - compiler_errors.invalid_var_declaration + 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 === '$') { + this.reactive_declaration_nodes.add(node); + const assignees = new Set(); + const assignee_nodes = new Set(); + const dependencies = new Set(); + const module_dependencies = new Set(); + let scope = this.instance_scope; + const { declarations: outset_scope_decalarations } = this.instance_scope; + const map = this.instance_scope_map; + walk(node.body, { + /** @type {import('estree-walker').SyncHandler} */ + enter(node, parent) { + if (node.type === 'VariableDeclaration' && node.kind === 'var') { + const is_var_in_outset = node.declarations.some( + /** @param {import('estree').VariableDeclarator} declaration */ (declaration) => { + /** @type {string[]} */ + const names = extract_names(declaration.id); + return !!names.find( + /** @param {string} name */ (name) => { + const var_node = outset_scope_decalarations.get(name); + return var_node === node; + } ); } + ); + if (is_var_in_outset) { + return component.error( + /** @type {any} */ (node), + compiler_errors.invalid_var_declaration + ); } - if (map.has(node)) { - scope = map.get(node); + } + if (map.has(node)) { + scope = map.get(node); + } + if (node.type === 'AssignmentExpression') { + const left = get_object(node.left); + extract_identifiers(left).forEach((node) => { + assignee_nodes.add(node); + assignees.add(node.name); + }); + if (node.operator !== '=') { + dependencies.add(left.name); } - if (node.type === 'AssignmentExpression') { - const left = get_object(node.left); - extract_identifiers(left).forEach( - /** @param {any} node */ (node) => { - assignee_nodes.add(node); - assignees.add(node.name); + } else if (node.type === 'UpdateExpression') { + const identifier = get_object(node.argument); + assignees.add(identifier.name); + } else if ( + is_reference( + /** @type {import('is-reference').NodeWithPropertyDefinition} */ (node), + /** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent) + ) + ) { + const identifier = get_object(node); + if (!assignee_nodes.has(identifier)) { + const { name } = identifier; + const owner = scope.find_owner(name); + const variable = component.var_lookup.get(name); + let should_add_as_dependency = true; + if (variable) { + variable.is_reactive_dependency = true; + if (variable.module && variable.writable) { + should_add_as_dependency = false; + module_dependencies.add(name); } - ); - if (node.operator !== '=') { - dependencies.add(left.name); } - } else if (node.type === 'UpdateExpression') { - const identifier = get_object(node.argument); - assignees.add(identifier.name); - } else if ( - is_reference( - /** @type {import('is-reference').NodeWithPropertyDefinition} */ (node), - /** @type {import('is-reference').NodeWithPropertyDefinition} */ (parent) - ) - ) { - const identifier = get_object(node); - if (!assignee_nodes.has(identifier)) { - const { name } = identifier; - const owner = scope.find_owner(name); - const variable = component.var_lookup.get(name); - let should_add_as_dependency = true; - if (variable) { - variable.is_reactive_dependency = true; - if (variable.module && variable.writable) { - should_add_as_dependency = false; - module_dependencies.add(name); - } - } - const is_writable_or_mutated = - variable && (variable.writable || variable.mutated); - if ( - should_add_as_dependency && - (!owner || owner === component.instance_scope) && - (name[0] === '$' || is_writable_or_mutated) - ) { - dependencies.add(name); - } + const is_writable_or_mutated = variable && (variable.writable || variable.mutated); + if ( + should_add_as_dependency && + (!owner || owner === component.instance_scope) && + (name[0] === '$' || is_writable_or_mutated) + ) { + dependencies.add(name); } - this.skip(); } - }, + this.skip(); + } + }, - /** @param {import('estree').Node} node */ - leave(node) { - if (map.has(node)) { - scope = scope.parent; - } + /** @param {import('estree').Node} node */ + leave(node) { + if (map.has(node)) { + scope = scope.parent; } - }); - if (module_dependencies.size > 0 && dependencies.size === 0) { - component.warn( - /** @type {any} */ (node.body), - compiler_warnings.module_script_variable_reactive_declaration( - Array.from(module_dependencies) - ) - ); } - const { expression } = /** @type {import('estree').ExpressionStatement} */ (node.body); - const declaration = - expression && /** @type {import('estree').AssignmentExpression} */ (expression).left; - unsorted_reactive_declarations.push({ - assignees, - dependencies, - node, - declaration - }); + }); + if (module_dependencies.size > 0 && dependencies.size === 0) { + component.warn( + /** @type {any} */ (node.body), + compiler_warnings.module_script_variable_reactive_declaration( + Array.from(module_dependencies) + ) + ); } - if (ignores.length) this.pop_ignores(); + const { expression } = /** @type {import('estree').ExpressionStatement} */ (node.body); + const declaration = + expression && /** @type {import('estree').AssignmentExpression} */ (expression).left; + unsorted_reactive_declarations.push({ + assignees, + dependencies, + node, + declaration + }); } - ); + 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) => { - 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); - } - ); - } - ); + 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) => { - if (!declaration.assignees.has(w)) { - acc.push([v, 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; - }, - [] - ) + }); + }); + 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) => { - if (declaration.assignees.has(name)) return; - const earlier_declarations = lookup.get(name); - if (earlier_declarations) { - earlier_declarations.forEach(add_declaration); - } + 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,153 +1683,144 @@ function process_component_options(component, nodes) { return chunk.expression.value; } if (node) { - node.attributes.forEach( - /** @param {any} attribute */ (attribute) => { - if (attribute.type === 'Attribute') { - const { name } = attribute; - - /** - * @param {import('../interfaces.js').Attribute} attribute - * @param {string} tag - */ - function parse_tag(attribute, tag) { - if (typeof tag !== 'string' && tag !== null) { - return component.error(attribute, compiler_errors.invalid_tag_attribute); - } - if (tag && !regex_valid_tag_name.test(tag)) { - return component.error(attribute, compiler_errors.invalid_tag_property); - } - if (tag && !component.compile_options.customElement) { - component.warn(attribute, compiler_warnings.missing_custom_element_compile_options); - } + node.attributes.forEach((attribute) => { + if (attribute.type === 'Attribute') { + const { name } = attribute; + + /** + * @param {import('../interfaces.js').Attribute} attribute + * @param {string} tag + */ + function parse_tag(attribute, tag) { + if (typeof tag !== 'string' && tag !== null) { + return component.error(attribute, compiler_errors.invalid_tag_attribute); + } + if (tag && !regex_valid_tag_name.test(tag)) { + return component.error(attribute, compiler_errors.invalid_tag_property); + } + if (tag && !component.compile_options.customElement) { + component.warn(attribute, compiler_warnings.missing_custom_element_compile_options); + } + component_options.customElement = + component_options.customElement || /** @type {any} */ ({}); + component_options.customElement.tag = tag; + } + switch (name) { + case 'tag': { + component.warn(attribute, compiler_warnings.tag_option_deprecated); + parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute)); + break; + } + case 'customElement': { component_options.customElement = component_options.customElement || /** @type {any} */ ({}); - component_options.customElement.tag = tag; - } - switch (name) { - case 'tag': { - component.warn(attribute, compiler_warnings.tag_option_deprecated); + const { value } = attribute; + if (value[0].type === 'MustacheTag' && value[0].expression?.value === null) { + component_options.customElement.tag = null; + break; + } else if (value[0].type === 'Text') { parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute)); break; + } else if (value[0].expression.type !== 'ObjectExpression') { + return component.error(attribute, compiler_errors.invalid_customElement_attribute); } - case 'customElement': { - component_options.customElement = - component_options.customElement || /** @type {any} */ ({}); - const { value } = attribute; - if (value[0].type === 'MustacheTag' && value[0].expression?.value === null) { - component_options.customElement.tag = null; - break; - } else if (value[0].type === 'Text') { - parse_tag(attribute, get_value(attribute, compiler_errors.invalid_tag_attribute)); - break; - } 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' - ); - if (tag) { - parse_tag(tag, tag.value?.value); - } else { - return component.error(attribute, compiler_errors.invalid_customElement_attribute); + 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((prop) => prop.key.name === 'props'); + if (props) { + const error = () => + component.error(attribute, compiler_errors.invalid_props_attribute); + if (props.value?.type !== 'ObjectExpression') { + return error(); } - const props = value[0].expression.properties.find( - /** @param {any} prop */ - (prop) => prop.key.name === 'props' - ); - if (props) { - const error = () => - component.error(attribute, compiler_errors.invalid_props_attribute); - if (props.value?.type !== 'ObjectExpression') { + component_options.customElement.props = {}; + for (const property of /** @type {import('estree').ObjectExpression} */ (props.value) + .properties) { + if ( + property.type !== 'Property' || + property.computed || + property.key.type !== 'Identifier' || + property.value.type !== 'ObjectExpression' + ) { return error(); } - component_options.customElement.props = {}; - for (const property of /** @type {import('estree').ObjectExpression} */ ( - props.value - ).properties) { + component_options.customElement.props[property.key.name] = {}; + for (const prop of property.value.properties) { if ( - property.type !== 'Property' || - property.computed || - property.key.type !== 'Identifier' || - property.value.type !== 'ObjectExpression' + prop.type !== 'Property' || + prop.computed || + prop.key.type !== 'Identifier' || + prop.value.type !== 'Literal' ) { return error(); } - component_options.customElement.props[property.key.name] = {}; - for (const prop of property.value.properties) { - if ( - prop.type !== 'Property' || - prop.computed || - prop.key.type !== 'Identifier' || - prop.value.type !== 'Literal' - ) { - return error(); - } - if ( - ['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 || - (prop.key.name === 'type' && - ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf( - /** @type {string} */ (prop.value.value) - ) === -1) || - (prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean') || - (prop.key.name === 'attribute' && typeof prop.value.value !== 'string') - ) { - return error(); - } - component_options.customElement.props[property.key.name][prop.key.name] = - prop.value.value; + if ( + ['reflect', 'attribute', 'type'].indexOf(prop.key.name) === -1 || + (prop.key.name === 'type' && + ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf( + /** @type {string} */ (prop.value.value) + ) === -1) || + (prop.key.name === 'reflect' && typeof prop.value.value !== 'boolean') || + (prop.key.name === 'attribute' && typeof prop.value.value !== 'string') + ) { + return error(); } + component_options.customElement.props[property.key.name][prop.key.name] = + prop.value.value; } } - const shadow = value[0].expression.properties.find( - /** @param {any} prop */ - (prop) => prop.key.name === 'shadow' - ); - if (shadow) { - const shadowdom = shadow.value?.value; - if (shadowdom !== 'open' && shadowdom !== 'none') { - return component.error(shadow, compiler_errors.invalid_shadow_attribute); - } - component_options.customElement.shadow = shadowdom; - } - break; } - case 'namespace': { - const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute); - if (typeof ns !== 'string') { - return component.error(attribute, compiler_errors.invalid_namespace_attribute); - } - if (valid_namespaces.indexOf(ns) === -1) { - const match = fuzzymatch(ns, valid_namespaces); - return component.error( - attribute, - compiler_errors.invalid_namespace_property(ns, match) - ); + const shadow = value[0].expression.properties.find( + (prop) => prop.key.name === 'shadow' + ); + if (shadow) { + const shadowdom = shadow.value?.value; + if (shadowdom !== 'open' && shadowdom !== 'none') { + return component.error(shadow, compiler_errors.invalid_shadow_attribute); } - component_options.namespace = ns; - break; + component_options.customElement.shadow = shadowdom; } - case 'accessors': - case 'immutable': - case 'preserveWhitespace': { - const value = get_value(attribute, compiler_errors.invalid_attribute_value(name)); - if (typeof value !== 'boolean') { - return component.error(attribute, compiler_errors.invalid_attribute_value(name)); - } - component_options[name] = value; - break; + break; + } + case 'namespace': { + const ns = get_value(attribute, compiler_errors.invalid_namespace_attribute); + if (typeof ns !== 'string') { + return component.error(attribute, compiler_errors.invalid_namespace_attribute); } - default: + if (valid_namespaces.indexOf(ns) === -1) { + const match = fuzzymatch(ns, valid_namespaces); return component.error( attribute, - compiler_errors.invalid_options_attribute_unknown(name) + compiler_errors.invalid_namespace_property(ns, match) ); + } + component_options.namespace = ns; + break; } - } else { - return component.error(attribute, compiler_errors.invalid_options_attribute); + case 'accessors': + case 'immutable': + case 'preserveWhitespace': { + const value = get_value(attribute, compiler_errors.invalid_attribute_value(name)); + if (typeof value !== 'boolean') { + return component.error(attribute, compiler_errors.invalid_attribute_value(name)); + } + component_options[name] = value; + break; + } + default: + return component.error( + attribute, + compiler_errors.invalid_options_attribute_unknown(name) + ); } + } else { + return component.error(attribute, compiler_errors.invalid_options_attribute); } - ); + }); } return component_options; } diff --git a/packages/svelte/src/compiler/compile/compiler_warnings.js b/packages/svelte/src/compiler/compile/compiler_warnings.js index 96b68a4228..c5d7ca8c5a 100644 --- a/packages/svelte/src/compiler/compile/compiler_warnings.js +++ b/packages/svelte/src/compiler/compile/compiler_warnings.js @@ -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: /** diff --git a/packages/svelte/src/compiler/compile/create_module.js b/packages/svelte/src/compiler/compile/create_module.js index 2fcad4778f..7c9ff37109 100644 --- a/packages/svelte/src/compiler/compile/create_module.js +++ b/packages/svelte/src/compiler/compile/create_module.js @@ -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,19 +63,17 @@ function get_internal_globals(globals, helpers) { type: 'VariableDeclarator', id: { type: 'ObjectPattern', - properties: globals.map( - /** @param {any} g */ (g) => ({ - type: 'Property', - method: false, - shorthand: false, - computed: false, - key: { type: 'Identifier', name: g.name }, - value: g.alias, - kind: 'init' - }) - ) + properties: globals.map((g) => ({ + type: 'Property', + method: false, + shorthand: false, + computed: false, + 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) => ({ - type: 'ImportSpecifier', - local: h.alias, - imported: { type: 'Identifier', name: h.name } - }) - ), + 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) => ({ - type: 'Specifier', - local: { type: 'Identifier', name: x.name }, - exported: { type: 'Identifier', name: x.as } - }) - ) + specifiers: module_exports.map((x) => ({ + type: 'Specifier', + local: { type: 'Identifier', name: x.name }, + exported: { type: 'Identifier', name: x.as } + })) }; program.body = b` /* ${banner} */ diff --git a/packages/svelte/src/compiler/compile/index.js b/packages/svelte/src/compiler/compile/index.js index 4271ee6c27..09bfc2996f 100644 --- a/packages/svelte/src/compiler/compile/index.js +++ b/packages/svelte/src/compiler/compile/index.js @@ -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) => { - 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); - } + 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}')`); } diff --git a/packages/svelte/src/compiler/compile/nodes/Attribute.js b/packages/svelte/src/compiler/compile/nodes/Attribute.js index 1cfb96dc25..43d26f62f4 100644 --- a/packages/svelte/src/compiler/compile/nodes/Attribute.js +++ b/packages/svelte/src/compiler/compile/nodes/Attribute.js @@ -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) => { - 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; - } - ); + : 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} */ const dependencies = new Set(); - this.chunks.forEach( - /** @param {any} chunk */ (chunk) => { - if (chunk.type === 'Expression') { - add_to_set(dependencies, chunk.dynamic_dependencies()); - } + 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}`; } diff --git a/packages/svelte/src/compiler/compile/nodes/Binding.js b/packages/svelte/src/compiler/compile/nodes/Binding.js index 1b4cbdeb13..0c69ada0d7 100644 --- a/packages/svelte/src/compiler/compile/nodes/Binding.js +++ b/packages/svelte/src/compiler/compile/nodes/Binding.js @@ -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) => { - const variable = component.var_lookup.get(name); - if (variable) { - variable.mutated = true; - } + 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,20 +108,18 @@ 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) => { - const each_block = scope.get_owner(name); - if (each_block && each_block.type === 'EachBlock') { - const rest_node = each_block.context_rest_properties.get(name); - if (rest_node) { - this.component.warn( - /** @type {any} */ (rest_node), - compiler_warnings.invalid_rest_eachblock_binding(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); + if (rest_node) { + this.component.warn( + /** @type {any} */ (rest_node), + compiler_warnings.invalid_rest_eachblock_binding(name) + ); } } - ); + }); } } diff --git a/packages/svelte/src/compiler/compile/nodes/Body.js b/packages/svelte/src/compiler/compile/nodes/Body.js index 8f38891697..c8b0adb22c 100644 --- a/packages/svelte/src/compiler/compile/nodes/Body.js +++ b/packages/svelte/src/compiler/compile/nodes/Body.js @@ -18,16 +18,14 @@ export default class Body extends Node { */ constructor(component, parent, scope, info) { super(component, parent, scope, info); - info.attributes.forEach( - /** @param {any} node */ (node) => { - if (node.type === 'EventHandler') { - this.handlers.push(new EventHandler(component, this, scope, node)); - } else if (node.type === 'Action') { - this.actions.push(new Action(component, this, scope, node)); - } else { - // TODO there shouldn't be anything else here... - } + info.attributes.forEach((node) => { + if (node.type === 'EventHandler') { + this.handlers.push(new EventHandler(component, this, scope, node)); + } else if (node.type === 'Action') { + this.actions.push(new Action(component, this, scope, node)); + } else { + // TODO there shouldn't be anything else here... } - ); + }); } } diff --git a/packages/svelte/src/compiler/compile/nodes/CatchBlock.js b/packages/svelte/src/compiler/compile/nodes/CatchBlock.js index 0d7df21152..49ea024235 100644 --- a/packages/svelte/src/compiler/compile/nodes/CatchBlock.js +++ b/packages/svelte/src/compiler/compile/nodes/CatchBlock.js @@ -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) => { - if (context.type !== 'DestructuredVariable') return; - this.scope.add(context.key.name, parent.expression.dependencies, this); - } - ); + 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) { diff --git a/packages/svelte/src/compiler/compile/nodes/ConstTag.js b/packages/svelte/src/compiler/compile/nodes/ConstTag.js index ab4d30410e..753c13ded9 100644 --- a/packages/svelte/src/compiler/compile/nodes/ConstTag.js +++ b/packages/svelte/src/compiler/compile/nodes/ConstTag.js @@ -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 }) => { - assignees.add(name); - const owner = this.scope.get_owner(name); - if (owner === parent) { - component.error(info, compiler_errors.invalid_const_declaration(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,18 +89,16 @@ 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) => { - if (context.type !== 'DestructuredVariable') return; - const owner = this.scope.get_owner(context.key.name); - if (owner && owner.type === 'ConstTag' && owner.parent === this.parent) { - this.component.error( - this.node, - compiler_errors.invalid_const_declaration(context.key.name) - ); - } - this.scope.add(context.key.name, this.expression.dependencies, this); + 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) { + this.component.error( + this.node, + compiler_errors.invalid_const_declaration(context.key.name) + ); } - ); + this.scope.add(context.key.name, this.expression.dependencies, this); + }); } } diff --git a/packages/svelte/src/compiler/compile/nodes/Document.js b/packages/svelte/src/compiler/compile/nodes/Document.js index b7b75b37a4..51bcc9775d 100644 --- a/packages/svelte/src/compiler/compile/nodes/Document.js +++ b/packages/svelte/src/compiler/compile/nodes/Document.js @@ -28,48 +28,46 @@ export default class Document extends Node { */ constructor(component, parent, scope, info) { super(component, parent, scope, info); - info.attributes.forEach( - /** @param {any} node */ (node) => { - if (node.type === 'EventHandler') { - this.handlers.push(new EventHandler(component, this, scope, node)); - } else if (node.type === 'Binding') { - if (!~valid_bindings.indexOf(node.name)) { - const match = fuzzymatch(node.name, valid_bindings); - if (match) { - return component.error( - node, - compiler_errors.invalid_binding_on( - node.name, - '', - ` (did you mean '${match}'?)` - ) - ); - } else { - return component.error( - node, - compiler_errors.invalid_binding_on( - node.name, - '', - ` — valid bindings are ${list(valid_bindings)}` - ) - ); - } + info.attributes.forEach((node) => { + if (node.type === 'EventHandler') { + this.handlers.push(new EventHandler(component, this, scope, node)); + } else if (node.type === 'Binding') { + if (!~valid_bindings.indexOf(node.name)) { + const match = fuzzymatch(node.name, valid_bindings); + if (match) { + return component.error( + node, + compiler_errors.invalid_binding_on( + node.name, + '', + ` (did you mean '${match}'?)` + ) + ); + } else { + return component.error( + node, + compiler_errors.invalid_binding_on( + node.name, + '', + ` — valid bindings are ${list(valid_bindings)}` + ) + ); } - this.bindings.push(new Binding(component, this, scope, node)); - } else if (node.type === 'Action') { - this.actions.push(new Action(component, this, scope, node)); - } else { - // TODO there shouldn't be anything else here... } + this.bindings.push(new Binding(component, this, scope, node)); + } else if (node.type === 'Action') { + this.actions.push(new Action(component, this, scope, 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); } diff --git a/packages/svelte/src/compiler/compile/nodes/EachBlock.js b/packages/svelte/src/compiler/compile/nodes/EachBlock.js index 6aa379815f..3effb263b6 100644 --- a/packages/svelte/src/compiler/compile/nodes/EachBlock.js +++ b/packages/svelte/src/compiler/compile/nodes/EachBlock.js @@ -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) => { - if (context.type !== 'DestructuredVariable') return; - this.scope.add(context.key.name, this.expression.dependencies, this); - } - ); + 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, diff --git a/packages/svelte/src/compiler/compile/nodes/Element.js b/packages/svelte/src/compiler/compile/nodes/Element.js index ecefb0c34e..6715eb1e5d 100644 --- a/packages/svelte/src/compiler/compile/nodes/Element.js +++ b/packages/svelte/src/compiler/compile/nodes/Element.js @@ -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: // // - 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,67 +460,58 @@ 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) => { - switch (node.type) { - case 'Action': - this.actions.push(new Action(component, this, scope, node)); - break; - case 'Attribute': - case 'Spread': - // special case - if (node.name === 'xmlns') this.namespace = node.value[0].data; - this.attributes.push(new Attribute(component, this, scope, node)); - break; - case 'Binding': - this.bindings.push(new Binding(component, this, scope, node)); - break; - case 'Class': - this.classes.push(new Class(component, this, scope, node)); - break; - case 'StyleDirective': - this.styles.push(new StyleDirective(component, this, scope, node)); - break; - case 'EventHandler': - this.handlers.push(new EventHandler(component, this, scope, node)); - break; - 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) => { - scope.add(name, dependencies, this); - } - ); - break; - } - case 'Transition': { - const transition = new Transition(component, this, scope, node); - if (node.intro) this.intro = transition; - if (node.outro) this.outro = transition; - break; - } - case 'Animation': - this.animation = new Animation(component, this, scope, node); - break; - default: - throw new Error(`Not implemented: ${node.type}`); + 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)); + break; + case 'Attribute': + case 'Spread': + // special case + if (node.name === 'xmlns') this.namespace = node.value[0].data; + this.attributes.push(new Attribute(component, this, scope, node)); + break; + case 'Binding': + this.bindings.push(new Binding(component, this, scope, node)); + break; + case 'Class': + this.classes.push(new Class(component, this, scope, node)); + break; + case 'StyleDirective': + this.styles.push(new StyleDirective(component, this, scope, node)); + break; + case 'EventHandler': + this.handlers.push(new EventHandler(component, this, scope, node)); + break; + case 'Let': { + const l = new Let(component, this, scope, node); + this.lets.push(l); + const dependencies = new Set([l.name.name]); + l.names.forEach((name) => { + scope.add(name, dependencies, this); + }); + break; + } + case 'Transition': { + const transition = new Transition(component, this, scope, node); + if (node.intro) this.intro = transition; + if (node.outro) this.outro = transition; + break; } + case 'Animation': + this.animation = new Animation(component, this, scope, node); + break; + default: + throw new Error(`Not implemented: ${node.type}`); } - ); + }); this.scope = scope; this.children = map_children(component, this, this.scope, info.children); this.validate(); @@ -578,242 +560,226 @@ export default class Element extends Node { } validate_attributes() { const { component, parent } = this; - this.attributes.forEach( - /** @param {any} attribute */ (attribute) => { - if (attribute.is_spread) return; - const name = attribute.name.toLowerCase(); - // Errors - if (regex_illegal_attribute_character.test(name)) { - return component.error(attribute, compiler_errors.illegal_attribute(name)); + this.attributes.forEach((attribute) => { + if (attribute.is_spread) return; + const name = attribute.name.toLowerCase(); + // Errors + if (regex_illegal_attribute_character.test(name)) { + return component.error(attribute, compiler_errors.illegal_attribute(name)); + } + if (name === 'slot') { + if (!attribute.is_static) { + return component.error(attribute, compiler_errors.invalid_slot_attribute); } - if (name === 'slot') { - if (!attribute.is_static) { - return component.error(attribute, compiler_errors.invalid_slot_attribute); - } - if (component.slot_outlets.has(name)) { - return component.error(attribute, compiler_errors.duplicate_slot_attribute(name)); - // this code was unreachable. Still needed? - // component.slot_outlets.add(name); - } - if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) { - return component.error(attribute, compiler_errors.invalid_slotted_content); - } + if (component.slot_outlets.has(name)) { + return component.error(attribute, compiler_errors.duplicate_slot_attribute(name)); + // this code was unreachable. Still needed? + // component.slot_outlets.add(name); } - // Warnings - if (this.namespace !== namespaces.foreign) { - if (name === 'is') { - component.warn(attribute, compiler_warnings.avoid_is); - } - if (react_attributes.has(attribute.name)) { - component.warn( - attribute, - compiler_warnings.invalid_html_attribute( - attribute.name, - react_attributes.get(attribute.name) - ) - ); - } + if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) { + return component.error(attribute, compiler_errors.invalid_slotted_content); } } - ); + // Warnings + if (this.namespace !== namespaces.foreign) { + if (name === 'is') { + component.warn(attribute, compiler_warnings.avoid_is); + } + if (react_attributes.has(attribute.name)) { + component.warn( + attribute, + compiler_warnings.invalid_html_attribute( + attribute.name, + react_attributes.get(attribute.name) + ) + ); + } + } + }); } 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) => { - if (attribute.is_spread) return; - const name = attribute.name.toLowerCase(); - // aria-props - if (name.startsWith('aria-')) { - if (invisible_elements.has(this.name)) { - // aria-unsupported-elements - component.warn(attribute, compiler_warnings.a11y_aria_attributes(this.name)); - } - const type = name.slice(5); - if (!aria_attribute_set.has(type)) { - const match = fuzzymatch(type, aria_attributes); - component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match)); - } - if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) { - component.warn(attribute, compiler_warnings.a11y_hidden(this.name)); - } - // aria-proptypes - let value = attribute.get_static_value(); - if (value === 'true') value = true; - if (value === 'false') value = false; - if ( - value !== null && - value !== undefined && - aria.has(/** @type {import('aria-query').ARIAProperty} */ (name)) - ) { - const schema = aria.get(/** @type {import('aria-query').ARIAProperty} */ (name)); - if (!is_valid_aria_attribute_value(schema, value)) { - component.warn( - attribute, - compiler_warnings.a11y_incorrect_attribute_type(schema, name) - ); - } - } - // aria-activedescendant-has-tabindex - if ( - name === 'aria-activedescendant' && - !this.is_dynamic_element && - !is_interactive_element(this.name, attribute_map) && - !attribute_map.has('tabindex') - ) { - component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex); - } + 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 + if (name.startsWith('aria-')) { + if (invisible_elements.has(this.name)) { + // aria-unsupported-elements + component.warn(attribute, compiler_warnings.a11y_aria_attributes(this.name)); + } + const type = name.slice(5); + if (!aria_attribute_set.has(type)) { + const match = fuzzymatch(type, aria_attributes); + component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match)); } - // aria-role - if (name === 'role') { - if (invisible_elements.has(this.name)) { - // aria-unsupported-elements - component.warn(attribute, compiler_warnings.a11y_misplaced_role(this.name)); + if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) { + component.warn(attribute, compiler_warnings.a11y_hidden(this.name)); + } + // aria-proptypes + let value = attribute.get_static_value(); + if (value === 'true') value = true; + if (value === 'false') value = false; + if ( + value !== null && + value !== undefined && + aria.has(/** @type {import('aria-query').ARIAProperty} */ (name)) + ) { + const schema = aria.get(/** @type {import('aria-query').ARIAProperty} */ (name)); + if (!is_valid_aria_attribute_value(schema, value)) { + component.warn( + attribute, + compiler_warnings.a11y_incorrect_attribute_type(schema, name) + ); } - const value = attribute.get_static_value(); - if (typeof value === 'string') { - value.split(regex_any_repeated_whitespaces).forEach( - /** @param {import('aria-query').ARIARoleDefinitionKey} current_role */ ( - current_role - ) => { - if (current_role && is_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)) { - const match = fuzzymatch(current_role, aria_roles); - component.warn( - attribute, - compiler_warnings.a11y_unknown_role(current_role, match) - ); - } - // no-redundant-roles - if ( - current_role === get_implicit_role(this.name, attribute_map) && - //
    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) - ) { + } + // aria-activedescendant-has-tabindex + if ( + name === 'aria-activedescendant' && + !this.is_dynamic_element && + !is_interactive_element(this.name, attribute_map) && + !attribute_map.has('tabindex') + ) { + component.warn(attribute, compiler_warnings.a11y_aria_activedescendant_has_tabindex); + } + } + // aria-role + if (name === 'role') { + if (invisible_elements.has(this.name)) { + // aria-unsupported-elements + component.warn(attribute, compiler_warnings.a11y_misplaced_role(this.name)); + } + const value = attribute.get_static_value(); + if (typeof value === 'string') { + value.split(regex_any_repeated_whitespaces).forEach( + /** @param {import('aria-query').ARIARoleDefinitionKey} current_role */ ( + current_role + ) => { + if (current_role && is_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)) { + const match = fuzzymatch(current_role, aria_roles); + component.warn(attribute, compiler_warnings.a11y_unknown_role(current_role, match)); + } + // no-redundant-roles + if ( + current_role === get_implicit_role(this.name, attribute_map) && + //
      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)); + } + // 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']); + if (!is_parent_section_or_article) { + const has_nested_redundant_role = + current_role === a11y_nested_implicit_semantics.get(this.name); + if (has_nested_redundant_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']); - if (!is_parent_section_or_article) { - const has_nested_redundant_role = - current_role === a11y_nested_implicit_semantics.get(this.name); - if (has_nested_redundant_role) { - component.warn( - attribute, - compiler_warnings.a11y_no_redundant_roles(current_role) - ); - } - } - // role-has-required-aria-props - if ( - !this.is_dynamic_element && - !is_semantic_role_element(current_role, this.name, attribute_map) - ) { - const role = roles.get(current_role); - 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) - ); - if (has_missing_props) { - component.warn( - attribute, - compiler_warnings.a11y_role_has_required_aria_props( - current_role, - required_role_props - ) - ); - } - } - } - // interactive-supports-focus - if ( - !has_disabled_attribute(attribute_map) && - !is_hidden_from_screen_reader(this.name, attribute_map) && - !is_presentation_role(current_role) && - is_interactive_roles(current_role) && - is_static_element(this.name, attribute_map) && - !attribute_map.get('tabindex') - ) { - const has_interactive_handlers = handlers.some( - /** @param {any} handler */ (handler) => - a11y_interactive_handlers.has(handler.name) + } + // role-has-required-aria-props + if ( + !this.is_dynamic_element && + !is_semantic_role_element(current_role, this.name, attribute_map) + ) { + const role = roles.get(current_role); + if (role) { + const required_role_props = Object.keys(role.requiredProps); + const has_missing_props = required_role_props.some( + (prop) => !attributes.find((a) => a.name === prop) ); - if (has_interactive_handlers) { + if (has_missing_props) { component.warn( - this, - compiler_warnings.a11y_interactive_supports_focus(current_role) + attribute, + compiler_warnings.a11y_role_has_required_aria_props( + current_role, + required_role_props + ) ); } } - // no-interactive-element-to-noninteractive-role - if ( - is_interactive_element(this.name, attribute_map) && - (is_non_interactive_roles(current_role) || is_presentation_role(current_role)) - ) { + } + // interactive-supports-focus + if ( + !has_disabled_attribute(attribute_map) && + !is_hidden_from_screen_reader(this.name, attribute_map) && + !is_presentation_role(current_role) && + is_interactive_roles(current_role) && + is_static_element(this.name, attribute_map) && + !attribute_map.get('tabindex') + ) { + const has_interactive_handlers = handlers.some((handler) => + a11y_interactive_handlers.has(handler.name) + ); + if (has_interactive_handlers) { component.warn( this, - compiler_warnings.a11y_no_interactive_element_to_noninteractive_role( - current_role, - this.name - ) + compiler_warnings.a11y_interactive_supports_focus(current_role) ); } - // no-noninteractive-element-to-interactive-role - if ( - is_non_interactive_element(this.name, attribute_map) && - is_interactive_roles(current_role) && - !a11y_non_interactive_element_to_interactive_role_exceptions[this.name]?.includes( - current_role + } + // no-interactive-element-to-noninteractive-role + if ( + is_interactive_element(this.name, attribute_map) && + (is_non_interactive_roles(current_role) || is_presentation_role(current_role)) + ) { + component.warn( + this, + compiler_warnings.a11y_no_interactive_element_to_noninteractive_role( + current_role, + this.name ) - ) { - component.warn( - this, - compiler_warnings.a11y_no_noninteractive_element_to_interactive_role( - current_role, - this.name - ) - ); - } + ); } - ); - } - } - // no-access-key - if (name === 'accesskey') { - component.warn(attribute, compiler_warnings.a11y_accesskey); - } - // no-autofocus - if (name === 'autofocus') { - component.warn(attribute, compiler_warnings.a11y_autofocus); - } - // scope - if (name === 'scope' && !this.is_dynamic_element && this.name !== 'th') { - component.warn(attribute, compiler_warnings.a11y_misplaced_scope); + // no-noninteractive-element-to-interactive-role + if ( + is_non_interactive_element(this.name, attribute_map) && + is_interactive_roles(current_role) && + !a11y_non_interactive_element_to_interactive_role_exceptions[this.name]?.includes( + current_role + ) + ) { + component.warn( + this, + compiler_warnings.a11y_no_noninteractive_element_to_interactive_role( + current_role, + this.name + ) + ); + } + } + ); } - // tabindex-no-positive - if (name === 'tabindex') { - const value = attribute.get_static_value(); - // @ts-ignore todo is tabindex=true correct case? - if (!isNaN(value) && +value > 0) { - component.warn(attribute, compiler_warnings.a11y_positive_tabindex); - } + } + // no-access-key + if (name === 'accesskey') { + component.warn(attribute, compiler_warnings.a11y_accesskey); + } + // no-autofocus + if (name === 'autofocus') { + component.warn(attribute, compiler_warnings.a11y_autofocus); + } + // scope + if (name === 'scope' && !this.is_dynamic_element && this.name !== 'th') { + component.warn(attribute, compiler_warnings.a11y_misplaced_scope); + } + // tabindex-no-positive + if (name === 'tabindex') { + const value = attribute.get_static_value(); + // @ts-ignore todo is tabindex=true correct case? + if (!isNaN(value) && +value > 0) { + 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,29 +823,25 @@ 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) => { - if ( - invalid_aria_props.has(/** @type {import('aria-query').ARIAProperty} */ (prop.name)) - ) { - component.warn( - prop, - compiler_warnings.a11y_role_supports_aria_props( - prop.name, - role_value, - is_implicit, - this.name - ) - ); - } + .filter((prop) => prop.type !== 'Spread') + .forEach((prop) => { + if ( + invalid_aria_props.has(/** @type {import('aria-query').ARIAProperty} */ (prop.name)) + ) { + component.warn( + prop, + compiler_warnings.a11y_role_supports_aria_props( + prop.name, + role_value, + is_implicit, + this.name + ) + ); } - ); + }); } // no-noninteractive-element-interactions if ( @@ -890,9 +852,8 @@ 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) => - a11y_recommended_interactive_handlers.has(handler.name) + const has_interactive_handlers = handlers.some((handler) => + a11y_recommended_interactive_handlers.has(handler.name) ); if (has_interactive_handlers) { component.warn( @@ -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) => { - if (node.type === 'Comment') return false; - if (node.type === 'Text') return regex_non_whitespace_character.test(node.data); - return true; - } - ); + 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) => { - if (binding.name !== 'this') { - return this.component.error( - binding, - compiler_errors.invalid_binding_foreign(binding.name) - ); - } + this.bindings.forEach((binding) => { + if (binding.name !== 'this') { + return this.component.error(binding, compiler_errors.invalid_binding_foreign(binding.name)); } - ); + }); } validate_bindings() { const { component } = this; @@ -1157,160 +1098,154 @@ export default class Element extends Node { } return value; }; - this.bindings.forEach( - /** @param {any} binding */ (binding) => { - const { name } = binding; - if (name === 'value') { - if (this.name !== 'input' && this.name !== 'textarea' && this.name !== 'select') { - return component.error( - binding, - compiler_errors.invalid_binding_elements(this.name, 'value') - ); - } - if (this.name === 'select') { - const attribute = this.attributes.find( - /** @param {import('./Attribute.js').default} attribute */ - (attribute) => attribute.name === 'multiple' - ); - if (attribute && !attribute.is_static) { - return component.error(attribute, compiler_errors.dynamic_multiple_attribute); - } - } else { - check_type_attribute(); - } - } else if (name === 'checked' || name === 'indeterminate') { - if (this.name !== 'input') { - return component.error( - binding, - compiler_errors.invalid_binding_elements(this.name, name) - ); - } - const type = check_type_attribute(); - if (type !== 'checkbox') { - return component.error( - binding, - compiler_errors.invalid_binding_no_checkbox(name, type === 'radio') - ); - } - } else if (name === 'group') { - if (this.name !== 'input') { - return component.error( - binding, - compiler_errors.invalid_binding_elements(this.name, 'group') - ); - } - const type = check_type_attribute(); - if (type !== 'checkbox' && type !== 'radio') { - return component.error( - binding, - compiler_errors.invalid_binding_element_with( - ' or ', - 'group' - ) - ); - } - } else if (name === 'files') { - if (this.name !== 'input') { - return component.error( - binding, - compiler_errors.invalid_binding_elements(this.name, 'files') - ); - } - const type = check_type_attribute(); - if (type !== 'file') { - return component.error( - binding, - compiler_errors.invalid_binding_element_with('', 'files') - ); - } - } else if (name === 'open') { - if (this.name !== 'details') { - return component.error( - binding, - compiler_errors.invalid_binding_element_with('
      ', name) - ); - } - } else if ( - name === 'currentTime' || - name === 'duration' || - name === 'paused' || - name === 'buffered' || - name === 'seekable' || - name === 'played' || - name === 'volume' || - name === 'muted' || - name === 'playbackRate' || - name === 'seeking' || - name === 'ended' || - name === 'readyState' - ) { - if (this.name !== 'audio' && this.name !== 'video') { - return component.error( - binding, - compiler_errors.invalid_binding_element_with('audio> or