UNPKG

eslint-plugin-unicorn-x

Version:
152 lines (131 loc) 3.35 kB
import cleanRegexp from 'clean-regexp'; import regexpTree from 'regexp-tree'; import escapeString from './utils/escape-string.js'; import {isStringLiteral, isNewExpression, isRegexLiteral} from './ast/index.js'; const MESSAGE_ID = 'better-regex'; const MESSAGE_ID_PARSE_ERROR = 'better-regex/parse-error'; const messages = { [MESSAGE_ID]: '{{original}} can be optimized to {{optimized}}.', [MESSAGE_ID_PARSE_ERROR]: 'Problem parsing {{original}}: {{error}}', }; const newExpressionOptions = {name: 'RegExp', minimumArguments: 1}; /** @param {import('eslint').Rule.RuleContext} context */ const create = (context) => { const {sortCharacterClasses} = context.options[0] || {}; const ignoreList = []; const optimizeOptions = { blacklist: ignoreList, }; if (sortCharacterClasses === false) { ignoreList.push('charClassClassrangesMerge'); } return { Literal(node) { if (!isRegexLiteral(node)) { return; } const {raw: original, regex} = node; // Regular Expressions with `u` and `v` flag are not well handled by `regexp-tree` // https://github.com/DmitrySoshnikov/regexp-tree/issues/162 if (regex.flags.includes('u') || regex.flags.includes('v')) { return; } let optimized = original; try { optimized = regexpTree .optimize(original, undefined, optimizeOptions) .toString(); } catch (error) { context.report({ node, messageId: MESSAGE_ID_PARSE_ERROR, data: { original, error: error.message, }, }); return; } if (original === optimized) { return; } const problem = { node, messageId: MESSAGE_ID, data: { original, optimized, }, }; if ( node.parent.type === 'MemberExpression' && node.parent.object === node && !node.parent.optional && !node.parent.computed && node.parent.property.type === 'Identifier' && (node.parent.property.name === 'toString' || node.parent.property.name === 'source') ) { context.report(problem); return; } problem.fix = (fixer) => fixer.replaceText(node, optimized); context.report(problem); }, NewExpression(node) { if (!isNewExpression(node, newExpressionOptions)) { return; } const [patternNode, flagsNode] = node.arguments; if (!isStringLiteral(patternNode)) { return; } const oldPattern = patternNode.value; const flags = isStringLiteral(flagsNode) ? flagsNode.value : ''; const newPattern = cleanRegexp(oldPattern, flags); if (oldPattern !== newPattern) { context.report({ node, messageId: MESSAGE_ID, data: { original: oldPattern, optimized: newPattern, }, fix: (fixer) => fixer.replaceText( patternNode, escapeString(newPattern, patternNode.raw.charAt(0)), ), }); } }, }; }; const schema = [ { type: 'object', additionalProperties: false, properties: { sortCharacterClasses: { type: 'boolean', }, }, }, ]; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Improve regexes by making them shorter, consistent, and safer.', recommended: false, }, fixable: 'code', schema, defaultOptions: [{sortCharacterClasses: true}], messages, }, }; export default config;