|
1 |
| -import type { RegExpVisitor } from "regexpp/visitor" |
2 |
| -import type { |
3 |
| - CharacterClass, |
4 |
| - CharacterClassElement, |
5 |
| - UnicodePropertyCharacterSet, |
6 |
| -} from "regexpp/ast" |
7 |
| -import type { RegExpContext } from "../utils" |
8 |
| -import { |
9 |
| - CP_DIGIT_ZERO, |
10 |
| - CP_SPACE, |
11 |
| - createRule, |
12 |
| - defineRegexpVisitor, |
13 |
| -} from "../utils" |
| 1 | +import { createRule } from "../utils" |
14 | 2 |
|
15 |
| -type CharacterClassElementKind = "\\w" | "\\d" | "\\s" | "\\p" | "*" |
16 |
| -const DEFAULT_ORDER: CharacterClassElementKind[] = [ |
17 |
| - "\\s", |
18 |
| - "\\w", |
19 |
| - "\\d", |
20 |
| - "\\p", |
21 |
| - "*", |
22 |
| -] |
23 |
| - |
24 |
| -/** |
25 |
| - * Get kind of CharacterClassElement for given CharacterClassElement |
26 |
| - */ |
27 |
| -function getCharacterClassElementKind( |
28 |
| - node: CharacterClassElement, |
29 |
| -): CharacterClassElementKind { |
30 |
| - if (node.type === "CharacterSet") { |
31 |
| - return node.kind === "word" |
32 |
| - ? "\\w" |
33 |
| - : node.kind === "digit" |
34 |
| - ? "\\d" |
35 |
| - : node.kind === "space" |
36 |
| - ? "\\s" |
37 |
| - : "\\p" |
38 |
| - } |
39 |
| - return "*" |
40 |
| -} |
| 3 | +import sortCharacterClassElements from "./sort-character-class-elements" |
41 | 4 |
|
42 | 5 | export default createRule("order-in-character-class", {
|
43 | 6 | meta: {
|
| 7 | + ...sortCharacterClassElements.meta, |
44 | 8 | docs: {
|
45 |
| - description: "enforces elements order in character class", |
46 |
| - category: "Stylistic Issues", |
| 9 | + ...sortCharacterClassElements.meta.docs, |
47 | 10 | recommended: false,
|
| 11 | + replacedBy: ["sort-character-class-elements"], |
48 | 12 | },
|
49 |
| - fixable: "code", |
50 |
| - schema: [ |
51 |
| - { |
52 |
| - type: "object", |
53 |
| - properties: { |
54 |
| - order: { |
55 |
| - type: "array", |
56 |
| - items: { enum: ["\\w", "\\d", "\\s", "\\p", "*"] }, |
57 |
| - }, |
58 |
| - }, |
59 |
| - additionalProperties: false, |
60 |
| - }, |
61 |
| - ], |
62 |
| - messages: { |
63 |
| - sortElements: |
64 |
| - "Expected character class elements to be in ascending order. '{{next}}' should be before '{{prev}}'.", |
65 |
| - }, |
66 |
| - type: "layout", |
| 13 | + // TODO Switch to deprecated in the major version. |
| 14 | + // deprecated: true, |
67 | 15 | },
|
68 | 16 | create(context) {
|
69 |
| - const orderOption: { |
70 |
| - "*": number |
71 |
| - "\\w"?: number |
72 |
| - "\\d"?: number |
73 |
| - "\\s"?: number |
74 |
| - "\\p"?: number |
75 |
| - } = { "*": Infinity } |
76 |
| - |
77 |
| - ;((context.options[0]?.order ?? |
78 |
| - DEFAULT_ORDER) as CharacterClassElementKind[]).forEach((o, i) => { |
79 |
| - orderOption[o] = i + 1 |
80 |
| - }) |
81 |
| - |
82 |
| - /** |
83 |
| - * Create visitor |
84 |
| - */ |
85 |
| - function createVisitor({ |
86 |
| - node, |
87 |
| - fixerApplyEscape, |
88 |
| - getRegexpLocation, |
89 |
| - getRegexpRange, |
90 |
| - }: RegExpContext): RegExpVisitor.Handlers { |
91 |
| - return { |
92 |
| - onCharacterClassEnter(ccNode) { |
93 |
| - const prevList: CharacterClassElement[] = [] |
94 |
| - for (const next of ccNode.elements) { |
95 |
| - if (prevList.length) { |
96 |
| - const prev = prevList[0] |
97 |
| - if (!isValidOrder(prev, next)) { |
98 |
| - let moveTarget = prev |
99 |
| - for (const p of prevList) { |
100 |
| - if (isValidOrder(p, next)) { |
101 |
| - break |
102 |
| - } else { |
103 |
| - moveTarget = p |
104 |
| - } |
105 |
| - } |
106 |
| - context.report({ |
107 |
| - node, |
108 |
| - loc: getRegexpLocation(next), |
109 |
| - messageId: "sortElements", |
110 |
| - data: { |
111 |
| - next: next.raw, |
112 |
| - prev: moveTarget.raw, |
113 |
| - }, |
114 |
| - *fix(fixer) { |
115 |
| - const nextRange = getRegexpRange(next) |
116 |
| - const targetRange = getRegexpRange( |
117 |
| - moveTarget, |
118 |
| - ) |
119 |
| - if (!targetRange || !nextRange) { |
120 |
| - return |
121 |
| - } |
122 |
| - |
123 |
| - yield fixer.insertTextBeforeRange( |
124 |
| - targetRange, |
125 |
| - fixerApplyEscape( |
126 |
| - escapeRaw(next, moveTarget), |
127 |
| - ), |
128 |
| - ) |
129 |
| - |
130 |
| - yield fixer.removeRange(nextRange) |
131 |
| - }, |
132 |
| - }) |
133 |
| - } |
134 |
| - } |
135 |
| - prevList.unshift(next) |
136 |
| - } |
137 |
| - }, |
138 |
| - } |
139 |
| - } |
140 |
| - |
141 |
| - /* eslint-disable complexity -- X( */ |
142 |
| - /** |
143 |
| - * Check that the two given CharacterClassElements are in a valid order. |
144 |
| - */ |
145 |
| - function isValidOrder( |
146 |
| - /* eslint-enable complexity -- X( */ |
147 |
| - prev: CharacterClassElement, |
148 |
| - next: CharacterClassElement, |
149 |
| - ) { |
150 |
| - const prevKind = getCharacterClassElementKind(prev) |
151 |
| - const nextKind = getCharacterClassElementKind(next) |
152 |
| - const prevOrder = orderOption[prevKind] ?? orderOption["*"] |
153 |
| - const nextOrder = orderOption[nextKind] ?? orderOption["*"] |
154 |
| - if (prevOrder < nextOrder) { |
155 |
| - return true |
156 |
| - } else if (prevOrder > nextOrder) { |
157 |
| - return false |
158 |
| - } |
159 |
| - if (prev.type === "CharacterSet" && prev.kind === "property") { |
160 |
| - if (next.type === "CharacterSet") { |
161 |
| - if (next.kind === "property") { |
162 |
| - return isValidOrderForUnicodePropertyCharacterSet( |
163 |
| - prev, |
164 |
| - next, |
165 |
| - ) |
166 |
| - } |
167 |
| - // e.g. /[\p{ASCII}\d]/ |
168 |
| - return false |
169 |
| - } |
170 |
| - // e.g. /[\p{ASCII}a]/ |
171 |
| - return true |
172 |
| - } else if ( |
173 |
| - next.type === "CharacterSet" && |
174 |
| - next.kind === "property" |
175 |
| - ) { |
176 |
| - if (prev.type === "CharacterSet") { |
177 |
| - // e.g. /[\d\p{ASCII}]/ |
178 |
| - return true |
179 |
| - } |
180 |
| - // e.g. /[a\p{ASCII}]/ |
181 |
| - return false |
182 |
| - } |
183 |
| - if (prev.type === "CharacterSet" && next.type === "CharacterSet") { |
184 |
| - if (prev.kind === "word" && next.kind === "digit") { |
185 |
| - return true |
186 |
| - } |
187 |
| - if (prev.kind === "digit" && next.kind === "word") { |
188 |
| - return false |
189 |
| - } |
190 |
| - } |
191 |
| - const prevCP = getTargetCodePoint(prev) |
192 |
| - const nextCP = getTargetCodePoint(next) |
193 |
| - if (prevCP <= nextCP) { |
194 |
| - return true |
195 |
| - } |
196 |
| - return false |
197 |
| - } |
198 |
| - |
199 |
| - /** |
200 |
| - * Check that the two given UnicodePropertyCharacterSet are in a valid order. |
201 |
| - */ |
202 |
| - function isValidOrderForUnicodePropertyCharacterSet( |
203 |
| - prev: UnicodePropertyCharacterSet, |
204 |
| - next: UnicodePropertyCharacterSet, |
205 |
| - ) { |
206 |
| - if (prev.key < next.key) { |
207 |
| - return true |
208 |
| - } else if (prev.key > next.key) { |
209 |
| - return false |
210 |
| - } |
211 |
| - if (prev.value) { |
212 |
| - if (next.value) { |
213 |
| - if (prev.value <= next.value) { |
214 |
| - return true |
215 |
| - } |
216 |
| - return false |
217 |
| - } |
218 |
| - return false |
219 |
| - } |
220 |
| - return true |
221 |
| - } |
222 |
| - |
223 |
| - /** |
224 |
| - * Gets the target code point for a given element. |
225 |
| - */ |
226 |
| - function getTargetCodePoint( |
227 |
| - node: Exclude<CharacterClassElement, UnicodePropertyCharacterSet>, |
228 |
| - ) { |
229 |
| - if (node.type === "CharacterSet") { |
230 |
| - if (node.kind === "digit" || node.kind === "word") { |
231 |
| - return CP_DIGIT_ZERO |
232 |
| - } |
233 |
| - if (node.kind === "space") { |
234 |
| - return CP_SPACE |
235 |
| - } |
236 |
| - return Infinity |
237 |
| - } |
238 |
| - if (node.type === "CharacterClassRange") { |
239 |
| - return node.min.value |
240 |
| - } |
241 |
| - return node.value |
242 |
| - } |
243 |
| - |
244 |
| - return defineRegexpVisitor(context, { |
245 |
| - createVisitor, |
246 |
| - }) |
| 17 | + return sortCharacterClassElements.create(context) |
247 | 18 | },
|
248 | 19 | })
|
249 |
| - |
250 |
| -/** |
251 |
| - * get the escape text from the given CharacterClassElement. |
252 |
| - */ |
253 |
| -function escapeRaw(node: CharacterClassElement, target: CharacterClassElement) { |
254 |
| - let raw = node.raw |
255 |
| - if (raw.startsWith("-")) { |
256 |
| - const parent = target.parent as CharacterClass |
257 |
| - const prev = parent.elements[parent.elements.indexOf(target) - 1] |
258 |
| - if ( |
259 |
| - prev && |
260 |
| - (prev.type === "Character" || prev.type === "CharacterSet") |
261 |
| - ) { |
262 |
| - raw = `\\${raw}` |
263 |
| - } |
264 |
| - } |
265 |
| - if (target.raw.startsWith("-")) { |
266 |
| - if (node.type === "Character" || node.type === "CharacterSet") { |
267 |
| - raw = `${raw}\\` |
268 |
| - } |
269 |
| - } |
270 |
| - return raw |
271 |
| -} |
0 commit comments