Skip to content

Commit

Permalink
update regex-shorthand to use regexp-tree for regex literals
Browse files Browse the repository at this point in the history
  • Loading branch information
jdanil committed Nov 16, 2019
1 parent 9e0db0b commit 99c7bd1
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 50 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -44,6 +44,7 @@
"lodash.topairs": "^4.3.0",
"lodash.upperfirst": "^4.3.1",
"read-pkg-up": "^7.0.0",
"regexp-tree": "^0.1.16",
"regexpp": "^3.0.0",
"reserved-words": "^0.1.2",
"safe-regex": "^2.1.1",
Expand Down
63 changes: 39 additions & 24 deletions rules/regex-shorthand.js
@@ -1,5 +1,6 @@
'use strict';
const cleanRegexp = require('clean-regexp');
const {generate, optimize, parse} = require('regexp-tree');
const getDocumentationUrl = require('./utils/get-documentation-url');
const quoteString = require('./utils/quote-string');

Expand All @@ -8,23 +9,46 @@ const message = 'Use regex shorthands to improve readability.';
const create = context => {
return {
'Literal[regex]': node => {
const oldPattern = node.regex.pattern;
const {flags} = node.regex;
const {type, value} = context.getSourceCode().getFirstToken(node);

const newPattern = cleanRegexp(oldPattern, flags);

// Handle regex literal inside RegExp constructor in the other handler
if (node.parent.type === 'NewExpression' && node.parent.callee.name === 'RegExp') {
if (type !== 'RegularExpression') {
return;
}

if (oldPattern !== newPattern) {
let parsedSource;
try {
parsedSource = parse(value);
} catch (error) {
context.report({
node,
message,
fix: fixer => fixer.replaceText(node, `/${newPattern}/${flags}`)
message: '{{original}} can\'t be parsed: {{message}}',
data: {
original: value,
message: error.message
}
});

return;
}

const originalRegex = generate(parsedSource).toString();
const optimizedRegex = optimize(value).toString();

if (originalRegex === optimizedRegex) {
return;
}

context.report({
node,
message: '{{original}} can be optimized to {{optimized}}',
data: {
original: value,
optimized: optimizedRegex
},
fix(fixer) {
return fixer.replaceText(node, optimizedRegex);
}
});
},
'NewExpression[callee.name="RegExp"]': node => {
const arguments_ = node.arguments;
Expand All @@ -35,27 +59,18 @@ const create = context => {

const hasRegExp = arguments_[0].regex;

let oldPattern;
let flags;
if (hasRegExp) {
oldPattern = arguments_[0].regex.pattern;
flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : arguments_[0].regex.flags;
} else {
oldPattern = arguments_[0].value;
flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : '';
return;
}

const oldPattern = arguments_[0].value;
const flags = arguments_[1] && arguments_[1].type === 'Literal' ? arguments_[1].value : '';

const newPattern = cleanRegexp(oldPattern, flags);

if (oldPattern !== newPattern) {
let fixed;
if (hasRegExp) {
fixed = `/${newPattern}/`;
} else {
// Escape backslash
fixed = (newPattern || '').replace(/\\/g, '\\\\');
fixed = quoteString(fixed);
}
// Escape backslash
const fixed = quoteString((newPattern || '').replace(/\\/g, '\\\\'));

context.report({
node,
Expand Down
134 changes: 108 additions & 26 deletions test/regex-shorthand.js
Expand Up @@ -23,7 +23,6 @@ ruleTester.run('regex-shorthand', rule, {
'const foo = /\\w/ig',
'const foo = /[a-z]/ig',
'const foo = /\\d*?/ig',
'const foo = /[a-z0-9_]/',
'const foo = new RegExp(\'\\d\')',
'const foo = new RegExp(\'\\d\', \'ig\')',
'const foo = new RegExp(\'\\d*?\')',
Expand All @@ -32,13 +31,15 @@ ruleTester.run('regex-shorthand', rule, {
'const foo = new RegExp(/\\d/ig)',
'const foo = new RegExp(/\\d/, \'ig\')',
'const foo = new RegExp(/\\d*?/)',
'const foo = new RegExp(/[a-z]/, \'i\')',
'const foo = new RegExp(/^[^*]*[*]?$/)'
'const foo = new RegExp(/[a-z]/, \'i\')'
],
invalid: [
{
code: 'const foo = /[0-9]/',
errors: [error],
errors: [{
...error,
message: '/[0-9]/ can be optimized to /\\d/'
}],
output: 'const foo = /\\d/'
},
{
Expand All @@ -58,8 +59,11 @@ ruleTester.run('regex-shorthand', rule, {
},
{
code: 'const foo = /[0-9]/ig',
errors: [error],
output: 'const foo = /\\d/ig'
errors: [{
...error,
message: '/[0-9]/ig can be optimized to /\\d/gi'
}],
output: 'const foo = /\\d/gi'
},
{
code: 'const foo = new RegExp(\'[0-9]\', \'ig\')',
Expand All @@ -68,93 +72,171 @@ ruleTester.run('regex-shorthand', rule, {
},
{
code: 'const foo = /[^0-9]/',
errors: [error],
errors: [{
...error,
message: '/[^0-9]/ can be optimized to /\\D/'
}],
output: 'const foo = /\\D/'
},
{
code: 'const foo = /[A-Za-z0-9_]/',
errors: [error],
errors: [{
...error,
message: '/[A-Za-z0-9_]/ can be optimized to /\\w/'
}],
output: 'const foo = /\\w/'
},
{
code: 'const foo = /[A-Za-z\\d_]/',
errors: [error],
errors: [{
...error,
message: '/[A-Za-z\\d_]/ can be optimized to /\\w/'
}],
output: 'const foo = /\\w/'
},
{
code: 'const foo = /[a-zA-Z0-9_]/',
errors: [error],
errors: [{
...error,
message: '/[a-zA-Z0-9_]/ can be optimized to /\\w/'
}],
output: 'const foo = /\\w/'
},
{
code: 'const foo = /[a-zA-Z\\d_]/',
errors: [error],
errors: [{
...error,
message: '/[a-zA-Z\\d_]/ can be optimized to /\\w/'
}],
output: 'const foo = /\\w/'
},
{
code: 'const foo = /[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/',
errors: [error],
errors: [{
...error,
message: '/[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/ can be optimized to /\\w+\\d?\\.\\w*/'
}],
output: 'const foo = /\\w+\\d?\\.\\w*/'
},
{
code: 'const foo = /[a-z0-9_]/i',
errors: [error],
errors: [{
...error,
message: '/[a-z0-9_]/i can be optimized to /\\w/i'
}],
output: 'const foo = /\\w/i'
},
{
code: 'const foo = /[a-z\\d_]/i',
errors: [error],
errors: [{
...error,
message: '/[a-z\\d_]/i can be optimized to /\\w/i'
}],
output: 'const foo = /\\w/i'
},
{
code: 'const foo = /[^A-Za-z0-9_]/',
errors: [error],
errors: [{
...error,
message: '/[^A-Za-z0-9_]/ can be optimized to /\\W/'
}],
output: 'const foo = /\\W/'
},
{
code: 'const foo = /[^A-Za-z\\d_]/',
errors: [error],
errors: [{
...error,
message: '/[^A-Za-z\\d_]/ can be optimized to /\\W/'
}],
output: 'const foo = /\\W/'
},
{
code: 'const foo = /[^a-z0-9_]/i',
errors: [error],
errors: [{
...error,
message: '/[^a-z0-9_]/i can be optimized to /\\W/i'
}],
output: 'const foo = /\\W/i'
},
{
code: 'const foo = /[^a-z\\d_]/i',
errors: [error],
errors: [{
...error,
message: '/[^a-z\\d_]/i can be optimized to /\\W/i'
}],
output: 'const foo = /\\W/i'
},
{
code: 'const foo = /[^a-z\\d_]/ig',
errors: [error],
output: 'const foo = /\\W/ig'
errors: [{
...error,
message: '/[^a-z\\d_]/ig can be optimized to /\\W/gi'
}],
output: 'const foo = /\\W/gi'
},
{
code: 'const foo = /[^\\d_a-z]/ig',
errors: [error],
output: 'const foo = /\\W/ig'
errors: [{
...error,
message: '/[^\\d_a-z]/ig can be optimized to /\\W/gi'
}],
output: 'const foo = /\\W/gi'
},
{
code: 'const foo = new RegExp(/[0-9]/)',
errors: [error],
errors: [{
...error,
message: '/[0-9]/ can be optimized to /\\d/'
}],
output: 'const foo = new RegExp(/\\d/)'
},
{
code: 'const foo = new RegExp(/[0-9]/, \'ig\')',
errors: [error],
errors: [{
...error,
message: '/[0-9]/ can be optimized to /\\d/'
}],
output: 'const foo = new RegExp(/\\d/, \'ig\')'
},
{
code: 'const foo = new RegExp(/[0-9]/)',
errors: [error],
errors: [{
...error,
message: '/[0-9]/ can be optimized to /\\d/'
}],
output: 'const foo = new RegExp(/\\d/)'
},
{
code: 'const foo = new RegExp(/[0-9]/, \'ig\')',
errors: [error],
errors: [{
...error,
message: '/[0-9]/ can be optimized to /\\d/'
}],
output: 'const foo = new RegExp(/\\d/, \'ig\')'
},
{
code: 'const foo = new RegExp(/^[^*]*[*]?$/)',
errors: [{
...error,
message: '/^[^*]*[*]?$/ can be optimized to /^[^*]*\\*?$/'
}],
output: 'const foo = new RegExp(/^[^*]*\\*?$/)'
},
{
code: 'const foo = /[a-z0-9_]/',
errors: [{
...error,
message: '/[a-z0-9_]/ can be optimized to /[\\d_a-z]/'
}],
output: 'const foo = /[\\d_a-z]/'
},
{
code: 'const foo = /^by @([a-zA-Z0-9-]+)/',
errors: [{
...error,
message: '/^by @([a-zA-Z0-9-]+)/ can be optimized to /^by @([\\d-A-Za-z]+)/'
}],
output: 'const foo = /^by @([\\d-A-Za-z]+)/'
}
]
});

0 comments on commit 99c7bd1

Please sign in to comment.