diff --git a/.changeset/cool-spoons-tap.md b/.changeset/cool-spoons-tap.md new file mode 100644 index 000000000000..461db03a81da --- /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 82bc592c1409..1571961badda 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteBody.js @@ -2,7 +2,7 @@ /** @import { Context } from '../types' */ 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, disallow_children } from './shared/special-element.js'; /** * @param {AST.SvelteBody} node @@ -11,10 +11,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 fe54ebf30d42..4c092defb0a6 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteDocument.js @@ -1,6 +1,6 @@ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ -import { disallow_children } from './shared/special-element.js'; +import { check_global_event_reference, disallow_children } from './shared/special-element.js'; import * as e from '../../../errors.js'; import { is_event_attribute } from '../../../utils/ast.js'; @@ -12,10 +12,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 20f5abc5d6b8..44c448fadd68 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/SvelteWindow.js @@ -1,6 +1,6 @@ /** @import { AST } from '#compiler' */ /** @import { Context } from '../types' */ -import { disallow_children } from './shared/special-element.js'; +import { check_global_event_reference, disallow_children } from './shared/special-element.js'; import * as e from '../../../errors.js'; import { is_event_attribute } from '../../../utils/ast.js'; @@ -12,10 +12,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/special-element.js b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/special-element.js index e72b3c6b2a1f..4fe8090f7228 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/special-element.js +++ b/packages/svelte/src/compiler/phases/2-analyze/visitors/shared/special-element.js @@ -1,5 +1,28 @@ /** @import { AST } from '#compiler' */ +/** @import { Context } from '../../types' */ +import { get_attribute_expression } from '../../../../utils/ast.js'; import * as e from '../../../../errors.js'; +import * as w from '../../../../warnings.js'; + +/** + * 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} attribute + * @param {Context} context + */ +export function check_global_event_reference(attribute, context) { + const value = get_attribute_expression( + /** @type {AST.Attribute & { value: [AST.ExpressionTag] | AST.ExpressionTag }} */ (attribute) + ); + + if ( + value.type === 'Identifier' && + value.name === attribute.name && + !context.state.scope.get(value.name) + ) { + w.attribute_global_event_reference(attribute, attribute.name); + } +} /** * @param {AST.SvelteBody | AST.SvelteDocument | AST.SvelteOptionsRaw | AST.SvelteWindow} node 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 000000000000..f47bee71df87 --- /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 000000000000..3ea7674419a1 --- /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 000000000000..e69a45ab3eeb --- /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 + } + } +]