eslint-plugin-regexp
Advanced tools
Comparing version
@@ -7,3 +7,3 @@ "use strict"; | ||
class RegExpReference { | ||
constructor(regExpContext, defineNode) { | ||
constructor(regExpContext) { | ||
this.readNodes = new Map(); | ||
@@ -15,4 +15,6 @@ this.state = { | ||
this.regExpContext = regExpContext; | ||
this.defineNode = defineNode; | ||
} | ||
get defineNode() { | ||
return this.regExpContext.regexpNode; | ||
} | ||
addReadNode(node) { | ||
@@ -84,18 +86,2 @@ this.readNodes.set(node, {}); | ||
} | ||
function getFlagLocation(context, node, flag) { | ||
const sourceCode = context.getSourceCode(); | ||
if (node.type === "Literal") { | ||
const flagIndex = node.range[1] - | ||
node.regex.flags.length + | ||
node.regex.flags.indexOf(flag); | ||
return { | ||
start: sourceCode.getLocFromIndex(flagIndex), | ||
end: sourceCode.getLocFromIndex(flagIndex + 1), | ||
}; | ||
} | ||
if (node.arguments.length >= 2) { | ||
return node.arguments[1].loc; | ||
} | ||
return context.getSourceCode().getTokenAfter(node.arguments[0]).loc; | ||
} | ||
function fixRemoveFlag({ flagsString, fixReplaceFlags }, flag) { | ||
@@ -110,3 +96,3 @@ if (flagsString) { | ||
createVisitor(regExpContext) { | ||
const { flags, regexpNode, toCharSet, ownsFlags } = regExpContext; | ||
const { flags, regexpNode, toCharSet, ownsFlags, getFlagLocation, } = regExpContext; | ||
if (!flags.ignoreCase || !ownsFlags) { | ||
@@ -150,3 +136,3 @@ return {}; | ||
node: regexpNode, | ||
loc: getFlagLocation(context, regexpNode, "i"), | ||
loc: getFlagLocation("i"), | ||
messageId: "uselessIgnoreCaseFlag", | ||
@@ -164,3 +150,3 @@ fix: fixRemoveFlag(regExpContext, "i"), | ||
createVisitor(regExpContext) { | ||
const { flags, regexpNode, ownsFlags } = regExpContext; | ||
const { flags, regexpNode, ownsFlags, getFlagLocation, } = regExpContext; | ||
if (!flags.multiline || !ownsFlags) { | ||
@@ -180,3 +166,3 @@ return {}; | ||
node: regexpNode, | ||
loc: getFlagLocation(context, regexpNode, "m"), | ||
loc: getFlagLocation("m"), | ||
messageId: "uselessMultilineFlag", | ||
@@ -194,3 +180,3 @@ fix: fixRemoveFlag(regExpContext, "m"), | ||
createVisitor(regExpContext) { | ||
const { flags, regexpNode, ownsFlags } = regExpContext; | ||
const { flags, regexpNode, ownsFlags, getFlagLocation, } = regExpContext; | ||
if (!flags.dotAll || !ownsFlags) { | ||
@@ -210,3 +196,3 @@ return {}; | ||
node: regexpNode, | ||
loc: getFlagLocation(context, regexpNode, "s"), | ||
loc: getFlagLocation("s"), | ||
messageId: "uselessDotAllFlag", | ||
@@ -223,6 +209,7 @@ fix: fixRemoveFlag(regExpContext, "s"), | ||
function reportUselessGlobalFlag(regExpReference, data) { | ||
const { getFlagLocation } = regExpReference.regExpContext; | ||
const node = regExpReference.defineNode; | ||
context.report({ | ||
node, | ||
loc: getFlagLocation(context, node, "g"), | ||
loc: getFlagLocation("g"), | ||
messageId: data.kind === 0 | ||
@@ -314,6 +301,7 @@ ? "uselessGlobalFlagForSplit" | ||
function reportUselessStickyFlag(regExpReference, data) { | ||
const { getFlagLocation } = regExpReference.regExpContext; | ||
const node = regExpReference.defineNode; | ||
context.report({ | ||
node, | ||
loc: getFlagLocation(context, node, "y"), | ||
loc: getFlagLocation("y"), | ||
messageId: "uselessStickyFlag", | ||
@@ -409,3 +397,3 @@ fix: data.fixable | ||
if (flags[flag]) { | ||
const regExpReference = new RegExpReference(regExpContext, regexpNode); | ||
const regExpReference = new RegExpReference(regExpContext); | ||
regExpReferenceList.push(regExpReference); | ||
@@ -504,2 +492,38 @@ regExpReferenceMap.set(regexpNode, regExpReference); | ||
} | ||
function createOwnedRegExpFlagsVisitor(context) { | ||
const sourceCode = context.getSourceCode(); | ||
function removeFlags(node) { | ||
const newFlags = node.regex.flags.replace(/[^u]+/g, ""); | ||
if (newFlags === node.regex.flags) { | ||
return; | ||
} | ||
context.report({ | ||
node, | ||
loc: utils_1.getFlagsLocation(sourceCode, node, node), | ||
messageId: "uselessFlagsOwned", | ||
fix(fixer) { | ||
const range = utils_1.getFlagsRange(node); | ||
return fixer.replaceTextRange(range, newFlags); | ||
}, | ||
}); | ||
} | ||
return utils_1.defineRegexpVisitor(context, { | ||
createSourceVisitor(regExpContext) { | ||
var _a; | ||
const { patternSource, regexpNode } = regExpContext; | ||
if (patternSource.isStringValue()) { | ||
patternSource.getOwnedRegExpLiterals().forEach(removeFlags); | ||
} | ||
else { | ||
if (regexpNode.arguments.length >= 2) { | ||
const ownedNode = (_a = patternSource.regexpValue) === null || _a === void 0 ? void 0 : _a.ownedNode; | ||
if (ownedNode) { | ||
removeFlags(ownedNode); | ||
} | ||
} | ||
} | ||
return {}; | ||
}, | ||
}); | ||
} | ||
function parseOption(userOption) { | ||
@@ -557,2 +581,3 @@ var _a; | ||
uselessStickyFlag: "The 'y' flag is unnecessary because 'String.prototype.split' ignores the 'y' flag.", | ||
uselessFlagsOwned: "The flags of this RegExp literal are useless because only the source of the regex is used.", | ||
}, | ||
@@ -579,4 +604,5 @@ type: "suggestion", | ||
} | ||
visitor = utils_1.compositingVisitors(visitor, createOwnedRegExpFlagsVisitor(context)); | ||
return visitor; | ||
}, | ||
}); |
@@ -76,2 +76,14 @@ "use strict"; | ||
} | ||
getOwnedRegExpLiteral() { | ||
if (utils_1.isRegexpLiteral(this.node)) { | ||
return this.node; | ||
} | ||
if (this.node.type === "MemberExpression" && | ||
this.node.object.type !== "Super" && | ||
utils_1.isRegexpLiteral(this.node.object) && | ||
utils_1.getPropertyName(this.node) === "source") { | ||
return this.node.object; | ||
} | ||
return null; | ||
} | ||
getReplaceRange(range) { | ||
@@ -81,11 +93,9 @@ if (!this.contains(range)) { | ||
} | ||
const regexp = this.getOwnedRegExpLiteral(); | ||
if (regexp) { | ||
return PatternReplaceRange.fromLiteral(regexp, this.sourceCode, this, range); | ||
} | ||
if (this.node.type === "Literal") { | ||
return PatternReplaceRange.fromLiteral(this.node, this.sourceCode, this, range); | ||
} | ||
if (this.node.type === "MemberExpression" && | ||
this.node.object.type !== "Super" && | ||
utils_1.isRegexpLiteral(this.node.object) && | ||
utils_1.getPropertyName(this.node) === "source") { | ||
return PatternReplaceRange.fromLiteral(this.node.object, this.sourceCode, this, range); | ||
} | ||
return null; | ||
@@ -191,2 +201,12 @@ } | ||
} | ||
getOwnedRegExpLiterals() { | ||
const literals = []; | ||
for (const segment of this.segments) { | ||
const regexp = segment.getOwnedRegExpLiteral(); | ||
if (regexp) { | ||
literals.push(regexp); | ||
} | ||
} | ||
return literals; | ||
} | ||
} | ||
@@ -193,0 +213,0 @@ exports.PatternSource = PatternSource; |
@@ -13,3 +13,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isEscapeSequence = exports.isUseHexEscape = exports.getEscapeSequenceKind = exports.EscapeSequenceKind = exports.isUnicodeCodePointEscape = exports.isUnicodeEscape = exports.isHexadecimalEscape = exports.isControlEscape = exports.isOctalEscape = exports.canUnwrapped = exports.mightCreateNewElement = exports.toCharSetSource = exports.quantToString = exports.getQuantifierOffsets = exports.compositingVisitors = exports.defineRegexpVisitor = exports.createRule = exports.parseFlags = exports.FLAG_UNICODE = exports.FLAG_STICKY = exports.FLAG_MULTILINE = exports.FLAG_IGNORECASE = exports.FLAG_DOTALL = exports.FLAG_GLOBAL = void 0; | ||
exports.isEscapeSequence = exports.isUseHexEscape = exports.getEscapeSequenceKind = exports.EscapeSequenceKind = exports.isUnicodeCodePointEscape = exports.isUnicodeEscape = exports.isHexadecimalEscape = exports.isControlEscape = exports.isOctalEscape = exports.canUnwrapped = exports.mightCreateNewElement = exports.toCharSetSource = exports.quantToString = exports.getQuantifierOffsets = exports.getFlagsLocation = exports.getFlagsRange = exports.compositingVisitors = exports.defineRegexpVisitor = exports.createRule = exports.parseFlags = exports.FLAG_UNICODE = exports.FLAG_STICKY = exports.FLAG_MULTILINE = exports.FLAG_IGNORECASE = exports.FLAG_DOTALL = exports.FLAG_GLOBAL = void 0; | ||
const regexpp_1 = require("regexpp"); | ||
@@ -90,3 +90,3 @@ const eslint_utils_1 = require("eslint-utils"); | ||
const parser = new regexpp_1.RegExpParser(); | ||
function verify(patternNode, regexpNode, patternSource, flagsString, ownsFlags, createVisitor) { | ||
function verify(patternNode, flagsNode, regexpNode, patternSource, flagsString, ownsFlags, createVisitor) { | ||
const flags = parseFlags(flagsString || ""); | ||
@@ -101,2 +101,3 @@ if (!patternSource) { | ||
flagsString, | ||
flagsNode, | ||
ownsFlags, | ||
@@ -120,2 +121,3 @@ }))); | ||
flagsString, | ||
flagsNode, | ||
ownsFlags, | ||
@@ -129,2 +131,3 @@ }))); | ||
regexpNode, | ||
flagsNode, | ||
context, | ||
@@ -136,6 +139,7 @@ flags, | ||
} | ||
const ownedRegExpLiterals = new Set(); | ||
return { | ||
"Program:exit": programExit, | ||
Literal(node) { | ||
if (!utils_1.isRegexpLiteral(node)) { | ||
if (!utils_1.isRegexpLiteral(node) || ownedRegExpLiterals.has(node)) { | ||
return; | ||
@@ -145,3 +149,3 @@ } | ||
const patternSource = pattern_source_1.PatternSource.fromRegExpLiteral(context, node); | ||
verify(node, node, patternSource, flagsString, true, (base) => { | ||
verify(node, node, node, patternSource, flagsString, true, (base) => { | ||
return createLiteralVisitorFromRules(rules, Object.assign({ node, | ||
@@ -158,12 +162,18 @@ flagsString, ownsFlags: true, regexpNode: node }, base)); | ||
const newOrCall = node; | ||
const [patternNode, flagsNode] = newOrCall.arguments; | ||
if (!patternNode || patternNode.type === "SpreadElement") { | ||
const args = newOrCall.arguments; | ||
const [patternArg, flagsArg] = args; | ||
if (!patternArg || patternArg.type === "SpreadElement") { | ||
continue; | ||
} | ||
const patternSource = pattern_source_1.PatternSource.fromExpression(context, patternNode); | ||
const patternSource = pattern_source_1.PatternSource.fromExpression(context, patternArg); | ||
patternSource === null || patternSource === void 0 ? void 0 : patternSource.getOwnedRegExpLiterals().forEach((n) => ownedRegExpLiterals.add(n)); | ||
let flagsNode = null; | ||
let flagsString = null; | ||
let ownsFlags = false; | ||
if (flagsNode && flagsNode.type !== "SpreadElement") { | ||
flagsString = ast_utils_1.getStringIfConstant(context, flagsNode); | ||
ownsFlags = utils_1.isStringLiteral(flagsNode); | ||
if (flagsArg) { | ||
if (flagsArg.type !== "SpreadElement") { | ||
flagsNode = flagsArg; | ||
flagsString = ast_utils_1.getStringIfConstant(context, flagsArg); | ||
ownsFlags = utils_1.isStringLiteral(flagsArg); | ||
} | ||
} | ||
@@ -173,7 +183,9 @@ else if (patternSource && patternSource.regexpValue) { | ||
ownsFlags = Boolean(patternSource.regexpValue.ownedNode); | ||
flagsNode = patternSource.regexpValue.ownedNode; | ||
} | ||
regexpDataList.push({ | ||
call: newOrCall, | ||
patternNode, | ||
patternNode: patternArg, | ||
patternSource, | ||
flagsNode, | ||
flagsString, | ||
@@ -183,4 +195,4 @@ ownsFlags, | ||
} | ||
for (const { call, patternNode, patternSource, flagsString, ownsFlags, } of regexpDataList) { | ||
verify(patternNode, call, patternSource, flagsString, ownsFlags, (base) => { | ||
for (const { call, patternNode, patternSource, flagsNode, flagsString, ownsFlags, } of regexpDataList) { | ||
verify(patternNode, flagsNode, call, patternSource, flagsString, ownsFlags, (base) => { | ||
return createSourceVisitorFromRules(rules, Object.assign({ node: patternNode, flagsString, | ||
@@ -260,3 +272,3 @@ ownsFlags, regexpNode: call }, base)); | ||
exports.compositingVisitors = compositingVisitors; | ||
function buildRegExpContextBase({ patternSource, regexpNode, context, flags, parsedPattern, }) { | ||
function buildRegExpContextBase({ patternSource, regexpNode, flagsNode, context, flags, parsedPattern, }) { | ||
const sourceCode = context.getSourceCode(); | ||
@@ -287,3 +299,4 @@ const cacheCharSet = new WeakMap(); | ||
}, | ||
getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode), | ||
getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode, flagsNode), | ||
getFlagLocation: (flag) => getFlagLocation(sourceCode, regexpNode, flagsNode, flag), | ||
fixReplaceNode: (node, replacement) => { | ||
@@ -296,3 +309,3 @@ return fixReplaceNode(patternSource, node, replacement); | ||
fixReplaceFlags: (newFlags) => { | ||
return fixReplaceFlags(patternSource, regexpNode, newFlags); | ||
return fixReplaceFlags(patternSource, regexpNode, flagsNode, newFlags); | ||
}, | ||
@@ -306,3 +319,3 @@ getUsageOfPattern: () => (cacheUsageOfPattern !== null && cacheUsageOfPattern !== void 0 ? cacheUsageOfPattern : (cacheUsageOfPattern = get_usage_of_pattern_1.getUsageOfPattern(regexpNode, context))), | ||
} | ||
function buildUnparsableRegExpContextBase({ patternSource, patternNode, regexpNode, context, flags, flagsString, ownsFlags, }) { | ||
function buildUnparsableRegExpContextBase({ patternSource, patternNode, regexpNode, context, flags, flagsString, flagsNode, ownsFlags, }) { | ||
const sourceCode = context.getSourceCode(); | ||
@@ -315,3 +328,4 @@ return { | ||
ownsFlags, | ||
getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode), | ||
getFlagsLocation: () => getFlagsLocation(sourceCode, regexpNode, flagsNode), | ||
getFlagLocation: (flag) => getFlagLocation(sourceCode, regexpNode, flagsNode, flag), | ||
fixReplaceFlags: (newFlags) => { | ||
@@ -321,25 +335,27 @@ if (!patternSource) { | ||
} | ||
return fixReplaceFlags(patternSource, regexpNode, newFlags); | ||
return fixReplaceFlags(patternSource, regexpNode, flagsNode, newFlags); | ||
}, | ||
}; | ||
} | ||
function getFlagsRange(regexpNode) { | ||
if (utils_1.isRegexpLiteral(regexpNode)) { | ||
function getFlagsRange(flagsNode) { | ||
if (!flagsNode) { | ||
return null; | ||
} | ||
if (utils_1.isRegexpLiteral(flagsNode)) { | ||
return [ | ||
regexpNode.range[1] - regexpNode.regex.flags.length, | ||
regexpNode.range[1], | ||
flagsNode.range[1] - flagsNode.regex.flags.length, | ||
flagsNode.range[1], | ||
]; | ||
} | ||
const flagsArg = regexpNode.arguments[1]; | ||
if (flagsArg && | ||
flagsArg.type === "Literal" && | ||
typeof flagsArg.value === "string") { | ||
return [flagsArg.range[0] + 1, flagsArg.range[1] - 1]; | ||
if (utils_1.isStringLiteral(flagsNode)) { | ||
return [flagsNode.range[0] + 1, flagsNode.range[1] - 1]; | ||
} | ||
return null; | ||
} | ||
function getFlagsLocation(sourceCode, regexpNode) { | ||
const range = getFlagsRange(regexpNode); | ||
exports.getFlagsRange = getFlagsRange; | ||
function getFlagsLocation(sourceCode, regexpNode, flagsNode) { | ||
var _a; | ||
const range = getFlagsRange(flagsNode); | ||
if (range == null) { | ||
return regexpNode.loc; | ||
return (_a = flagsNode === null || flagsNode === void 0 ? void 0 : flagsNode.loc) !== null && _a !== void 0 ? _a : regexpNode.loc; | ||
} | ||
@@ -351,2 +367,35 @@ return { | ||
} | ||
exports.getFlagsLocation = getFlagsLocation; | ||
function getFlagRange(sourceCode, flagsNode, flag) { | ||
if (!flagsNode || !flag) { | ||
return null; | ||
} | ||
if (utils_1.isRegexpLiteral(flagsNode)) { | ||
const index = flagsNode.regex.flags.indexOf(flag); | ||
if (index === -1) { | ||
return null; | ||
} | ||
const start = flagsNode.range[1] - flagsNode.regex.flags.length + index; | ||
return [start, start + 1]; | ||
} | ||
if (utils_1.isStringLiteral(flagsNode)) { | ||
const index = flagsNode.value.indexOf(flag); | ||
if (index === -1) { | ||
return null; | ||
} | ||
return utils_1.getStringValueRange(sourceCode, flagsNode, index, index + 1); | ||
} | ||
return null; | ||
} | ||
function getFlagLocation(sourceCode, regexpNode, flagsNode, flag) { | ||
var _a; | ||
const range = getFlagRange(sourceCode, flagsNode, flag); | ||
if (range == null) { | ||
return (_a = flagsNode === null || flagsNode === void 0 ? void 0 : flagsNode.loc) !== null && _a !== void 0 ? _a : regexpNode.loc; | ||
} | ||
return { | ||
start: sourceCode.getLocFromIndex(range[0]), | ||
end: sourceCode.getLocFromIndex(range[1]), | ||
}; | ||
} | ||
function fixReplaceNode(patternSource, regexpNode, replacement) { | ||
@@ -401,3 +450,3 @@ return (fixer) => { | ||
} | ||
function fixReplaceFlags(patternSource, regexpNode, replacement) { | ||
function fixReplaceFlags(patternSource, regexpNode, flagsNode, replacement) { | ||
return (fixer) => { | ||
@@ -420,3 +469,3 @@ let newFlags; | ||
} | ||
const range = getFlagsRange(regexpNode); | ||
const range = getFlagsRange(flagsNode); | ||
if (range == null) { | ||
@@ -423,0 +472,0 @@ return null; |
{ | ||
"name": "eslint-plugin-regexp", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "ESLint plugin for finding RegExp mistakes and RegExp style guide violations.", | ||
@@ -55,3 +55,3 @@ "engines": { | ||
"devDependencies": { | ||
"@ota-meshi/eslint-plugin": "^0.7.0", | ||
"@ota-meshi/eslint-plugin": "^0.8.0", | ||
"@types/chai": "^4.2.18", | ||
@@ -68,3 +68,3 @@ "@types/eslint": "^7.2.0", | ||
"env-cmd": "^10.1.0", | ||
"eslint": "^7.3.0", | ||
"eslint": "^7.32.0", | ||
"eslint-config-prettier": "^8.0.0", | ||
@@ -76,4 +76,4 @@ "eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^3.4.0", | ||
"eslint-plugin-regexp": "^0.13.1", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"eslint-plugin-regexp": "^1.0.0", | ||
"eslint-plugin-vue": "^7.5.0", | ||
@@ -80,0 +80,0 @@ "eslint-plugin-yml": "^0.10.0", |
531919
0.72%13294
0.72%