Skip to content

Commit a05da96

Browse files
committed
fix(parser): Support escaped characters in attribute value
1 parent ff21060 commit a05da96

File tree

3 files changed

+217
-1
lines changed

3 files changed

+217
-1
lines changed

src/__fixtures__/tests.ts

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,4 +483,219 @@ export const tests: [string, Selector[][], string][] = [
483483
],
484484
"pseudo selector with data",
485485
],
486+
487+
/*
488+
* Bad attributes (taken from Sizzle)
489+
* https://github.com/jquery/sizzle/blob/af163873d7cdfc57f18b16c04b1915209533f0b1/test/unit/selector.js#L602-L651
490+
*/
491+
[
492+
"[id=types_all]",
493+
[
494+
[
495+
{
496+
type: "attribute",
497+
action: "equals",
498+
name: "id",
499+
value: "types_all",
500+
ignoreCase: false,
501+
},
502+
],
503+
],
504+
"Underscores don't need escaping",
505+
],
506+
[
507+
"[name=foo\\ bar]",
508+
[
509+
[
510+
{
511+
type: "attribute",
512+
action: "equals",
513+
name: "name",
514+
value: "foo bar",
515+
ignoreCase: false,
516+
},
517+
],
518+
],
519+
"Escaped space",
520+
],
521+
[
522+
"[name=foo\\.baz]",
523+
[
524+
[
525+
{
526+
type: "attribute",
527+
action: "equals",
528+
name: "name",
529+
value: "foo.baz",
530+
ignoreCase: false,
531+
},
532+
],
533+
],
534+
"Escaped dot",
535+
],
536+
[
537+
"[name=foo\\[baz\\]]",
538+
[
539+
[
540+
{
541+
type: "attribute",
542+
action: "equals",
543+
name: "name",
544+
value: "foo[baz]",
545+
ignoreCase: false,
546+
},
547+
],
548+
],
549+
"Escaped brackets",
550+
],
551+
[
552+
"[data-attr='foo_baz\\']']",
553+
[
554+
[
555+
{
556+
type: "attribute",
557+
action: "equals",
558+
name: "data-attr",
559+
value: "foo_baz']",
560+
ignoreCase: false,
561+
},
562+
],
563+
],
564+
"Escaped quote + right bracket",
565+
],
566+
[
567+
"[data-attr='\\'']",
568+
[
569+
[
570+
{
571+
type: "attribute",
572+
action: "equals",
573+
name: "data-attr",
574+
value: "'",
575+
ignoreCase: false,
576+
},
577+
],
578+
],
579+
"Quoted quote",
580+
],
581+
[
582+
"[data-attr='\\\\']",
583+
[
584+
[
585+
{
586+
type: "attribute",
587+
action: "equals",
588+
name: "data-attr",
589+
value: "\\",
590+
ignoreCase: false,
591+
},
592+
],
593+
],
594+
"Quoted backslash",
595+
],
596+
[
597+
"[data-attr='\\\\\\'']",
598+
[
599+
[
600+
{
601+
type: "attribute",
602+
action: "equals",
603+
name: "data-attr",
604+
value: "\\'",
605+
ignoreCase: false,
606+
},
607+
],
608+
],
609+
"Quoted backslash quote",
610+
],
611+
[
612+
"[data-attr='\\\\\\\\']",
613+
[
614+
[
615+
{
616+
type: "attribute",
617+
action: "equals",
618+
name: "data-attr",
619+
value: "\\\\",
620+
ignoreCase: false,
621+
},
622+
],
623+
],
624+
"Quoted backslash backslash",
625+
],
626+
[
627+
"[data-attr='\\5C\\\\']",
628+
[
629+
[
630+
{
631+
type: "attribute",
632+
action: "equals",
633+
name: "data-attr",
634+
value: "\\\\",
635+
ignoreCase: false,
636+
},
637+
],
638+
],
639+
"Quoted backslash backslash (numeric escape)",
640+
],
641+
[
642+
"[data-attr='\\5C \\\\']",
643+
[
644+
[
645+
{
646+
type: "attribute",
647+
action: "equals",
648+
name: "data-attr",
649+
value: "\\\\",
650+
ignoreCase: false,
651+
},
652+
],
653+
],
654+
"Quoted backslash backslash (numeric escape with trailing space)",
655+
],
656+
[
657+
"[data-attr='\\5C\t\\\\']",
658+
[
659+
[
660+
{
661+
type: "attribute",
662+
action: "equals",
663+
name: "data-attr",
664+
value: "\\\\",
665+
ignoreCase: false,
666+
},
667+
],
668+
],
669+
"Quoted backslash backslash (numeric escape with trailing tab)",
670+
],
671+
[
672+
"[data-attr='\\04e00']",
673+
[
674+
[
675+
{
676+
type: "attribute",
677+
action: "equals",
678+
name: "data-attr",
679+
value: "\u4e00",
680+
ignoreCase: false,
681+
},
682+
],
683+
],
684+
"Long numeric escape (BMP)",
685+
],
686+
[
687+
"[data-attr='\\01D306A']",
688+
[
689+
[
690+
{
691+
type: "attribute",
692+
action: "equals",
693+
name: "data-attr",
694+
value: "\uD834\uDF06A",
695+
ignoreCase: false,
696+
},
697+
],
698+
],
699+
"Long numeric escape (non-BMP)",
700+
],
486701
];

src/parse.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export type TraversalType =
7070
const reName = /^[^\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;
7171
const reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
7272
// Modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87
73-
const reAttr = /^\s*((?:\\.|[\w\u00b0-\uFFFF-])+)\s*(?:(\S?)=\s*(?:(['"])([^]*?)\3|(#?(?:\\.|[\w\u00b0-\uFFFF-])*)|)|)\s*(i)?\]/;
73+
const reAttr = /^\s*((?:\\.|[\w\u00b0-\uFFFF-])+)\s*(?:(\S?)=\s*(?:(['"])((?:[^\\]|\\[^])*?)\3|(#?(?:\\.|[\w\u00b0-\uFFFF-])*)|)|)\s*(i)?\]/;
7474

7575
const actionTypes: { [key: string]: AttributeAction } = {
7676
undefined: "exists",

src/stringify.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const charsToEscape = new Set([
1616
"[",
1717
"]",
1818
" ",
19+
"\\",
1920
]);
2021

2122
export default function stringify(token: Selector[][]): string {

0 commit comments

Comments
 (0)