diff --git a/.changeset/cool-spoons-tap.md b/.changeset/cool-spoons-tap.md new file mode 100644 index 0000000000..461db03a81 --- /dev/null +++ b/.changeset/cool-spoons-tap.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: warn on undeclared shorthand event handlers on ``, `` and `` diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js index 82bc592c14..39b559b32f 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js @@ -3,6 +3,7 @@ import * as e from '../../../errors.js'; import { is_event_attribute } from '../../../utils/ast.js'; import { disallow_children } from './shared/special-element.js'; +import { check_global_event_reference } from './shared/utils.js'; /** * @param {AST.SvelteBody} node @@ -11,10 +12,9 @@ import { disallow_children } from './shared/special-element.js'; export function SvelteBody(node, context) { disallow_children(node); for (const attribute of node.attributes) { - if ( - attribute.type === 'SpreadAttribute' || - (attribute.type === 'Attribute' && !is_event_attribute(attribute)) - ) { + if (attribute.type === 'Attribute' && is_event_attribute(attribute)) { + check_global_event_reference(attribute, context); + } else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') { e.svelte_body_illegal_attribute(attribute); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js index fe54ebf30d..23e984f607 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js @@ -3,6 +3,7 @@ import { disallow_children } from './shared/special-element.js'; import * as e from '../../../errors.js'; import { is_event_attribute } from '../../../utils/ast.js'; +import { check_global_event_reference } from './shared/utils.js'; /** * @param {AST.SvelteDocument} node @@ -12,10 +13,9 @@ export function SvelteDocument(node, context) { disallow_children(node); for (const attribute of node.attributes) { - if ( - attribute.type === 'SpreadAttribute' || - (attribute.type === 'Attribute' && !is_event_attribute(attribute)) - ) { + if (attribute.type === 'Attribute' && is_event_attribute(attribute)) { + check_global_event_reference(attribute, context); + } else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') { e.illegal_element_attribute(attribute, 'svelte:document'); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js index 20f5abc5d6..5281c7a635 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js @@ -3,6 +3,7 @@ import { disallow_children } from './shared/special-element.js'; import * as e from '../../../errors.js'; import { is_event_attribute } from '../../../utils/ast.js'; +import { check_global_event_reference } from './shared/utils.js'; /** * @param {AST.SvelteWindow} node @@ -12,10 +13,9 @@ export function SvelteWindow(node, context) { disallow_children(node); for (const attribute of node.attributes) { - if ( - attribute.type === 'SpreadAttribute' || - (attribute.type === 'Attribute' && !is_event_attribute(attribute)) - ) { + if (attribute.type === 'Attribute' && is_event_attribute(attribute)) { + check_global_event_reference(attribute, context); + } else if (attribute.type === 'SpreadAttribute' || attribute.type === 'Attribute') { e.illegal_element_attribute(attribute, 'svelte:window'); } } diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js index ff02dee02a..6a0c58ca30 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/element.js @@ -9,6 +9,7 @@ import { validate_attribute_name, validate_slot_attribute } from './attribute.js'; +import { check_global_event_reference } from './utils.js'; const EVENT_MODIFIERS = [ 'preventDefault', @@ -64,14 +65,7 @@ export function validate_element(node, context) { e.attribute_invalid_event_handler(attribute); } - const value = get_attribute_expression(attribute); - if ( - value.type === 'Identifier' && - value.name === attribute.name && - !context.state.scope.get(value.name) - ) { - w.attribute_global_event_reference(attribute, attribute.name); - } + check_global_event_reference(attribute, context); } if (attribute.name === 'slot') { diff --git a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js index 35116d715a..a32355abf5 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/utils.js @@ -4,7 +4,11 @@ /** @import { Scope } from '../../../scope' */ /** @import { NodeLike } from '../../../../errors.js' */ import * as e from '../../../../errors.js'; -import { extract_identifiers, get_parent } from '../../../../utils/ast.js'; +import { + extract_identifiers, + get_attribute_expression, + get_parent +} from '../../../../utils/ast.js'; import * as w from '../../../../warnings.js'; import * as b from '#compiler/builders'; import { get_rune } from '../../../scope.js'; @@ -298,3 +302,21 @@ export function validate_export(node, scope, name) { e.state_invalid_export(node); } } + +/** + * Warns when an event attribute uses the shorthand form (`{onclick}`) but the + * referenced name isn't declared, so it silently resolves to the global handler. + * @param {AST.Attribute & { value: [AST.ExpressionTag] | AST.ExpressionTag }} attribute + * @param {Context} context + */ +export function check_global_event_reference(attribute, context) { + const value = get_attribute_expression(attribute); + + if ( + value.type === 'Identifier' && + value.name === attribute.name && + !context.state.scope.get(value.name) + ) { + w.attribute_global_event_reference(attribute, attribute.name); + } +} diff --git a/packages/svelte/tests/validator/samples/global-event-reference-special-elements/_config.js b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/_config.js new file mode 100644 index 0000000000..f47bee71df --- /dev/null +++ b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/_config.js @@ -0,0 +1,3 @@ +import { test } from '../../test'; + +export default test({}); diff --git a/packages/svelte/tests/validator/samples/global-event-reference-special-elements/input.svelte b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/input.svelte new file mode 100644 index 0000000000..3ea7674419 --- /dev/null +++ b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/input.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/svelte/tests/validator/samples/global-event-reference-special-elements/warnings.json b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/warnings.json new file mode 100644 index 0000000000..e69a45ab3e --- /dev/null +++ b/packages/svelte/tests/validator/samples/global-event-reference-special-elements/warnings.json @@ -0,0 +1,38 @@ +[ + { + "code": "attribute_global_event_reference", + "message": "You are referencing `globalThis.onresize`. Did you forget to declare a variable with that name?", + "start": { + "column": 27, + "line": 5 + }, + "end": { + "column": 37, + "line": 5 + } + }, + { + "code": "attribute_global_event_reference", + "message": "You are referencing `globalThis.onvisibilitychange`. Did you forget to declare a variable with that name?", + "start": { + "column": 17, + "line": 6 + }, + "end": { + "column": 37, + "line": 6 + } + }, + { + "code": "attribute_global_event_reference", + "message": "You are referencing `globalThis.onfocus`. Did you forget to declare a variable with that name?", + "start": { + "column": 13, + "line": 7 + }, + "end": { + "column": 22, + "line": 7 + } + } +]