@@ -18,6 +18,26 @@ const {
1818const astUtils = require ( "./utils/ast-utils.js" ) ;
1919const { isValidWithUnicodeFlag } = require ( "./utils/regular-expressions" ) ;
2020
21+ /**
22+ * Checks whether the flag configuration should be treated as a missing flag.
23+ * @param {"u"|"v"|undefined } requireFlag A particular flag to require
24+ * @param {string } flags The regex flags
25+ * @returns {boolean } Whether the flag configuration results in a missing flag.
26+ */
27+ function checkFlags ( requireFlag , flags ) {
28+ let missingFlag ;
29+
30+ if ( requireFlag === "v" ) {
31+ missingFlag = ! flags . includes ( "v" ) ;
32+ } else if ( requireFlag === "u" ) {
33+ missingFlag = ! flags . includes ( "u" ) ;
34+ } else {
35+ missingFlag = ! flags . includes ( "u" ) && ! flags . includes ( "v" ) ;
36+ }
37+
38+ return missingFlag ;
39+ }
40+
2141//------------------------------------------------------------------------------
2242// Rule Definition
2343//------------------------------------------------------------------------------
@@ -37,31 +57,65 @@ module.exports = {
3757
3858 messages : {
3959 addUFlag : "Add the 'u' flag." ,
40- requireUFlag : "Use the 'u' flag."
60+ addVFlag : "Add the 'v' flag." ,
61+ requireUFlag : "Use the 'u' flag." ,
62+ requireVFlag : "Use the 'v' flag."
4163 } ,
4264
43- schema : [ ]
65+ schema : [
66+ {
67+ type : "object" ,
68+ properties : {
69+ requireFlag : {
70+ enum : [ "u" , "v" ]
71+ }
72+ } ,
73+ additionalProperties : false
74+ }
75+ ]
4476 } ,
4577
4678 create ( context ) {
4779
4880 const sourceCode = context . sourceCode ;
4981
82+ const {
83+ requireFlag
84+ } = context . options [ 0 ] ?? { } ;
85+
5086 return {
5187 "Literal[regex]" ( node ) {
5288 const flags = node . regex . flags || "" ;
5389
54- if ( ! flags . includes ( "u" ) && ! flags . includes ( "v" ) ) {
90+ const missingFlag = checkFlags ( requireFlag , flags ) ;
91+
92+ if ( missingFlag ) {
5593 context . report ( {
56- messageId : "requireUFlag" ,
94+ messageId : requireFlag === "v" ? "requireVFlag" : "requireUFlag" ,
5795 node,
58- suggest : isValidWithUnicodeFlag ( context . languageOptions . ecmaVersion , node . regex . pattern )
96+ suggest : isValidWithUnicodeFlag ( context . languageOptions . ecmaVersion , node . regex . pattern , requireFlag )
5997 ? [
6098 {
6199 fix ( fixer ) {
62- return fixer . insertTextAfter ( node , "u" ) ;
100+ const replaceFlag = requireFlag ?? "u" ;
101+ const regex = sourceCode . getText ( node ) ;
102+ const slashPos = regex . lastIndexOf ( "/" ) ;
103+
104+ if ( requireFlag ) {
105+ const flag = requireFlag === "u" ? "v" : "u" ;
106+
107+ if ( regex . includes ( flag , slashPos ) ) {
108+ return fixer . replaceText (
109+ node ,
110+ regex . slice ( 0 , slashPos ) +
111+ regex . slice ( slashPos ) . replace ( flag , requireFlag )
112+ ) ;
113+ }
114+ }
115+
116+ return fixer . insertTextAfter ( node , replaceFlag ) ;
63117 } ,
64- messageId : "addUFlag"
118+ messageId : requireFlag === "v" ? "addVFlag" : "addUFlag"
65119 }
66120 ]
67121 : null
@@ -85,22 +139,49 @@ module.exports = {
85139 const pattern = getStringIfConstant ( patternNode , scope ) ;
86140 const flags = getStringIfConstant ( flagsNode , scope ) ;
87141
88- if ( ! flagsNode || ( typeof flags === "string" && ! flags . includes ( "u" ) && ! flags . includes ( "v" ) ) ) {
142+ let missingFlag = ! flagsNode ;
143+
144+ if ( typeof flags === "string" ) {
145+ missingFlag = checkFlags ( requireFlag , flags ) ;
146+ }
147+
148+ if ( missingFlag ) {
89149 context . report ( {
90- messageId : "requireUFlag" ,
150+ messageId : requireFlag === "v" ? "requireVFlag" : "requireUFlag" ,
91151 node : refNode ,
92- suggest : typeof pattern === "string" && isValidWithUnicodeFlag ( context . languageOptions . ecmaVersion , pattern )
152+ suggest : typeof pattern === "string" && isValidWithUnicodeFlag ( context . languageOptions . ecmaVersion , pattern , requireFlag )
93153 ? [
94154 {
95155 fix ( fixer ) {
156+ const replaceFlag = requireFlag ?? "u" ;
157+
96158 if ( flagsNode ) {
97159 if ( ( flagsNode . type === "Literal" && typeof flagsNode . value === "string" ) || flagsNode . type === "TemplateLiteral" ) {
98160 const flagsNodeText = sourceCode . getText ( flagsNode ) ;
161+ const flag = requireFlag === "u" ? "v" : "u" ;
162+
163+ if ( flags . includes ( flag ) ) {
164+
165+ // Avoid replacing "u" in escapes like `\uXXXX`
166+ if ( flagsNode . type === "Literal" && flagsNode . raw . includes ( "\\" ) ) {
167+ return null ;
168+ }
169+
170+ // Avoid replacing "u" in expressions like "`${regularFlags}g`"
171+ if ( flagsNode . type === "TemplateLiteral" && (
172+ flagsNode . expressions . length ||
173+ flagsNode . quasis . some ( ( { value : { raw } } ) => raw . includes ( "\\" ) )
174+ ) ) {
175+ return null ;
176+ }
177+
178+ return fixer . replaceText ( flagsNode , flagsNodeText . replace ( flag , replaceFlag ) ) ;
179+ }
99180
100181 return fixer . replaceText ( flagsNode , [
101182 flagsNodeText . slice ( 0 , flagsNodeText . length - 1 ) ,
102183 flagsNodeText . slice ( flagsNodeText . length - 1 )
103- ] . join ( "u" ) ) ;
184+ ] . join ( replaceFlag ) ) ;
104185 }
105186
106187 // We intentionally don't suggest concatenating + "u" to non-literals
@@ -112,11 +193,11 @@ module.exports = {
112193 return fixer . insertTextAfter (
113194 penultimateToken ,
114195 astUtils . isCommaToken ( penultimateToken )
115- ? ' "u",'
116- : ' , "u"'
196+ ? ` " ${ replaceFlag } ",`
197+ : ` , "${ replaceFlag } "`
117198 ) ;
118199 } ,
119- messageId : "addUFlag"
200+ messageId : requireFlag === "v" ? "addVFlag" : "addUFlag"
120201 }
121202 ]
122203 : null
0 commit comments