@@ -191,15 +191,24 @@ export default function tag(parser) {
191
191
} ;
192
192
}
193
193
194
- /** @type {Set< string> } */
195
- const unique_names = new Set ( ) ;
194
+ /** @type {string[] } */
195
+ const unique_names = [ ] ;
196
196
197
197
const current = parser . current ( ) ;
198
198
const is_top_level_script_or_style =
199
199
( name === 'script' || name === 'style' ) && current . type === 'Root' ;
200
200
201
+ const read = is_top_level_script_or_style ? read_static_attribute : read_attribute ;
202
+
201
203
let attribute ;
202
- while ( ( attribute = read_attribute ( parser , unique_names , is_top_level_script_or_style ) ) ) {
204
+ while ( ( attribute = read ( parser ) ) ) {
205
+ if (
206
+ ( attribute . type === 'Attribute' || attribute . type === 'BindDirective' ) &&
207
+ unique_names . includes ( attribute . name )
208
+ ) {
209
+ error ( attribute . start , 'duplicate-attribute' ) ;
210
+ }
211
+
203
212
element . attributes . push ( attribute ) ;
204
213
parser . allow_whitespace ( ) ;
205
214
}
@@ -376,22 +385,58 @@ const regex_starts_with_quote_characters = /^["']/;
376
385
377
386
/**
378
387
* @param {import('../index.js').Parser } parser
379
- * @param {Set<string> } unique_names
380
- * @param {boolean } is_static If `true`, `{` and `}` are not treated as delimiters for expressions
381
- * @returns {any }
388
+ * @returns {import('#compiler').Attribute | null }
382
389
*/
383
- function read_attribute ( parser , unique_names , is_static ) {
390
+ function read_static_attribute ( parser ) {
384
391
const start = parser . index ;
385
392
386
- /** @param {string } name */
387
- function check_unique ( name ) {
388
- if ( unique_names . has ( name ) ) {
389
- error ( start , 'duplicate-attribute' ) ;
393
+ const name = parser . read_until ( regex_token_ending_character ) ;
394
+ if ( ! name ) return null ;
395
+
396
+ /** @type {true | Array<import('#compiler').Text | import('#compiler').ExpressionTag> } */
397
+ let value = true ;
398
+
399
+ if ( parser . eat ( '=' ) ) {
400
+ parser . allow_whitespace ( ) ;
401
+ let raw = parser . match_regex ( regex_attribute_value ) ;
402
+ if ( ! raw ) {
403
+ error ( parser . index , 'missing-attribute-value' ) ;
404
+ }
405
+
406
+ parser . index += raw . length ;
407
+
408
+ const quoted = raw [ 0 ] === '"' || raw [ 0 ] === "'" ;
409
+ if ( quoted ) {
410
+ raw = raw . slice ( 1 , - 1 ) ;
390
411
}
391
- unique_names . add ( name ) ;
412
+
413
+ value = [
414
+ {
415
+ start : parser . index - raw . length - ( quoted ? 1 : 0 ) ,
416
+ end : quoted ? parser . index - 1 : parser . index ,
417
+ type : 'Text' ,
418
+ raw : raw ,
419
+ data : decode_character_references ( raw , true ) ,
420
+ parent : null
421
+ }
422
+ ] ;
392
423
}
393
424
394
- if ( ! is_static && parser . eat ( '{' ) ) {
425
+ if ( parser . match_regex ( regex_starts_with_quote_characters ) ) {
426
+ error ( parser . index , 'expected-token' , '=' ) ;
427
+ }
428
+
429
+ return create_attribute ( name , start , parser . index , value ) ;
430
+ }
431
+
432
+ /**
433
+ * @param {import('../index.js').Parser } parser
434
+ * @returns {import('#compiler').Attribute | import('#compiler').SpreadAttribute | import('#compiler').Directive | null }
435
+ */
436
+ function read_attribute ( parser ) {
437
+ const start = parser . index ;
438
+
439
+ if ( parser . eat ( '{' ) ) {
395
440
parser . allow_whitespace ( ) ;
396
441
397
442
if ( parser . eat ( '...' ) ) {
@@ -421,8 +466,6 @@ function read_attribute(parser, unique_names, is_static) {
421
466
error ( start , 'empty-attribute-shorthand' ) ;
422
467
}
423
468
424
- check_unique ( name ) ;
425
-
426
469
parser . allow_whitespace ( ) ;
427
470
parser . eat ( '}' , true ) ;
428
471
@@ -462,25 +505,19 @@ function read_attribute(parser, unique_names, is_static) {
462
505
let value = true ;
463
506
if ( parser . eat ( '=' ) ) {
464
507
parser . allow_whitespace ( ) ;
465
- value = read_attribute_value ( parser , is_static ) ;
508
+ value = read_attribute_value ( parser ) ;
466
509
end = parser . index ;
467
510
} else if ( parser . match_regex ( regex_starts_with_quote_characters ) ) {
468
511
error ( parser . index , 'expected-token' , '=' ) ;
469
512
}
470
513
471
- if ( ! is_static && type ) {
514
+ if ( type ) {
472
515
const [ directive_name , ...modifiers ] = name . slice ( colon_index + 1 ) . split ( '|' ) ;
473
516
474
517
if ( directive_name === '' ) {
475
518
error ( start + colon_index + 1 , 'empty-directive-name' , type ) ;
476
519
}
477
520
478
- if ( type === 'BindDirective' && directive_name !== 'this' ) {
479
- check_unique ( directive_name ) ;
480
- } else if ( type !== 'OnDirective' && type !== 'UseDirective' ) {
481
- check_unique ( name ) ;
482
- }
483
-
484
521
if ( type === 'StyleDirective' ) {
485
522
return {
486
523
start,
@@ -548,8 +585,6 @@ function read_attribute(parser, unique_names, is_static) {
548
585
return directive ;
549
586
}
550
587
551
- check_unique ( name ) ;
552
-
553
588
return create_attribute ( name , start , end , value ) ;
554
589
}
555
590
@@ -573,33 +608,8 @@ const regex_attribute_value = /^(?:"([^"]*)"|'([^'])*'|([^>\s]))/;
573
608
574
609
/**
575
610
* @param {import('../index.js').Parser } parser
576
- * @param {boolean } is_static If `true`, `{` and `}` are not treated as delimiters for expressions
577
611
*/
578
- function read_attribute_value ( parser , is_static ) {
579
- if ( is_static ) {
580
- let value = parser . match_regex ( regex_attribute_value ) ;
581
- if ( ! value ) {
582
- error ( parser . index , 'missing-attribute-value' ) ;
583
- }
584
-
585
- parser . index += value . length ;
586
-
587
- const quoted = value [ 0 ] === '"' || value [ 0 ] === "'" ;
588
- if ( quoted ) {
589
- value = value . slice ( 1 , - 1 ) ;
590
- }
591
-
592
- return [
593
- {
594
- start : parser . index - value . length - ( quoted ? 1 : 0 ) ,
595
- end : quoted ? parser . index - 1 : parser . index ,
596
- type : 'Text' ,
597
- raw : value ,
598
- data : decode_character_references ( value , true )
599
- }
600
- ] ;
601
- }
602
-
612
+ function read_attribute_value ( parser ) {
603
613
const quote_mark = parser . eat ( "'" ) ? "'" : parser . eat ( '"' ) ? '"' : null ;
604
614
if ( quote_mark && parser . eat ( quote_mark ) ) {
605
615
return [
0 commit comments