@ -12,12 +12,19 @@ import { create_fragment } from '../utils/create.js';
import { create _attribute , create _expression _metadata } from '../../nodes.js' ;
import { get _attribute _expression , is _expression _attribute } from '../../../utils/ast.js' ;
import { closing _tag _omitted } from '../../../../html-tree-validation.js' ;
import { list } from '../../../utils/string.js' ;
// eslint-disable-next-line no-useless-escape
const valid _tag _name = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/ ;
/** Invalid attribute characters if the attribute is not surrounded by quotes */
const regex _starts _with _invalid _attr _value = /^(\/>|[\s"'=<>`])/ ;
const regex _invalid _unquoted _attribute _value = /^(\/>|[\s"'=<>`])/ ;
const regex _closing _textarea _tag = /^<\/textarea(\s[^>]*)?>/i ;
const regex _closing _comment = /-->/ ;
const regex _component _name = /^(?:[A-Z]|[A-Za-z][A-Za-z0-9_$]*\.)/ ;
const regex _valid _component _name =
/^(?:[A-Z][A-Za-z0-9_$.]*|[a-z][A-Za-z0-9_$]*\.[A-Za-z0-9_$])[A-Za-z0-9_$.]*$/ ;
const regex _whitespace _or _slash _or _closing _tag = /(\s|\/|>)/ ;
const regex _token _ending _character = /[\s=/>"']/ ;
const regex _starts _with _quote _characters = /^["']/ ;
const regex _attribute _value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/ ;
const regex _valid _tag _name = /^!?[a-zA-Z]{1,}:?[a-zA-Z0-9-]*/ ;
/** @type {Map<string, Compiler.ElementLike['type']>} */
const root _only _meta _tags = new Map ( [
@ -37,47 +44,6 @@ const meta_tags = new Map([
[ 'svelte:fragment' , 'SvelteFragment' ]
] ) ;
const valid _meta _tags = Array . from ( meta _tags . keys ( ) ) ;
const SELF = /^svelte:self(?=[\s/>])/ ;
const COMPONENT = /^svelte:component(?=[\s/>])/ ;
const SLOT = /^svelte:fragment(?=[\s/>])/ ;
const ELEMENT = /^svelte:element(?=[\s/>])/ ;
/** @param {Compiler.TemplateNode[]} stack */
function parent _is _head ( stack ) {
let i = stack . length ;
while ( i -- ) {
const { type } = stack [ i ] ;
if ( type === 'SvelteHead' ) return true ;
if ( type === 'RegularElement' || type === 'Component' ) return false ;
}
return false ;
}
/** @param {Compiler.TemplateNode[]} stack */
function parent _is _shadowroot _template ( stack ) {
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
let i = stack . length ;
while ( i -- ) {
if (
stack [ i ] . type === 'RegularElement' &&
/** @type {Compiler.RegularElement} */ ( stack [ i ] ) . attributes . some (
( a ) => a . type === 'Attribute' && a . name === 'shadowrootmode'
)
) {
return true ;
}
}
return false ;
}
const regex _closing _textarea _tag = /^<\/textarea(\s[^>]*)?>/i ;
const regex _closing _comment = /-->/ ;
const regex _component _name = /^(?:[A-Z]|[A-Za-z][A-Za-z0-9_$]*\.)/ ;
const regex _valid _component _name =
/^(?:[A-Z][A-Za-z0-9_$.]*|[a-z][A-Za-z0-9_$]*\.[A-Za-z0-9_$])[A-Za-z0-9_$.]*$/ ;
/** @param {Parser} parser */
export default function element ( parser ) {
const start = parser . index ++ ;
@ -100,31 +66,62 @@ export default function element(parser) {
}
const is _closing _tag = parser . eat ( '/' ) ;
const name = parser . read _until ( regex _whitespace _or _slash _or _closing _tag ) ;
const name = read _tag _name ( parser ) ;
if ( is _closing _tag ) {
parser . allow _whitespace ( ) ;
parser . eat ( '>' , true ) ;
if ( root _only _meta _tags . has ( name ) ) {
if ( is _closing _tag ) {
if (
[ 'svelte:options' , 'svelte:window' , 'svelte:body' , 'svelte:document' ] . includes ( name ) &&
/** @type {Compiler.ElementLike} */ ( parent ) . fragment . nodes . length
) {
e . svelte _meta _invalid _content (
/** @type {Compiler.ElementLike} */ ( parent ) . fragment . nodes [ 0 ] . start ,
name
) ;
}
} else {
if ( name in parser . meta _tags ) {
e . svelte _meta _duplicate ( start , name ) ;
}
if ( is _void ( name ) ) {
e . void _element _invalid _content ( start ) ;
}
if ( parent . type !== 'Root' ) {
e . svelte _meta _invalid _placement ( start , name ) ;
// close any elements that don't have their own closing tags, e.g. <div><p></div>
while ( /** @type {Compiler.RegularElement} */ ( parent ) . name !== name ) {
if ( parent . type !== 'RegularElement' ) {
if ( parser . last _auto _closed _tag && parser . last _auto _closed _tag . tag === name ) {
e . element _invalid _closing _tag _autoclosed ( start , name , parser . last _auto _closed _tag . reason ) ;
} else {
e . element _invalid _closing _tag ( start , name ) ;
}
}
parser . meta _tags [ name ] = true ;
parent . end = start ;
parser . pop ( ) ;
parent = parser . current ( ) ;
}
parent . end = parser . index ;
parser . pop ( ) ;
if ( parser . last _auto _closed _tag && parser . stack . length < parser . last _auto _closed _tag . depth ) {
parser . last _auto _closed _tag = undefined ;
}
return ;
}
if ( name . startsWith ( 'svelte:' ) && ! meta _tags . has ( name ) ) {
const bounds = { start : start + 1 , end : start + 1 + name . length } ;
e . svelte _meta _invalid _tag ( bounds , list ( Array . from ( meta _tags . keys ( ) ) ) ) ;
}
if ( ! regex _valid _tag _name . test ( name ) ) {
const bounds = { start : start + 1 , end : start + 1 + name . length } ;
e . element _invalid _tag _name ( bounds ) ;
}
if ( root _only _meta _tags . has ( name ) ) {
if ( name in parser . meta _tags ) {
e . svelte _meta _duplicate ( start , name ) ;
}
if ( parent . type !== 'Root' ) {
e . svelte _meta _invalid _placement ( start , name ) ;
}
parser . meta _tags [ name ] = true ;
}
const type = meta _tags . has ( name )
@ -175,38 +172,7 @@ export default function element(parser) {
parser . allow _whitespace ( ) ;
if ( is _closing _tag ) {
if ( is _void ( name ) ) {
e . void _element _invalid _content ( start ) ;
}
parser . eat ( '>' , true ) ;
// close any elements that don't have their own closing tags, e.g. <div><p></div>
while ( /** @type {Compiler.RegularElement} */ ( parent ) . name !== name ) {
if ( parent . type !== 'RegularElement' ) {
if ( parser . last _auto _closed _tag && parser . last _auto _closed _tag . tag === name ) {
e . element _invalid _closing _tag _autoclosed ( start , name , parser . last _auto _closed _tag . reason ) ;
} else {
e . element _invalid _closing _tag ( start , name ) ;
}
}
parent . end = start ;
parser . pop ( ) ;
parent = parser . current ( ) ;
}
parent . end = parser . index ;
parser . pop ( ) ;
if ( parser . last _auto _closed _tag && parser . stack . length < parser . last _auto _closed _tag . depth ) {
parser . last _auto _closed _tag = undefined ;
}
return ;
} else if ( parent . type === 'RegularElement' && closing _tag _omitted ( parent . name , name ) ) {
if ( parent . type === 'RegularElement' && closing _tag _omitted ( parent . name , name ) ) {
parent . end = start ;
parser . pop ( ) ;
parser . last _auto _closed _tag = {
@ -386,64 +352,34 @@ export default function element(parser) {
}
}
const regex _whitespace _or _slash _or _closing _tag = /(\s|\/|>)/ ;
/** @param {Parser} parser */
function read _tag _name ( parser ) {
const start = parser . index ;
if ( parser . read ( SELF ) ) {
// check we're inside a block, otherwise this
// will cause infinite recursion
let i = parser . stack . length ;
let legal = false ;
while ( i -- ) {
const fragment = parser . stack [ i ] ;
if (
fragment . type === 'IfBlock' ||
fragment . type === 'EachBlock' ||
fragment . type === 'Component' ||
fragment . type === 'SnippetBlock'
) {
legal = true ;
break ;
}
}
if ( ! legal ) {
e . svelte _self _invalid _placement ( start ) ;
}
return 'svelte:self' ;
}
if ( parser . read ( COMPONENT ) ) return 'svelte:component' ;
if ( parser . read ( ELEMENT ) ) return 'svelte:element' ;
if ( parser . read ( SLOT ) ) return 'svelte:fragment' ;
const name = parser . read _until ( regex _whitespace _or _slash _or _closing _tag ) ;
if ( meta _tags . has ( name ) ) return name ;
if ( name . startsWith ( 'svelte:' ) ) {
const list = ` ${ valid _meta _tags . slice ( 0 , - 1 ) . join ( ', ' ) } or ${ valid _meta _tags [ valid _meta _tags . length - 1 ] } ` ;
e . svelte _meta _invalid _tag ( start , list ) ;
/** @param {Compiler.TemplateNode[]} stack */
function parent _is _head ( stack ) {
let i = stack . length ;
while ( i -- ) {
const { type } = stack [ i ] ;
if ( type === 'SvelteHead' ) return true ;
if ( type === 'RegularElement' || type === 'Component' ) return false ;
}
return false ;
}
if ( ! valid _tag _name . test ( name ) ) {
e . element _invalid _tag _name ( start ) ;
/** @param {Compiler.TemplateNode[]} stack */
function parent _is _shadowroot _template ( stack ) {
// https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
let i = stack . length ;
while ( i -- ) {
if (
stack [ i ] . type === 'RegularElement' &&
/** @type {Compiler.RegularElement} */ ( stack [ i ] ) . attributes . some (
( a ) => a . type === 'Attribute' && a . name === 'shadowrootmode'
)
) {
return true ;
}
}
return name ;
return false ;
}
// eslint-disable-next-line no-useless-escape
const regex _token _ending _character = /[\s=\/>"']/ ;
const regex _starts _with _quote _characters = /^["']/ ;
const regex _attribute _value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]+))/ ;
/ * *
* @ param { Parser } parser
* @ returns { Compiler . Attribute | null }
@ -692,7 +628,7 @@ function read_attribute_value(parser) {
( ) => {
// handle common case of quote marks existing outside of regex for performance reasons
if ( quote _mark ) return parser . match ( quote _mark ) ;
return ! ! parser . match _regex ( regex _ starts_with _ invalid_ attr_value ) ;
return ! ! parser . match _regex ( regex _ invalid_ unquoted_ attribute _value ) ;
} ,
'in attribute value'
) ;