Skip to content

Commit ff73d8b

Browse files
committed
feat: suggestions
1 parent 9373b88 commit ff73d8b

File tree

1 file changed

+83
-26
lines changed

1 file changed

+83
-26
lines changed

lib/rules/invalid-regex-rule.js

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Copyright (c) 2020 Gonzalo Müller Bravo.
22
// Licensed under the MIT License (MIT), see LICENSE.txt
3-
const { buildCreateFunction } = require('../utils/create-utils.js')
4-
const { formatReportMessage } = require('../utils/report-utils.js')
5-
const { shouldCheck } = require('../utils/check-utils.js')
3+
const {buildCreateFunction} = require('../utils/create-utils.js')
4+
const {formatReportMessage} = require('../utils/report-utils.js')
5+
const {shouldCheck} = require('../utils/check-utils.js')
66

7-
const { REGEX_FLAGS_FIELD_DEFINITION, FILES_FIELD_DEFINITION } = require('./common-fields-definitions.js')
7+
const {REGEX_FLAGS_FIELD_DEFINITION, FILES_FIELD_DEFINITION} = require('./common-fields-definitions.js')
88

99
/**
1010
* @param {string} source Text that was checked.
@@ -14,9 +14,9 @@ const { REGEX_FLAGS_FIELD_DEFINITION, FILES_FIELD_DEFINITION } = require('./comm
1414
function foundStartLocation(source, foundAt) {
1515
return Array.from(source.substring(0, foundAt))
1616
.reduce((result, char) => char !== '\n'
17-
? { line: result.line, column: result.column + 1 }
18-
: { line: result.line + 1, column: 0 },
19-
{ line: 1, column: 0 })
17+
? {line: result.line, column: result.column + 1}
18+
: {line: result.line + 1, column: 0},
19+
{line: 1, column: 0})
2020
}
2121

2222
/**
@@ -28,8 +28,9 @@ function foundStartLocation(source, foundAt) {
2828
* @param {{ regex: RegExp, id: string | undefined, message: string | undefined }} pattern Information of the pattern to be use to inspect.
2929
* @param {*} replace Replacement function
3030
* @param {Function} report Report function
31+
* @param {*[]} [suggestions] Array of suggestion functions.
3132
*/
32-
function checkRegex(source, pattern, replace, report) {
33+
function checkRegex(source, pattern, replace, report, suggestions = []) {
3334
/**
3435
* @param {string} source Text to be checked.
3536
* @param {RegExp} regex Regular Expression use to check.
@@ -48,12 +49,16 @@ function checkRegex(source, pattern, replace, report) {
4849
from => `Invalid regular expression ${from} found`
4950
)
5051
pattern.regex.test('')
51-
let matchDetail = { matchStart: -1 }
52-
while(matchDetail.matchStart !== matchDetail.nextChar && (matchDetail = checkRegexInSource())) {
52+
let matchDetail = {matchStart: -1}
53+
while (matchDetail.matchStart !== matchDetail.nextChar && (matchDetail = checkRegexInSource())) {
5354
report({
54-
loc: { start: foundStartLocation(source, matchDetail.matchStart) },
55+
loc: {start: foundStartLocation(source, matchDetail.matchStart)},
5556
message,
56-
fix: replace(0, matchDetail)
57+
fix: replace(0, matchDetail),
58+
suggest: suggestions.map(s => ({
59+
desc: s.message,
60+
fix: s.replacement(0, matchDetail),
61+
})),
5762
})
5863
}
5964
}
@@ -68,11 +73,12 @@ function buildReplacementFunction(replacement) {
6873
return replacement
6974
}
7075
}
71-
catch(e) {}
76+
catch (e) {
77+
}
7278
return $[0]
7379
}
7480
}
75-
catch(e) {
81+
catch (e) {
7682
return null
7783
}
7884
}
@@ -91,7 +97,7 @@ function createReplacement(replacement) {
9197
switch (typeof replacement) {
9298
case 'string':
9399
return (from, matchDetail) => fixer => fixer.replaceTextRange([from + matchDetail.matchStart, from + matchDetail.nextChar], replacement)
94-
case 'object':{
100+
case 'object': {
95101
const replacementFunction = buildReplacementFunction(replacement.function)
96102
if (typeof replacementFunction === 'function') {
97103
return (from, matchDetail) =>
@@ -106,14 +112,27 @@ function createReplacement(replacement) {
106112
}
107113

108114
function checkPatterns(fileName, source, patterns, report) {
109-
patterns.forEach(pattern => shouldCheck(pattern.files, fileName) &&
110-
checkRegex(source, pattern, createReplacement(pattern.details.replacement), report))
115+
patterns.forEach(pattern => {
116+
if (!shouldCheck(pattern.files, fileName)) {
117+
return
118+
}
119+
120+
const replacement = createReplacement(pattern.details.replacement)
121+
let suggestions = pattern.details.suggestions || []
122+
suggestions = suggestions.map((d) => ({
123+
message: d.message,
124+
replacement: createReplacement(d.replacement)
125+
}))
126+
127+
checkRegex(source, pattern, replacement, report, suggestions)
128+
})
111129
}
112130

113131
module.exports = {
114132
meta: {
115133
type: 'suggestion',
116134
fixable: 'code',
135+
hasSuggestions: true,
117136
docs: {
118137
description: 'Invalid regular expressions to be reported',
119138
category: 'Stylistic Issues',
@@ -148,7 +167,7 @@ module.exports = {
148167
},
149168
flags: REGEX_FLAGS_FIELD_DEFINITION,
150169
replacement: {
151-
oneOf:[{
170+
oneOf: [{
152171
title: 'Replacement',
153172
description: 'Replacement for invalid pattern',
154173
type: 'string'
@@ -168,15 +187,53 @@ module.exports = {
168187
maxProperties: 1
169188
}]
170189
},
171-
message: {
172-
title: 'Invalid message',
173-
description: 'Message to be shown when Invalid pattern is found',
174-
type: 'string',
175-
minLength: 3
190+
suggestions: {
191+
type: 'array',
192+
description: 'Array of suggestions to be used when the pattern is found',
193+
title: 'Suggestions',
194+
items: {
195+
type: 'object',
196+
description: 'Suggestion to be used when the pattern is found',
197+
properties: {
198+
message: {
199+
title: 'Suggestion message',
200+
description: 'Message to be shown when the suggestion is applied',
201+
type: 'string',
202+
minLength: 3
203+
},
204+
replacement: {
205+
oneOf: [{
206+
title: 'Replacement',
207+
description: 'Replacement for invalid pattern',
208+
type: 'string'
209+
}, {
210+
title: 'Detailed replacement',
211+
description: 'Detailed replacements for invalid patterns',
212+
type: 'object',
213+
properties: {
214+
function: {
215+
title: 'Replacement function',
216+
description: 'Function used to replace the found pattern. It receives the found text and must return the replacement text',
217+
type: 'string',
218+
minLength: 1
219+
}
220+
},
221+
minProperties: 1,
222+
maxProperties: 1
223+
}]
224+
}
225+
},
226+
},
227+
message: {
228+
title: 'Invalid message',
229+
description: 'Message to be shown when Invalid pattern is found',
230+
type: 'string',
231+
minLength: 3
232+
},
233+
files: FILES_FIELD_DEFINITION
176234
},
177-
files: FILES_FIELD_DEFINITION
178-
},
179-
required: ['regex']
235+
required: ['regex']
236+
}
180237
}]
181238
},
182239
minItems: 1

0 commit comments

Comments
 (0)