UNPKG

svelte

Version:

Cybernetically enhanced web apps

161 lines (141 loc) 4.79 kB
/** @import { AST } from '#compiler' */ /** @import { Context } from '../../types' */ import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js'; import { regex_illegal_attribute_character } from '../../../patterns.js'; import * as e from '../../../../errors.js'; import * as w from '../../../../warnings.js'; import { validate_attribute, validate_attribute_name, validate_slot_attribute } from './attribute.js'; const EVENT_MODIFIERS = [ 'preventDefault', 'stopPropagation', 'stopImmediatePropagation', 'capture', 'once', 'passive', 'nonpassive', 'self', 'trusted' ]; /** * @param {AST.RegularElement | AST.SvelteElement} node * @param {Context} context */ export function validate_element(node, context) { let has_animate_directive = false; /** @type {AST.TransitionDirective | null} */ let in_transition = null; /** @type {AST.TransitionDirective | null} */ let out_transition = null; for (const attribute of node.attributes) { if (attribute.type === 'Attribute') { const is_expression = is_expression_attribute(attribute); if (context.state.analysis.runes) { validate_attribute(attribute, node); if (is_expression) { const expression = get_attribute_expression(attribute); if (expression.type === 'SequenceExpression') { let i = /** @type {number} */ (expression.start); while (--i > 0) { const char = context.state.analysis.source[i]; if (char === '(') break; // parenthesized sequence expressions are ok if (char === '{') e.attribute_invalid_sequence_expression(expression); } } } } if (regex_illegal_attribute_character.test(attribute.name)) { e.attribute_invalid_name(attribute, attribute.name); } if (attribute.name.startsWith('on') && attribute.name.length > 2) { if (!is_expression) { 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); } } if (attribute.name === 'slot') { /** @type {AST.RegularElement | AST.SvelteElement | AST.Component | AST.SvelteComponent | AST.SvelteSelf | undefined} */ validate_slot_attribute(context, attribute); } if (attribute.name === 'is') { w.attribute_avoid_is(attribute); } const correct_name = react_attributes.get(attribute.name); if (correct_name) { w.attribute_invalid_property_name(attribute, attribute.name, correct_name); } validate_attribute_name(attribute); } else if (attribute.type === 'AnimateDirective') { const parent = context.path.at(-2); if (parent?.type !== 'EachBlock') { e.animation_invalid_placement(attribute); } else if (!parent.key) { e.animation_missing_key(attribute); } else if ( parent.body.nodes.filter( (n) => n.type !== 'Comment' && n.type !== 'ConstTag' && (n.type !== 'Text' || n.data.trim() !== '') ).length > 1 ) { e.animation_invalid_placement(attribute); } if (has_animate_directive) { e.animation_duplicate(attribute); } else { has_animate_directive = true; } } else if (attribute.type === 'TransitionDirective') { const existing = /** @type {AST.TransitionDirective | null} */ ( (attribute.intro && in_transition) || (attribute.outro && out_transition) ); if (existing) { const a = existing.intro ? (existing.outro ? 'transition' : 'in') : 'out'; const b = attribute.intro ? (attribute.outro ? 'transition' : 'in') : 'out'; if (a === b) { e.transition_duplicate(attribute, a); } else { e.transition_conflict(attribute, a, b); } } if (attribute.intro) in_transition = attribute; if (attribute.outro) out_transition = attribute; } else if (attribute.type === 'OnDirective') { let has_passive_modifier = false; let conflicting_passive_modifier = ''; for (const modifier of attribute.modifiers) { if (!EVENT_MODIFIERS.includes(modifier)) { const list = `${EVENT_MODIFIERS.slice(0, -1).join(', ')} or ${EVENT_MODIFIERS.at(-1)}`; e.event_handler_invalid_modifier(attribute, list); } if (modifier === 'passive') { has_passive_modifier = true; } else if (modifier === 'nonpassive' || modifier === 'preventDefault') { conflicting_passive_modifier = modifier; } if (has_passive_modifier && conflicting_passive_modifier) { e.event_handler_invalid_modifier_combination( attribute, 'passive', conflicting_passive_modifier ); } } } } } const react_attributes = new Map([ ['className', 'class'], ['htmlFor', 'for'] ]);