chore: dedupe void element lists (#12690)

* chore: dedupe void element lists

* fix
pull/12691/head
Rich Harris 3 months ago committed by GitHub
parent 44a1324a2a
commit a45ad5df7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,7 +1,7 @@
/** @import { Expression } from 'estree' */ /** @import { Expression } from 'estree' */
/** @import * as Compiler from '#compiler' */ /** @import * as Compiler from '#compiler' */
/** @import { Parser } from '../index.js' */ /** @import { Parser } from '../index.js' */
import { is_void } from '../../../../constants.js'; import { is_void } from '../../../../utils.js';
import read_expression from '../read/expression.js'; import read_expression from '../read/expression.js';
import { read_script } from '../read/script.js'; import { read_script } from '../read/script.js';
import read_style from '../read/style.js'; import read_style from '../read/style.js';

@ -1,12 +1,13 @@
/** @import { RegularElement } from '#compiler' */ /** @import { RegularElement } from '#compiler' */
/** @import { Context } from '../types' */ /** @import { Context } from '../types' */
import { is_void } from '../../../../utils.js';
import { import {
is_tag_valid_with_ancestor, is_tag_valid_with_ancestor,
is_tag_valid_with_parent is_tag_valid_with_parent
} from '../../../../html-tree-validation.js'; } from '../../../../html-tree-validation.js';
import * as e from '../../../errors.js'; import * as e from '../../../errors.js';
import * as w from '../../../warnings.js'; import * as w from '../../../warnings.js';
import { MathMLElements, SVGElements, VoidElements } from '../../constants.js'; import { MathMLElements, SVGElements } from '../../constants.js';
import { create_attribute } from '../../nodes.js'; import { create_attribute } from '../../nodes.js';
import { regex_starts_with_newline } from '../../patterns.js'; import { regex_starts_with_newline } from '../../patterns.js';
import { check_element } from './shared/a11y.js'; import { check_element } from './shared/a11y.js';
@ -157,7 +158,7 @@ export function RegularElement(node, context) {
if ( if (
context.state.analysis.source[node.end - 2] === '/' && context.state.analysis.source[node.end - 2] === '/' &&
context.state.options.namespace !== 'foreign' && context.state.options.namespace !== 'foreign' &&
!VoidElements.includes(node_name) && !is_void(node_name) &&
!SVGElements.includes(node_name) !SVGElements.includes(node_name)
) { ) {
w.element_invalid_self_closing_tag(node, node.name); w.element_invalid_self_closing_tag(node, node.name);

@ -4,6 +4,7 @@
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */ /** @import { ComponentClientTransformState, ComponentContext } from '../types' */
/** @import { Scope } from '../../../scope' */ /** @import { Scope } from '../../../scope' */
import { DOMBooleanAttributes } from '../../../../../constants.js'; import { DOMBooleanAttributes } from '../../../../../constants.js';
import { is_void } from '../../../../../utils.js';
import { escape_html } from '../../../../../escaping.js'; import { escape_html } from '../../../../../escaping.js';
import { dev, is_ignored, locator } from '../../../../state.js'; import { dev, is_ignored, locator } from '../../../../state.js';
import { import {
@ -12,7 +13,7 @@ import {
is_text_attribute is_text_attribute
} from '../../../../utils/ast.js'; } from '../../../../utils/ast.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { DOMProperties, LoadErrorElements, VoidElements } from '../../../constants.js'; import { DOMProperties, LoadErrorElements } from '../../../constants.js';
import { is_custom_element_node } from '../../../nodes.js'; import { is_custom_element_node } from '../../../nodes.js';
import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
import { serialize_get_binding } from '../utils.js'; import { serialize_get_binding } from '../utils.js';
@ -358,7 +359,7 @@ export function RegularElement(node, context) {
location.push(child_locations); location.push(child_locations);
} }
if (!VoidElements.includes(node.name)) { if (!is_void(node.name)) {
context.state.template.push(`</${node.name}>`); context.state.template.push(`</${node.name}>`);
} }
} }

@ -2,9 +2,9 @@
/** @import { RegularElement, Text } from '#compiler' */ /** @import { RegularElement, Text } from '#compiler' */
/** @import { ComponentContext, ComponentServerTransformState } from '../types.js' */ /** @import { ComponentContext, ComponentServerTransformState } from '../types.js' */
/** @import { Scope } from '../../../scope.js' */ /** @import { Scope } from '../../../scope.js' */
import { is_void } from '../../../../../utils.js';
import { dev, locator } from '../../../../state.js'; import { dev, locator } from '../../../../state.js';
import * as b from '../../../../utils/builders.js'; import * as b from '../../../../utils/builders.js';
import { VoidElements } from '../../../constants.js';
import { clean_nodes, determine_namespace_for_children } from '../../utils.js'; import { clean_nodes, determine_namespace_for_children } from '../../utils.js';
import { serialize_element_attributes } from './shared/element.js'; import { serialize_element_attributes } from './shared/element.js';
import { process_children, serialize_template } from './shared/utils.js'; import { process_children, serialize_template } from './shared/utils.js';
@ -95,7 +95,7 @@ export function RegularElement(node, context) {
); );
} }
if (!VoidElements.includes(node.name) || namespace === 'foreign') { if (!is_void(node.name) || namespace === 'foreign') {
state.template.push(b.literal(`</${node.name}>`)); state.template.push(b.literal(`</${node.name}>`));
} }

