svelte
Version:
Cybernetically enhanced web apps
161 lines (141 loc) • 4.79 kB
JavaScript
/** @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']
]);