@@ -12,12 +12,19 @@ import { create_fragment } from '../utils/create.js';
12
12
import { create_attribute , create_expression_metadata } from '../../nodes.js' ;
13
13
import { get_attribute_expression , is_expression_attribute } from '../../../utils/ast.js' ;
14
14
import { closing_tag_omitted } from '../../../../html-tree-validation.js' ;
15
+ import { list } from '../../../utils/string.js' ;
15
16
16
- // eslint-disable-next-line no-useless-escape
17
- const valid_tag_name = / ^ \! ? [ a - z A - Z ] { 1 , } : ? [ a - z A - Z 0 - 9 \- ] * / ;
18
-
19
- /** Invalid attribute characters if the attribute is not surrounded by quotes */
20
- const regex_starts_with_invalid_attr_value = / ^ ( \/ > | [ \s " ' = < > ` ] ) / ;
17
+ const regex_invalid_unquoted_attribute_value = / ^ ( \/ > | [ \s " ' = < > ` ] ) / ;
18
+ const regex_closing_textarea_tag = / ^ < \/ t e x t a r e a ( \s [ ^ > ] * ) ? > / i;
19
+ const regex_closing_comment = / - - > / ;
20
+ const regex_component_name = / ^ (?: [ A - Z ] | [ A - Z a - z ] [ A - Z a - z 0 - 9 _ $ ] * \. ) / ;
21
+ const regex_valid_component_name =
22
+ / ^ (?: [ A - Z ] [ A - Z a - z 0 - 9 _ $ . ] * | [ a - z ] [ A - Z a - z 0 - 9 _ $ ] * \. [ A - Z a - z 0 - 9 _ $ ] ) [ A - Z a - z 0 - 9 _ $ . ] * $ / ;
23
+ const regex_whitespace_or_slash_or_closing_tag = / ( \s | \/ | > ) / ;
24
+ const regex_token_ending_character = / [ \s = / > " ' ] / ;
25
+ const regex_starts_with_quote_characters = / ^ [ " ' ] / ;
26
+ const regex_attribute_value = / ^ (?: " ( [ ^ " ] * ) " | ' ( [ ^ ' ] ) * ' | ( [ ^ > \s ] + ) ) / ;
27
+ const regex_valid_tag_name = / ^ ! ? [ a - z A - Z ] { 1 , } : ? [ a - z A - Z 0 - 9 - ] * / ;
21
28
22
29
/** @type {Map<string, Compiler.ElementLike['type']> } */
23
30
const root_only_meta_tags = new Map ( [
@@ -37,47 +44,6 @@ const meta_tags = new Map([
37
44
[ 'svelte:fragment' , 'SvelteFragment' ]
38
45
] ) ;
39
46
40
- const valid_meta_tags = Array . from ( meta_tags . keys ( ) ) ;
41
-
42
- const SELF = / ^ s v e l t e : s e l f (? = [ \s / > ] ) / ;
43
- const COMPONENT = / ^ s v e l t e : c o m p o n e n t (? = [ \s / > ] ) / ;
44
- const SLOT = / ^ s v e l t e : f r a g m e n t (? = [ \s / > ] ) / ;
45
- const ELEMENT = / ^ s v e l t e : e l e m e n t (? = [ \s / > ] ) / ;
46
-
47
- /** @param {Compiler.TemplateNode[] } stack */
48
- function parent_is_head ( stack ) {
49
- let i = stack . length ;
50
- while ( i -- ) {
51
- const { type } = stack [ i ] ;
52
- if ( type === 'SvelteHead' ) return true ;
53
- if ( type === 'RegularElement' || type === 'Component' ) return false ;
54
- }
55
- return false ;
56
- }
57
-
58
- /** @param {Compiler.TemplateNode[] } stack */
59
- function parent_is_shadowroot_template ( stack ) {
60
- // https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
61
- let i = stack . length ;
62
- while ( i -- ) {
63
- if (
64
- stack [ i ] . type === 'RegularElement' &&
65
- /** @type {Compiler.RegularElement } */ ( stack [ i ] ) . attributes . some (
66
- ( a ) => a . type === 'Attribute' && a . name === 'shadowrootmode'
67
- )
68
- ) {
69
- return true ;
70
- }
71
- }
72
- return false ;
73
- }
74
-
75
- const regex_closing_textarea_tag = / ^ < \/ t e x t a r e a ( \s [ ^ > ] * ) ? > / i;
76
- const regex_closing_comment = / - - > / ;
77
- const regex_component_name = / ^ (?: [ A - Z ] | [ A - Z a - z ] [ A - Z a - z 0 - 9 _ $ ] * \. ) / ;
78
- const regex_valid_component_name =
79
- / ^ (?: [ A - Z ] [ A - Z a - z 0 - 9 _ $ . ] * | [ a - z ] [ A - Z a - z 0 - 9 _ $ ] * \. [ A - Z a - z 0 - 9 _ $ ] ) [ A - Z a - z 0 - 9 _ $ . ] * $ / ;
80
-
81
47
/** @param {Parser } parser */
82
48
export default function element ( parser ) {
83
49
const start = parser . index ++ ;
@@ -100,31 +66,62 @@ export default function element(parser) {
100
66
}
101
67
102
68
const is_closing_tag = parser . eat ( '/' ) ;
69
+ const name = parser . read_until ( regex_whitespace_or_slash_or_closing_tag ) ;
103
70
104
- const name = read_tag_name ( parser ) ;
71
+ if ( is_closing_tag ) {
72
+ parser . allow_whitespace ( ) ;
73
+ parser . eat ( '>' , true ) ;
105
74
106
- if ( root_only_meta_tags . has ( name ) ) {
107
- if ( is_closing_tag ) {
108
- if (
109
- [ 'svelte:options' , 'svelte:window' , 'svelte:body' , 'svelte:document' ] . includes ( name ) &&
110
- /** @type {Compiler.ElementLike } */ ( parent ) . fragment . nodes . length
111
- ) {
112
- e . svelte_meta_invalid_content (
113
- /** @type {Compiler.ElementLike } */ ( parent ) . fragment . nodes [ 0 ] . start ,
114
- name
115
- ) ;
116
- }
117
- } else {
118
- if ( name in parser . meta_tags ) {
119
- e . svelte_meta_duplicate ( start , name ) ;
120
- }
75
+ if ( is_void ( name ) ) {
76
+ e . void_element_invalid_content ( start ) ;
77
+ }
121
78
122
- if ( parent . type !== 'Root' ) {
123
- e . svelte_meta_invalid_placement ( start , name ) ;
79
+ // close any elements that don't have their own closing tags, e.g. <div><p></div>
80
+ while ( /** @type {Compiler.RegularElement } */ ( parent ) . name !== name ) {
81
+ if ( parent . type !== 'RegularElement' ) {
82
+ if ( parser . last_auto_closed_tag && parser . last_auto_closed_tag . tag === name ) {
83
+ e . element_invalid_closing_tag_autoclosed ( start , name , parser . last_auto_closed_tag . reason ) ;
84
+ } else {
85
+ e . element_invalid_closing_tag ( start , name ) ;
86
+ }
124
87
}
125
88
126
- parser . meta_tags [ name ] = true ;
89
+ parent . end = start ;
90
+ parser . pop ( ) ;
91
+
92
+ parent = parser . current ( ) ;
93
+ }
94
+
95
+ parent . end = parser . index ;
96
+ parser . pop ( ) ;
97
+
98
+ if ( parser . last_auto_closed_tag && parser . stack . length < parser . last_auto_closed_tag . depth ) {
99
+ parser . last_auto_closed_tag = undefined ;
127
100
}
101
+
102
+ return ;
103
+ }
104
+
105
+ if ( name . startsWith ( 'svelte:' ) && ! meta_tags . has ( name ) ) {
106
+ const bounds = { start : start + 1 , end : start + 1 + name . length } ;
107
+ e . svelte_meta_invalid_tag ( bounds , list ( Array . from ( meta_tags . keys ( ) ) ) ) ;
108
+ }
109
+
110
+ if ( ! regex_valid_tag_name . test ( name ) ) {
111
+ const bounds = { start : start + 1 , end : start + 1 + name . length } ;
112
+ e . element_invalid_tag_name ( bounds ) ;
113
+ }
114
+
115
+ if ( root_only_meta_tags . has ( name ) ) {
116
+ if ( name in parser . meta_tags ) {
117
+ e . svelte_meta_duplicate ( start , name ) ;
118
+ }
119
+
120
+ if ( parent . type !== 'Root' ) {
121
+ e . svelte_meta_invalid_placement ( start , name ) ;
122
+ }
123
+
124
+ parser . meta_tags [ name ] = true ;
128
125
}
129
126
130
127
const type = meta_tags . has ( name )
@@ -175,38 +172,7 @@ export default function element(parser) {
175
172
176
173
parser . allow_whitespace ( ) ;
177
174
178
- if ( is_closing_tag ) {
179
- if ( is_void ( name ) ) {
180
- e . void_element_invalid_content ( start ) ;
181
- }
182
-
183
- parser . eat ( '>' , true ) ;
184
-
185
- // close any elements that don't have their own closing tags, e.g. <div><p></div>
186
- while ( /** @type {Compiler.RegularElement } */ ( parent ) . name !== name ) {
187
- if ( parent . type !== 'RegularElement' ) {
188
- if ( parser . last_auto_closed_tag && parser . last_auto_closed_tag . tag === name ) {
189
- e . element_invalid_closing_tag_autoclosed ( start , name , parser . last_auto_closed_tag . reason ) ;
190
- } else {
191
- e . element_invalid_closing_tag ( start , name ) ;
192
- }
193
- }
194
-
195
- parent . end = start ;
196
- parser . pop ( ) ;
197
-
198
- parent = parser . current ( ) ;
199
- }
200
-
201
- parent . end = parser . index ;
202
- parser . pop ( ) ;
203
-
204
- if ( parser . last_auto_closed_tag && parser . stack . length < parser . last_auto_closed_tag . depth ) {
205
- parser . last_auto_closed_tag = undefined ;
206
- }
207
-
208
- return ;
209
- } else if ( parent . type === 'RegularElement' && closing_tag_omitted ( parent . name , name ) ) {
175
+ if ( parent . type === 'RegularElement' && closing_tag_omitted ( parent . name , name ) ) {
210
176
parent . end = start ;
211
177
parser . pop ( ) ;
212
178
parser . last_auto_closed_tag = {
@@ -386,64 +352,34 @@ export default function element(parser) {
386
352
}
387
353
}
388
354
389
- const regex_whitespace_or_slash_or_closing_tag = / ( \s | \/ | > ) / ;
390
-
391
- /** @param {Parser } parser */
392
- function read_tag_name ( parser ) {
393
- const start = parser . index ;
394
-
395
- if ( parser . read ( SELF ) ) {
396
- // check we're inside a block, otherwise this
397
- // will cause infinite recursion
398
- let i = parser . stack . length ;
399
- let legal = false ;
400
-
401
- while ( i -- ) {
402
- const fragment = parser . stack [ i ] ;
403
- if (
404
- fragment . type === 'IfBlock' ||
405
- fragment . type === 'EachBlock' ||
406
- fragment . type === 'Component' ||
407
- fragment . type === 'SnippetBlock'
408
- ) {
409
- legal = true ;
410
- break ;
411
- }
412
- }
413
-
414
- if ( ! legal ) {
415
- e . svelte_self_invalid_placement ( start ) ;
416
- }
417
-
418
- return 'svelte:self' ;
419
- }
420
-
421
- if ( parser . read ( COMPONENT ) ) return 'svelte:component' ;
422
- if ( parser . read ( ELEMENT ) ) return 'svelte:element' ;
423
-
424
- if ( parser . read ( SLOT ) ) return 'svelte:fragment' ;
425
-
426
- const name = parser . read_until ( regex_whitespace_or_slash_or_closing_tag ) ;
427
-
428
- if ( meta_tags . has ( name ) ) return name ;
429
-
430
- if ( name . startsWith ( 'svelte:' ) ) {
431
- const list = `${ valid_meta_tags . slice ( 0 , - 1 ) . join ( ', ' ) } or ${ valid_meta_tags [ valid_meta_tags . length - 1 ] } ` ;
432
- e . svelte_meta_invalid_tag ( start , list ) ;
355
+ /** @param {Compiler.TemplateNode[] } stack */
356
+ function parent_is_head ( stack ) {
357
+ let i = stack . length ;
358
+ while ( i -- ) {
359
+ const { type } = stack [ i ] ;
360
+ if ( type === 'SvelteHead' ) return true ;
361
+ if ( type === 'RegularElement' || type === 'Component' ) return false ;
433
362
}
363
+ return false ;
364
+ }
434
365
435
- if ( ! valid_tag_name . test ( name ) ) {
436
- e . element_invalid_tag_name ( start ) ;
366
+ /** @param {Compiler.TemplateNode[] } stack */
367
+ function parent_is_shadowroot_template ( stack ) {
368
+ // https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#building_a_declarative_shadow_root
369
+ let i = stack . length ;
370
+ while ( i -- ) {
371
+ if (
372
+ stack [ i ] . type === 'RegularElement' &&
373
+ /** @type {Compiler.RegularElement } */ ( stack [ i ] ) . attributes . some (
374
+ ( a ) => a . type === 'Attribute' && a . name === 'shadowrootmode'
375
+ )
376
+ ) {
377
+ return true ;
378
+ }
437
379
}
438
-
439
- return name ;
380
+ return false ;
440
381
}
441
382
442
- // eslint-disable-next-line no-useless-escape
443
- const regex_token_ending_character = / [ \s = \/ > " ' ] / ;
444
- const regex_starts_with_quote_characters = / ^ [ " ' ] / ;
445
- const regex_attribute_value = / ^ (?: " ( [ ^ " ] * ) " | ' ( [ ^ ' ] ) * ' | ( [ ^ > \s ] + ) ) / ;
446
-
447
383
/**
448
384
* @param {Parser } parser
449
385
* @returns {Compiler.Attribute | null }
@@ -692,7 +628,7 @@ function read_attribute_value(parser) {
692
628
( ) => {
693
629
// handle common case of quote marks existing outside of regex for performance reasons
694
630
if ( quote_mark ) return parser . match ( quote_mark ) ;
695
- return ! ! parser . match_regex ( regex_starts_with_invalid_attr_value ) ;
631
+ return ! ! parser . match_regex ( regex_invalid_unquoted_attribute_value ) ;
696
632
} ,
697
633
'in attribute value'
698
634
) ;
0 commit comments