@@ -13,12 +13,13 @@ import {
1313 CP_DIGIT_NINE ,
1414 CP_LOW_LINE ,
1515} from "../utils"
16+ import { Chars } from "regexp-ast-analysis"
1617
1718/**
1819 * Checks if small letter char class range
1920 * @param node The node to check
2021 */
21- function isSmallLetterCharacterClassRange ( node : CharacterClassElement ) {
22+ function isSmallLetterRange ( node : CharacterClassElement ) {
2223 return (
2324 node . type === "CharacterClassRange" &&
2425 node . min . value === CP_SMALL_A &&
@@ -30,7 +31,7 @@ function isSmallLetterCharacterClassRange(node: CharacterClassElement) {
3031 * Checks if capital letter char class range
3132 * @param node The node to check
3233 */
33- function isCapitalLetterCharacterClassRange ( node : CharacterClassElement ) {
34+ function isCapitalLetterRange ( node : CharacterClassElement ) {
3435 return (
3536 node . type === "CharacterClassRange" &&
3637 node . min . value === CP_CAPITAL_A &&
@@ -42,7 +43,7 @@ function isCapitalLetterCharacterClassRange(node: CharacterClassElement) {
4243 * Checks if digit char class
4344 * @param node The node to check
4445 */
45- function isDigitCharacterClass ( node : CharacterClassElement ) {
46+ function isDigitRangeOrSet ( node : CharacterClassElement ) {
4647 return (
4748 ( node . type === "CharacterClassRange" &&
4849 node . min . value === CP_DIGIT_ZERO &&
@@ -55,7 +56,7 @@ function isDigitCharacterClass(node: CharacterClassElement) {
5556 * Checks if includes `_`
5657 * @param node The node to check
5758 */
58- function includesLowLineCharacterClass ( node : CharacterClassElement ) {
59+ function isUnderscoreCharacter ( node : CharacterClassElement ) {
5960 return node . type === "Character" && node . value === CP_LOW_LINE
6061}
6162
@@ -84,93 +85,100 @@ export default createRule("prefer-w", {
8485 fixReplaceNode,
8586 getRegexpRange,
8687 fixerApplyEscape,
88+ toCharSet,
8789 } : RegExpContext ) : RegExpVisitor . Handlers {
8890 return {
8991 onCharacterClassEnter ( ccNode : CharacterClass ) {
92+ const charSet = toCharSet ( ccNode )
93+
94+ let predefined : string | undefined = undefined
95+ if ( charSet . equals ( Chars . word ( flags ) ) ) {
96+ predefined = "\\w"
97+ } else if ( charSet . equals ( Chars . word ( flags ) . negate ( ) ) ) {
98+ predefined = "\\W"
99+ }
100+
101+ if ( predefined ) {
102+ context . report ( {
103+ node,
104+ loc : getRegexpLocation ( ccNode ) ,
105+ messageId : "unexpected" ,
106+ data : {
107+ type : "character class" ,
108+ expr : ccNode . raw ,
109+ instead : predefined ,
110+ } ,
111+ fix : fixReplaceNode ( ccNode , predefined ) ,
112+ } )
113+ return
114+ }
115+
90116 const lowerAToZ : CharacterClassElement [ ] = [ ]
91117 const capitalAToZ : CharacterClassElement [ ] = [ ]
92118 const digit : CharacterClassElement [ ] = [ ]
93- const lowLine : CharacterClassElement [ ] = [ ]
119+ const underscore : CharacterClassElement [ ] = [ ]
94120 for ( const element of ccNode . elements ) {
95- if ( isSmallLetterCharacterClassRange ( element ) ) {
121+ if ( isSmallLetterRange ( element ) ) {
96122 lowerAToZ . push ( element )
97123 if ( flags . ignoreCase ) {
98124 capitalAToZ . push ( element )
99125 }
100- } else if (
101- isCapitalLetterCharacterClassRange ( element )
102- ) {
126+ } else if ( isCapitalLetterRange ( element ) ) {
103127 capitalAToZ . push ( element )
104128 if ( flags . ignoreCase ) {
105129 lowerAToZ . push ( element )
106130 }
107- } else if ( isDigitCharacterClass ( element ) ) {
131+ } else if ( isDigitRangeOrSet ( element ) ) {
108132 digit . push ( element )
109- } else if ( includesLowLineCharacterClass ( element ) ) {
110- lowLine . push ( element )
133+ } else if ( isUnderscoreCharacter ( element ) ) {
134+ underscore . push ( element )
111135 }
112136 }
137+
113138 if (
114139 lowerAToZ . length &&
115140 capitalAToZ . length &&
116141 digit . length &&
117- lowLine . length
142+ underscore . length
118143 ) {
119144 const unexpectedElements = [
120145 ...new Set ( [
121146 ...lowerAToZ ,
122147 ...capitalAToZ ,
123148 ...digit ,
124- ...lowLine ,
149+ ...underscore ,
125150 ] ) ,
126151 ] . sort ( ( a , b ) => a . start - b . start )
127152
128- if (
129- ccNode . elements . length === unexpectedElements . length
130- ) {
131- const instead = ccNode . negate ? "\\W" : "\\w"
132- context . report ( {
133- node,
134- loc : getRegexpLocation ( ccNode ) ,
135- messageId : "unexpected" ,
136- data : {
137- type : "character class" ,
138- expr : ccNode . raw ,
139- instead,
140- } ,
141- fix : fixReplaceNode ( ccNode , instead ) ,
142- } )
143- } else {
144- context . report ( {
145- node,
146- loc : getRegexpLocation ( ccNode ) ,
147- messageId : "unexpected" ,
148- data : {
149- type : "character class ranges" ,
150- expr : `[${ unexpectedElements
151- . map ( ( e ) => e . raw )
152- . join ( "" ) } ]`,
153- instead : "\\w" ,
154- } ,
155- * fix ( fixer : Rule . RuleFixer ) {
156- const range = getRegexpRange ( ccNode )
157- if ( range == null ) {
158- return
159- }
160- yield fixer . replaceTextRange (
161- getRegexpRange (
162- unexpectedElements . shift ( ) ! ,
163- ) ! ,
164- fixerApplyEscape ( "\\w" ) ,
153+ context . report ( {
154+ node,
155+ loc : getRegexpLocation ( ccNode ) ,
156+ messageId : "unexpected" ,
157+ data : {
158+ type : "character class ranges" ,
159+ expr : `[${ unexpectedElements
160+ . map ( ( e ) => e . raw )
161+ . join ( "" ) } ]`,
162+ instead : "\\w" ,
163+ } ,
164+ * fix ( fixer : Rule . RuleFixer ) {
165+ const range = getRegexpRange ( ccNode )
166+ if ( range == null ) {
167+ return
168+ }
169+ yield fixer . replaceTextRange (
170+ getRegexpRange (
171+ unexpectedElements . shift ( ) ! ,
172+ ) ! ,
173+ fixerApplyEscape ( "\\w" ) ,
174+ )
175+ for ( const element of unexpectedElements ) {
176+ yield fixer . removeRange (
177+ getRegexpRange ( element ) ! ,
165178 )
166- for ( const element of unexpectedElements ) {
167- yield fixer . removeRange (
168- getRegexpRange ( element ) ! ,
169- )
170- }
171- } ,
172- } )
173- }
179+ }
180+ } ,
181+ } )
174182 }
175183 } ,
176184 }
0 commit comments