UNPKG

svelte

Version:

Cybernetically enhanced web apps

262 lines (235 loc) 7.53 kB
/** @import { ObjectExpression } from 'estree' */ /** @import { AST } from '#compiler' */ import { NAMESPACE_MATHML, NAMESPACE_SVG } from '../../../../constants.js'; import * as e from '../../../errors.js'; /** * @param {AST.SvelteOptionsRaw} node * @returns {AST.Root['options']} */ export default function read_options(node) { /** @type {AST.SvelteOptions} */ const component_options = { start: node.start, end: node.end, // @ts-ignore attributes: node.attributes }; if (!node) { return component_options; } for (const attribute of node.attributes) { if (attribute.type !== 'Attribute') { e.svelte_options_invalid_attribute(attribute); } const { name } = attribute; switch (name) { case 'runes': { component_options.runes = get_boolean_value(attribute); break; } case 'tag': { e.svelte_options_deprecated_tag(attribute); break; // eslint doesn't know this is unnecessary } case 'customElement': { /** @type {AST.SvelteOptions['customElement']} */ const ce = {}; const { value: v } = attribute; const value = v === true || Array.isArray(v) ? v : [v]; if (value === true) { e.svelte_options_invalid_customelement(attribute); } else if (value[0].type === 'Text') { const tag = get_static_value(attribute); validate_tag(attribute, tag); ce.tag = tag; component_options.customElement = ce; break; } else if (value[0].expression.type !== 'ObjectExpression') { // Before Svelte 4 it was necessary to explicitly set customElement to null or else you'd get a warning. // This is no longer necessary, but for backwards compat just skip in this case now. if (value[0].expression.type === 'Literal' && value[0].expression.value === null) { break; } e.svelte_options_invalid_customelement(attribute); } /** @type {Array<[string, any]>} */ const properties = []; for (const property of value[0].expression.properties) { if ( property.type !== 'Property' || property.computed || property.key.type !== 'Identifier' ) { e.svelte_options_invalid_customelement(attribute); } properties.push([property.key.name, property.value]); } const tag = properties.find(([name]) => name === 'tag'); if (tag) { const tag_value = tag[1]?.value; validate_tag(tag, tag_value); ce.tag = tag_value; } const props = properties.find(([name]) => name === 'props')?.[1]; if (props) { if (props.type !== 'ObjectExpression') { e.svelte_options_invalid_customelement_props(attribute); } ce.props = {}; for (const property of /** @type {ObjectExpression} */ (props).properties) { if ( property.type !== 'Property' || property.computed || property.key.type !== 'Identifier' || property.value.type !== 'ObjectExpression' ) { e.svelte_options_invalid_customelement_props(attribute); } ce.props[property.key.name] = {}; for (const prop of property.value.properties) { if ( prop.type !== 'Property' || prop.computed || prop.key.type !== 'Identifier' || prop.value.type !== 'Literal' ) { e.svelte_options_invalid_customelement_props(attribute); } if (prop.key.name === 'type') { if ( ['String', 'Number', 'Boolean', 'Array', 'Object'].indexOf( /** @type {string} */ (prop.value.value) ) === -1 ) { e.svelte_options_invalid_customelement_props(attribute); } ce.props[property.key.name].type = /** @type {any} */ (prop.value.value); } else if (prop.key.name === 'reflect') { if (typeof prop.value.value !== 'boolean') { e.svelte_options_invalid_customelement_props(attribute); } ce.props[property.key.name].reflect = prop.value.value; } else if (prop.key.name === 'attribute') { if (typeof prop.value.value !== 'string') { e.svelte_options_invalid_customelement_props(attribute); } ce.props[property.key.name].attribute = prop.value.value; } else { e.svelte_options_invalid_customelement_props(attribute); } } } } const shadow = properties.find(([name]) => name === 'shadow')?.[1]; if (shadow) { const shadowdom = shadow?.value; if (shadowdom !== 'open' && shadowdom !== 'none') { e.svelte_options_invalid_customelement_shadow(shadow); } ce.shadow = shadowdom; } const extend = properties.find(([name]) => name === 'extend')?.[1]; if (extend) { ce.extend = extend; } component_options.customElement = ce; break; } case 'namespace': { const value = get_static_value(attribute); if (value === NAMESPACE_SVG) { component_options.namespace = 'svg'; } else if (value === NAMESPACE_MATHML) { component_options.namespace = 'mathml'; } else if (value === 'html' || value === 'mathml' || value === 'svg') { component_options.namespace = value; } else { e.svelte_options_invalid_attribute_value(attribute, `"html", "mathml" or "svg"`); } break; } case 'css': { const value = get_static_value(attribute); if (value === 'injected') { component_options.css = value; } else { e.svelte_options_invalid_attribute_value(attribute, `"injected"`); } break; } case 'immutable': { component_options.immutable = get_boolean_value(attribute); break; } case 'preserveWhitespace': { component_options.preserveWhitespace = get_boolean_value(attribute); break; } case 'accessors': { component_options.accessors = get_boolean_value(attribute); break; } default: e.svelte_options_unknown_attribute(attribute, name); } } return component_options; } /** * @param {any} attribute */ function get_static_value(attribute) { const { value } = attribute; if (value === true) return true; const chunk = Array.isArray(value) ? value[0] : value; if (!chunk) return true; if (value.length > 1) { return null; } if (chunk.type === 'Text') return chunk.data; if (chunk.expression.type !== 'Literal') { return null; } return chunk.expression.value; } /** * @param {any} attribute */ function get_boolean_value(attribute) { const value = get_static_value(attribute); if (typeof value !== 'boolean') { e.svelte_options_invalid_attribute_value(attribute, 'true or false'); } return value; } // https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name const tag_name_char = '[a-z0-9_.\xB7\xC0-\xD6\xD8-\xF6\xF8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u{10000}-\u{EFFFF}-]'; const regex_valid_tag_name = new RegExp(`^[a-z]${tag_name_char}*-${tag_name_char}*$`, 'u'); const reserved_tag_names = [ 'annotation-xml', 'color-profile', 'font-face', 'font-face-src', 'font-face-uri', 'font-face-format', 'font-face-name', 'missing-glyph' ]; /** * @param {any} attribute * @param {string | null} tag * @returns {asserts tag is string} */ function validate_tag(attribute, tag) { if (typeof tag !== 'string') { e.svelte_options_invalid_tagname(attribute); } if (tag) { if (!regex_valid_tag_name.test(tag)) { e.svelte_options_invalid_tagname(attribute); } else if (reserved_tag_names.includes(tag)) { e.svelte_options_reserved_tagname(attribute); } } }