@ -8,25 +8,6 @@ export const DOMProperties = [
...DOMBooleanAttributes ...DOMBooleanAttributes
]; ];
export const VoidElements = [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'menuitem',
'meta',
'param',
'source',
'track',
'wbr'
];
export const PassiveEvents = ['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel']; export const PassiveEvents = ['wheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel'];
export const Runes = /** @type {const} */ ([ export const Runes = /** @type {const} */ ([

@ -180,25 +180,6 @@ export const reserved = [
'yield' 'yield'
]; ];
const void_element_names = [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
];
// we use a list of ignorable runtime warnings because not every runtime warning // we use a list of ignorable runtime warnings because not every runtime warning
// can be ignored and we want to keep the validation for svelte-ignore in place // can be ignored and we want to keep the validation for svelte-ignore in place
export const IGNORABLE_RUNTIME_WARNINGS = /** @type {const} */ ([ export const IGNORABLE_RUNTIME_WARNINGS = /** @type {const} */ ([
@ -209,8 +190,3 @@ export const IGNORABLE_RUNTIME_WARNINGS = /** @type {const} */ ([
'ownership_invalid_binding', 'ownership_invalid_binding',
'ownership_invalid_mutation' 'ownership_invalid_mutation'
]); ]);
/** @param {string} name */
export function is_void(name) {
return void_element_names.includes(name) || name.toLowerCase() === '!doctype';
}

@ -11,36 +11,19 @@ import {
ELEMENT_PRESERVE_ATTRIBUTE_CASE, ELEMENT_PRESERVE_ATTRIBUTE_CASE,
ELEMENT_IS_NAMESPACED ELEMENT_IS_NAMESPACED
} from '../../constants.js'; } from '../../constants.js';
import { escape_html } from '../../escaping.js'; import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js'; import { current_component, pop, push } from './context.js';
import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js'; import { EMPTY_COMMENT, BLOCK_CLOSE, BLOCK_OPEN } from './hydration.js';
import { validate_store } from '../shared/validate.js'; import { validate_store } from '../shared/validate.js';
import { is_void } from '../../utils.js';
// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
// https://infra.spec.whatwg.org/#noncharacter // https://infra.spec.whatwg.org/#noncharacter
const INVALID_ATTR_NAME_CHAR_REGEX = const INVALID_ATTR_NAME_CHAR_REGEX =
/[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u; /[\s'">/=\u{FDD0}-\u{FDEF}\u{FFFE}\u{FFFF}\u{1FFFE}\u{1FFFF}\u{2FFFE}\u{2FFFF}\u{3FFFE}\u{3FFFF}\u{4FFFE}\u{4FFFF}\u{5FFFE}\u{5FFFF}\u{6FFFE}\u{6FFFF}\u{7FFFE}\u{7FFFF}\u{8FFFE}\u{8FFFF}\u{9FFFE}\u{9FFFF}\u{AFFFE}\u{AFFFF}\u{BFFFE}\u{BFFFF}\u{CFFFE}\u{CFFFF}\u{DFFFE}\u{DFFFF}\u{EFFFE}\u{EFFFF}\u{FFFFE}\u{FFFFF}\u{10FFFE}\u{10FFFF}]/u;
export const VoidElements = new Set([
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'menuitem',
'meta',
'param',
'source',
'track',
'wbr'
]);
/** /**
* @param {Payload} to_copy * @param {Payload} to_copy
* @returns {Payload} * @returns {Payload}
@ -82,7 +65,7 @@ export function element(payload, tag, attributes_fn = noop, children_fn = noop)
attributes_fn(); attributes_fn();
payload.out += `>`; payload.out += `>`;
if (!VoidElements.has(tag)) { if (!is_void(tag)) {
children_fn(); children_fn();
if (!RawTextElements.includes(tag)) { if (!RawTextElements.includes(tag)) {
payload.out += EMPTY_COMMENT; payload.out += EMPTY_COMMENT;

@ -1,6 +1,6 @@
/** @import { TemplateNode } from '#client' */ /** @import { TemplateNode } from '#client' */
/** @import { Getters } from '#shared' */ /** @import { Getters } from '#shared' */
import { is_void } from '../../constants.js'; import { is_void } from '../../utils.js';
import * as w from './warnings.js'; import * as w from './warnings.js';
import * as e from './errors.js'; import * as e from './errors.js';

@ -12,3 +12,30 @@ export function hash(str) {
while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i); while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
return (hash >>> 0).toString(36); return (hash >>> 0).toString(36);
} }
const VOID_ELEMENT_NAMES = [
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
];
/**
* Returns `true` if `name` is of a void element
* @param {string} name
*/
export function is_void(name) {
return VOID_ELEMENT_NAMES.includes(name) || name.toLowerCase() === '!doctype';
}

Loading…
Cancel
Save