eslint-plugin-regexp
Advanced tools
Comparing version
@@ -70,10 +70,3 @@ "use strict"; | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, reportNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
const newText = convertText(CONVERTER[letterCase]); | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(newText, node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, reportNode, () => convertText(CONVERTER[letterCase])), | ||
}); | ||
@@ -80,0 +73,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const utils_1 = require("../utils"); | ||
const regexp_ast_analysis_1 = require("regexp-ast-analysis"); | ||
const OPTION_SS1 = "[\\s\\S]"; | ||
@@ -8,9 +9,2 @@ const OPTION_SS2 = "[\\S\\s]"; | ||
const OPTION_DOTALL = "dotAll"; | ||
function getCharacterSetKey(node) { | ||
if (node.kind === "property") { | ||
return `\\p{${node.kind + | ||
(node.value == null ? node.key : `${node.key}=${node.value}`)}}`; | ||
} | ||
return node.kind; | ||
} | ||
exports.default = utils_1.createRule("match-any", { | ||
@@ -46,3 +40,3 @@ meta: { | ||
messages: { | ||
unexpected: 'Unexpected using "{{expr}}" to match any character.', | ||
unexpected: "Unexpected using '{{expr}}' to match any character.", | ||
}, | ||
@@ -54,9 +48,15 @@ type: "suggestion", | ||
const sourceCode = context.getSourceCode(); | ||
const allowList = (_b = (_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.allows) !== null && _b !== void 0 ? _b : [OPTION_SS1, OPTION_DOTALL]; | ||
const allowList = (_b = (_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.allows) !== null && _b !== void 0 ? _b : [ | ||
OPTION_SS1, | ||
OPTION_DOTALL, | ||
]; | ||
const allows = new Set(allowList); | ||
const prefer = (allowList[0] !== OPTION_DOTALL && allowList[0]) || null; | ||
function fix(fixer, node, regexpNode) { | ||
if (!prefer) { | ||
const preference = allowList[0] || null; | ||
function fix(fixer, node, regexpNode, flags) { | ||
if (!preference) { | ||
return null; | ||
} | ||
if (preference === OPTION_DOTALL && !flags.dotAll) { | ||
return null; | ||
} | ||
const range = utils_1.getRegexpRange(sourceCode, node, regexpNode); | ||
@@ -67,118 +67,45 @@ if (range == null) { | ||
if (regexpNode.type === "CharacterClass" && | ||
prefer.startsWith("[") && | ||
prefer.endsWith("]")) { | ||
return fixer.replaceTextRange([range[0] + 1, range[1] - 1], utils_1.fixerApplyEscape(prefer.slice(1, -1), node)); | ||
preference.startsWith("[") && | ||
preference.endsWith("]")) { | ||
return fixer.replaceTextRange([range[0] + 1, range[1] - 1], utils_1.fixerApplyEscape(preference.slice(1, -1), node)); | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(prefer, node)); | ||
const replacement = preference === OPTION_DOTALL ? "." : preference; | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(replacement, node)); | ||
} | ||
function createVisitor(node, _pattern, flags) { | ||
let characterClassData = null; | ||
function createVisitor(node, _pattern, flagsStr) { | ||
const flags = utils_1.parseFlags(flagsStr); | ||
return { | ||
onCharacterSetEnter(csNode) { | ||
if (csNode.kind === "any") { | ||
if (flags.includes(utils_1.FLAG_DOTALL)) { | ||
if (!allows.has(OPTION_DOTALL)) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, csNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: ".", | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, csNode); | ||
}, | ||
}); | ||
} | ||
} | ||
return; | ||
if (csNode.kind === "any" && | ||
flags.dotAll && | ||
!allows.has(OPTION_DOTALL)) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, csNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: ".", | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, csNode, flags); | ||
}, | ||
}); | ||
} | ||
if (characterClassData && | ||
!characterClassData.reported && | ||
!characterClassData.node.negate) { | ||
const key = getCharacterSetKey(csNode); | ||
const alreadyCharSet = characterClassData.charSets.get(key); | ||
if (alreadyCharSet != null && | ||
alreadyCharSet.negate === !csNode.negate) { | ||
const ccNode = characterClassData.node; | ||
if (!ccNode.negate && | ||
ccNode.elements.length === 2 && | ||
alreadyCharSet.kind === "space") { | ||
if (!alreadyCharSet.negate) { | ||
if (allows.has(OPTION_SS1)) { | ||
return; | ||
} | ||
} | ||
else { | ||
if (allows.has(OPTION_SS2)) { | ||
return; | ||
} | ||
} | ||
} | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, ccNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: ccNode.raw, | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, ccNode); | ||
}, | ||
}); | ||
characterClassData.reported = true; | ||
} | ||
else { | ||
characterClassData.charSets.set(key, csNode); | ||
} | ||
} | ||
}, | ||
onCharacterClassRangeEnter(ccrNode) { | ||
if (ccrNode.min.value === 0 && | ||
ccrNode.max.value === 65535) { | ||
if (characterClassData && | ||
!characterClassData.reported && | ||
!characterClassData.node.negate) { | ||
const ccNode = characterClassData.node; | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, ccNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: ccNode.raw, | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, ccNode); | ||
}, | ||
}); | ||
characterClassData.reported = true; | ||
} | ||
} | ||
}, | ||
onCharacterClassEnter(ccNode) { | ||
if (ccNode.elements.length === 0) { | ||
if (ccNode.negate && !allows.has(OPTION_CARET)) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, ccNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: "[^]", | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, ccNode); | ||
}, | ||
}); | ||
} | ||
return; | ||
if (regexp_ast_analysis_1.matchesAllCharacters(ccNode, flags) && | ||
!allows.has(ccNode.raw)) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, ccNode), | ||
messageId: "unexpected", | ||
data: { | ||
expr: ccNode.raw, | ||
}, | ||
fix(fixer) { | ||
return fix(fixer, node, ccNode, flags); | ||
}, | ||
}); | ||
} | ||
characterClassData = { | ||
node: ccNode, | ||
charSets: new Map(), | ||
reported: false, | ||
}; | ||
}, | ||
onCharacterClassLeave() { | ||
characterClassData = null; | ||
}, | ||
}; | ||
@@ -185,0 +112,0 @@ } |
@@ -33,9 +33,3 @@ "use strict"; | ||
data: { negatedCharSet }, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, ccNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(negatedCharSet, node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, ccNode, negatedCharSet), | ||
}); | ||
@@ -42,0 +36,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const utils_1 = require("../utils"); | ||
const CPS_SINGLE_SPACES = [...utils_1.CPS_SINGLE_SPACES]; | ||
function isCharacterInCharacterClassRange(char, range) { | ||
@@ -30,3 +31,3 @@ return range.min.value <= char.value && char.value <= range.max.value; | ||
if (set.negate) { | ||
return []; | ||
return { intersections: [] }; | ||
} | ||
@@ -37,2 +38,8 @@ const codePointRange = [range.min.value, range.max.value]; | ||
} | ||
function isCodePointInRange(codePoint) { | ||
return codePointRange[0] <= codePoint && codePoint <= codePointRange[1]; | ||
} | ||
function isRangeInRange(cpRange) { | ||
return (codePointRange[0] <= cpRange[0] && cpRange[1] <= codePointRange[1]); | ||
} | ||
function getIntersectionAndNotSeparate(otherRange) { | ||
@@ -50,7 +57,14 @@ const intersection = getRangesIntersection(codePointRange, otherRange); | ||
const intersection = getRangesIntersection(codePointRange, utils_1.CP_RANGE_DIGIT); | ||
return intersection ? [intersection] : []; | ||
return { intersections: intersection ? [intersection] : [] }; | ||
} | ||
if (set.kind === "space") { | ||
if (isRangeInRange(utils_1.CP_RANGE_SPACES) && | ||
CPS_SINGLE_SPACES.every((codePoint) => isCodePointInRange(codePoint))) { | ||
return { | ||
intersections: [], | ||
set, | ||
}; | ||
} | ||
const result = []; | ||
for (const codePoint of utils_1.CPS_SINGLE_SPACES) { | ||
for (const codePoint of CPS_SINGLE_SPACES) { | ||
if (isCodePointIsRangeEdge(codePoint)) { | ||
@@ -61,5 +75,14 @@ result.push(codePoint); | ||
const intersection = getIntersectionAndNotSeparate(utils_1.CP_RANGE_SPACES); | ||
return intersection ? [...result, intersection] : result; | ||
return { | ||
intersections: intersection ? [...result, intersection] : result, | ||
}; | ||
} | ||
if (set.kind === "word") { | ||
if (isCodePointInRange(utils_1.CP_LOW_LINE) && | ||
utils_1.CP_RANGES_WORDS.every((r) => isRangeInRange(r))) { | ||
return { | ||
intersections: [], | ||
set, | ||
}; | ||
} | ||
const intersections = []; | ||
@@ -72,7 +95,9 @@ for (const wordRange of utils_1.CP_RANGES_WORDS) { | ||
} | ||
return isCodePointIsRangeEdge(utils_1.CP_LOW_LINE) | ||
? [...intersections, utils_1.CP_LOW_LINE] | ||
: intersections; | ||
return { | ||
intersections: isCodePointIsRangeEdge(utils_1.CP_LOW_LINE) | ||
? [...intersections, utils_1.CP_LOW_LINE] | ||
: intersections, | ||
}; | ||
} | ||
return []; | ||
return { intersections: [] }; | ||
} | ||
@@ -116,5 +141,5 @@ function groupingElements(elements) { | ||
return { | ||
characters, | ||
characterClassRanges, | ||
characterSets, | ||
characters: [...characters.values()], | ||
characterClassRanges: [...characterClassRanges.values()], | ||
characterSets: [...characterSets.values()], | ||
}; | ||
@@ -148,5 +173,6 @@ function buildCharacterSetKey(characterSet) { | ||
messages: { | ||
duplicates: 'Unexpected element "{{element}}" duplication.', | ||
charIsIncluded: 'The "{{char}}" is included in "{{element}}".', | ||
intersect: 'Unexpected intersection of "{{elementA}}" and "{{elementB}}" was found "{{intersection}}".', | ||
duplicates: "Unexpected element '{{element}}' duplication.", | ||
charIsIncluded: "The '{{char}}' is included in '{{element}}'.", | ||
charSetIsInRange: "The '{{charSet}}' is included in '{{range}}'.", | ||
intersect: "Unexpected intersection of '{{elementA}}' and '{{elementB}}' was found '{{intersection}}'.", | ||
}, | ||
@@ -200,2 +226,13 @@ }, | ||
} | ||
function reportCharSetInRange(node, set, range) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, set), | ||
messageId: "charSetIsInRange", | ||
data: { | ||
charSet: set.raw, | ||
range: range.raw, | ||
}, | ||
}); | ||
} | ||
function createVisitor(node) { | ||
@@ -205,7 +242,7 @@ return { | ||
const { characters, characterClassRanges, characterSets, } = groupingElements(ccNode.elements); | ||
for (const [char, ...dupeChars] of characters.values()) { | ||
for (const [char, ...dupeChars] of characters) { | ||
if (dupeChars.length) { | ||
reportDuplicates(node, [char, ...dupeChars]); | ||
} | ||
for (const [range] of characterClassRanges.values()) { | ||
for (const [range] of characterClassRanges) { | ||
if (isCharacterInCharacterClassRange(char, range)) { | ||
@@ -215,3 +252,3 @@ reportCharIncluded(node, [char, ...dupeChars], range); | ||
} | ||
for (const [set] of characterSets.values()) { | ||
for (const [set] of characterSets) { | ||
if (isCharacterInCharacterSet(char, set)) { | ||
@@ -222,10 +259,7 @@ reportCharIncluded(node, [char, ...dupeChars], set); | ||
} | ||
for (const [key, [range, ...dupeRanges],] of characterClassRanges) { | ||
for (const [range, ...dupeRanges] of characterClassRanges) { | ||
if (dupeRanges.length) { | ||
reportDuplicates(node, [range, ...dupeRanges]); | ||
} | ||
for (const [keyOther, [rangeOther],] of characterClassRanges) { | ||
if (keyOther === key) { | ||
continue; | ||
} | ||
for (const [rangeOther] of characterClassRanges.filter(([ccr]) => ccr !== range)) { | ||
const intersection = getCharacterClassRangesIntersection(range, rangeOther); | ||
@@ -236,4 +270,7 @@ if (intersection) { | ||
} | ||
for (const [set] of characterSets.values()) { | ||
const intersections = getCharacterClassRangeAndCharacterSetIntersections(range, set); | ||
for (const [set] of characterSets) { | ||
const { set: reportSet, intersections, } = getCharacterClassRangeAndCharacterSetIntersections(range, set); | ||
if (reportSet) { | ||
reportCharSetInRange(node, reportSet, range); | ||
} | ||
for (const intersection of intersections) { | ||
@@ -244,3 +281,3 @@ reportIntersect(node, [range, ...dupeRanges], set, intersection); | ||
} | ||
for (const [set, ...dupeSets] of characterSets.values()) { | ||
for (const [set, ...dupeSets] of characterSets) { | ||
if (dupeSets.length) { | ||
@@ -247,0 +284,0 @@ reportDuplicates(node, [set, ...dupeSets]); |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const regexp_ast_analysis_1 = require("regexp-ast-analysis"); | ||
const utils_1 = require("../utils"); | ||
@@ -12,4 +13,3 @@ exports.default = utils_1.createRule("no-empty-lookarounds-assertion", { | ||
messages: { | ||
unexpectedLookahead: "Unexpected empty lookahead.", | ||
unexpectedLookbehind: "Unexpected empty lookbehind.", | ||
unexpected: "Unexpected empty {{kind}}. It will trivially {{result}} all inputs.", | ||
}, | ||
@@ -27,9 +27,11 @@ type: "suggestion", | ||
} | ||
if (aNode.alternatives.every((alt) => alt.elements.length === 0)) { | ||
if (regexp_ast_analysis_1.isPotentiallyEmpty(aNode.alternatives)) { | ||
context.report({ | ||
node, | ||
loc: utils_1.getRegexpLocation(sourceCode, node, aNode), | ||
messageId: aNode.kind === "lookahead" | ||
? "unexpectedLookahead" | ||
: "unexpectedLookbehind", | ||
messageId: "unexpected", | ||
data: { | ||
kind: aNode.kind, | ||
result: aNode.negate ? "reject" : "accept", | ||
}, | ||
}); | ||
@@ -36,0 +38,0 @@ } |
@@ -12,3 +12,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected "[\\b]". Use "\\u0008" instead.', | ||
unexpected: "Unexpected '[\\b]'. Use '\\u0008' instead.", | ||
}, | ||
@@ -15,0 +15,0 @@ type: "suggestion", |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected invisible character. Use "{{instead}}" instead.', | ||
unexpected: "Unexpected invisible character. Use '{{instead}}' instead.", | ||
}, | ||
@@ -16,0 +16,0 @@ type: "suggestion", |
@@ -12,3 +12,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected octal escape sequence "{{expr}}".', | ||
unexpected: "Unexpected octal escape sequence '{{expr}}'.", | ||
}, | ||
@@ -22,3 +22,12 @@ type: "suggestion", | ||
onCharacterEnter(cNode) { | ||
if (cNode.raw.startsWith("\\0") && cNode.raw !== "\\0") { | ||
if (cNode.raw === "\\0") { | ||
return; | ||
} | ||
if (!/^\\[0-7]+$/.test(cNode.raw)) { | ||
return; | ||
} | ||
const report = cNode.raw.startsWith("\\0") || | ||
!(cNode.parent.type === "CharacterClass" || | ||
cNode.parent.type === "CharacterClassRange"); | ||
if (report) { | ||
context.report({ | ||
@@ -25,0 +34,0 @@ node, |
@@ -87,7 +87,3 @@ "use strict"; | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, ccNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
fix: utils_1.fixReplaceNode(sourceCode, node, ccNode, () => { | ||
let text = element.type === "CharacterClassRange" | ||
@@ -103,4 +99,4 @@ ? element.min.raw | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(text, node)); | ||
}, | ||
return text; | ||
}), | ||
}); | ||
@@ -107,0 +103,0 @@ }, |
@@ -12,3 +12,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected quantifier "{{expr}}".', | ||
unexpected: "Unexpected quantifier '{{expr}}'.", | ||
}, | ||
@@ -15,0 +15,0 @@ type: "suggestion", |
@@ -30,7 +30,3 @@ "use strict"; | ||
messageId: "unexpected", | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, ccrNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
fix: utils_1.fixReplaceNode(sourceCode, node, ccrNode, () => { | ||
let text = ccrNode.min.value < ccrNode.max.value | ||
@@ -44,6 +40,6 @@ ? ccrNode.min.raw + ccrNode.max.raw | ||
next.raw === "-") { | ||
text += utils_1.fixerApplyEscape("\\", node); | ||
text += "\\"; | ||
} | ||
return fixer.replaceTextRange(range, text); | ||
}, | ||
return text; | ||
}), | ||
}); | ||
@@ -50,0 +46,0 @@ }, |
@@ -10,5 +10,6 @@ "use strict"; | ||
}, | ||
fixable: "code", | ||
schema: [], | ||
messages: { | ||
unexpected: 'Unexpected quantifier "{{expr}}".', | ||
unexpected: "Unexpected quantifier '{{expr}}'.", | ||
}, | ||
@@ -38,2 +39,3 @@ type: "suggestion", | ||
}, | ||
fix: utils_1.fixReplaceQuant(sourceCode, node, qNode, `{${qNode.min}}`), | ||
}); | ||
@@ -40,0 +42,0 @@ } |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected the disjunction of single element alternatives. Use character class "[...]" instead.', | ||
unexpected: "Unexpected the disjunction of single element alternatives. Use character class '[...]' instead.", | ||
}, | ||
@@ -16,0 +16,0 @@ type: "suggestion", |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected {{type}} "{{expr}}". Use "{{instead}}" instead.', | ||
unexpected: "Unexpected {{type}} '{{expr}}'. Use '{{instead}}' instead.", | ||
}, | ||
@@ -46,9 +46,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, reportNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(instead, node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, reportNode, instead), | ||
}); | ||
@@ -55,0 +49,0 @@ } |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected quantifier "{{expr}}". Use "+" instead.', | ||
unexpected: "Unexpected quantifier '{{expr}}'. Use '+' instead.", | ||
}, | ||
@@ -34,12 +34,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, qNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange([ | ||
range[0] + startOffset, | ||
range[0] + endOffset, | ||
], "+"); | ||
}, | ||
fix: utils_1.fixReplaceQuant(sourceCode, node, qNode, "+"), | ||
}); | ||
@@ -46,0 +37,0 @@ } |
@@ -89,19 +89,7 @@ "use strict"; | ||
getQuantifier() { | ||
const greedy = this.greedy === false ? "?" : ""; | ||
if (this.min === 0 && this.max === Number.POSITIVE_INFINITY) { | ||
return `*${greedy}`; | ||
} | ||
else if (this.min === 1 && this.max === Number.POSITIVE_INFINITY) { | ||
return `+${greedy}`; | ||
} | ||
else if (this.min === 0 && this.max === 1) { | ||
return `?${greedy}`; | ||
} | ||
else if (this.min === this.max) { | ||
return `{${this.min}}`; | ||
} | ||
else if (this.max === Number.POSITIVE_INFINITY) { | ||
return `{${this.min},}${greedy}`; | ||
} | ||
return `{${this.min},${this.max}}${greedy}`; | ||
return utils_1.quantToString({ | ||
min: this.min, | ||
max: this.max, | ||
greedy: this.greedy !== false, | ||
}); | ||
} | ||
@@ -118,3 +106,3 @@ } | ||
messages: { | ||
unexpected: 'Unexpected consecutive same {{type}}. Use "{{quantifier}}" instead.', | ||
unexpected: "Unexpected consecutive same {{type}}. Use '{{quantifier}}' instead.", | ||
}, | ||
@@ -121,0 +109,0 @@ type: "suggestion", |
@@ -13,4 +13,4 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected quantifier "{{expr}}". Use "?" instead.', | ||
unexpectedGroup: 'Unexpected group "{{expr}}". Use "{{instead}}" instead.', | ||
unexpected: "Unexpected quantifier '{{expr}}'. Use '?' instead.", | ||
unexpectedGroup: "Unexpected group '{{expr}}'. Use '{{instead}}' instead.", | ||
}, | ||
@@ -35,12 +35,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, qNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange([ | ||
range[0] + startOffset, | ||
range[0] + endOffset, | ||
], "?"); | ||
}, | ||
fix: utils_1.fixReplaceQuant(sourceCode, node, qNode, "?"), | ||
}); | ||
@@ -87,9 +78,3 @@ } | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, reportNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(instead, node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, reportNode, instead), | ||
}); | ||
@@ -96,0 +81,0 @@ } |
@@ -71,3 +71,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected multiple adjacent characters. Use "{{range}}" instead.', | ||
unexpected: "Unexpected multiple adjacent characters. Use '{{range}}' instead.", | ||
}, | ||
@@ -74,0 +74,0 @@ type: "suggestion", |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected quantifier "{{expr}}". Use "*" instead.', | ||
unexpected: "Unexpected quantifier '{{expr}}'. Use '*' instead.", | ||
}, | ||
@@ -34,12 +34,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, qNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange([ | ||
range[0] + startOffset, | ||
range[0] + endOffset, | ||
], "*"); | ||
}, | ||
fix: utils_1.fixReplaceQuant(sourceCode, node, qNode, "*"), | ||
}); | ||
@@ -46,0 +37,0 @@ } |
@@ -13,3 +13,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected character "{{expr}}". Use "\\t" instead.', | ||
unexpected: "Unexpected character '{{expr}}'. Use '\\t' instead.", | ||
}, | ||
@@ -33,9 +33,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, cNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape("\\t", node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, cNode, "\\t"), | ||
}); | ||
@@ -42,0 +36,0 @@ } |
@@ -31,7 +31,3 @@ "use strict"; | ||
messageId: "disallowSurrogatePair", | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, cNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
fix: utils_1.fixReplaceNode(sourceCode, node, cNode, () => { | ||
let text = String.fromCodePoint(cNode.value) | ||
@@ -43,4 +39,4 @@ .codePointAt(0) | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(`\\u{${text}}`, node)); | ||
}, | ||
return `\\u{${text}}`; | ||
}), | ||
}); | ||
@@ -47,0 +43,0 @@ } |
@@ -32,3 +32,3 @@ "use strict"; | ||
messages: { | ||
unexpected: 'Unexpected {{type}} "{{expr}}". Use "{{instead}}" instead.', | ||
unexpected: "Unexpected {{type}} '{{expr}}'. Use '{{instead}}' instead.", | ||
}, | ||
@@ -89,9 +89,3 @@ type: "suggestion", | ||
}, | ||
fix(fixer) { | ||
const range = utils_1.getRegexpRange(sourceCode, node, ccNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
return fixer.replaceTextRange(range, utils_1.fixerApplyEscape(instead, node)); | ||
}, | ||
fix: utils_1.fixReplaceNode(sourceCode, node, ccNode, instead), | ||
}); | ||
@@ -98,0 +92,0 @@ } |
@@ -13,3 +13,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.canUnwrapped = exports.getQuantifierOffsets = exports.fixerApplyEscape = exports.getRegexpLocation = exports.getRegexpRange = exports.defineRegexpVisitor = exports.createRule = exports.FLAG_UNICODE = exports.FLAG_STICKY = exports.FLAG_MULTILINE = exports.FLAG_IGNORECASE = exports.FLAG_DOTALL = exports.FLAG_GLOBAL = void 0; | ||
exports.canUnwrapped = exports.quantToString = exports.getQuantifierOffsets = exports.fixReplaceQuant = exports.fixReplaceNode = exports.fixerApplyEscape = exports.getRegexpLocation = exports.getRegexpRange = 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"); | ||
@@ -27,2 +27,19 @@ const eslint_utils_1 = require("eslint-utils"); | ||
exports.FLAG_UNICODE = "u"; | ||
const flagsCache = new Map(); | ||
function parseFlags(flags) { | ||
let cached = flagsCache.get(flags); | ||
if (cached === undefined) { | ||
cached = { | ||
dotAll: flags.includes(exports.FLAG_DOTALL), | ||
global: flags.includes(exports.FLAG_GLOBAL), | ||
ignoreCase: flags.includes(exports.FLAG_IGNORECASE), | ||
multiline: flags.includes(exports.FLAG_MULTILINE), | ||
sticky: flags.includes(exports.FLAG_STICKY), | ||
unicode: flags.includes(exports.FLAG_UNICODE), | ||
}; | ||
flagsCache.set(flags, cached); | ||
} | ||
return cached; | ||
} | ||
exports.parseFlags = parseFlags; | ||
function createRule(ruleName, rule) { | ||
@@ -259,2 +276,43 @@ return { | ||
exports.fixerApplyEscape = fixerApplyEscape; | ||
function fixReplaceNode(sourceCode, node, regexpNode, replacement) { | ||
return (fixer) => { | ||
const range = getRegexpRange(sourceCode, node, regexpNode); | ||
if (range == null) { | ||
return null; | ||
} | ||
let text; | ||
if (typeof replacement === "string") { | ||
text = replacement; | ||
} | ||
else { | ||
text = replacement(); | ||
if (text == null) { | ||
return null; | ||
} | ||
} | ||
return fixer.replaceTextRange(range, fixerApplyEscape(text, node)); | ||
}; | ||
} | ||
exports.fixReplaceNode = fixReplaceNode; | ||
function fixReplaceQuant(sourceCode, node, quantifier, replacement) { | ||
return (fixer) => { | ||
const range = getRegexpRange(sourceCode, node, quantifier); | ||
if (range == null) { | ||
return null; | ||
} | ||
let text; | ||
if (typeof replacement === "string") { | ||
text = replacement; | ||
} | ||
else { | ||
text = replacement(); | ||
if (text == null) { | ||
return null; | ||
} | ||
} | ||
const [startOffset, endOffset] = getQuantifierOffsets(quantifier); | ||
return fixer.replaceTextRange([range[0] + startOffset, range[0] + endOffset], text); | ||
}; | ||
} | ||
exports.fixReplaceQuant = fixReplaceQuant; | ||
function getQuantifierOffsets(qNode) { | ||
@@ -266,2 +324,34 @@ const startOffset = qNode.element.end - qNode.start; | ||
exports.getQuantifierOffsets = getQuantifierOffsets; | ||
function quantToString(quant) { | ||
if (quant.max < quant.min || | ||
quant.min < 0 || | ||
!Number.isInteger(quant.min) || | ||
!(Number.isInteger(quant.max) || quant.max === Infinity)) { | ||
throw new Error(`Invalid quantifier { min: ${quant.min}, max: ${quant.max} }`); | ||
} | ||
let value; | ||
if (quant.min === 0 && quant.max === 1) { | ||
value = "?"; | ||
} | ||
else if (quant.min === 0 && quant.max === Infinity) { | ||
value = "*"; | ||
} | ||
else if (quant.min === 1 && quant.max === Infinity) { | ||
value = "+"; | ||
} | ||
else if (quant.min === quant.max) { | ||
value = `{${quant.min}}`; | ||
} | ||
else if (quant.max === Infinity) { | ||
value = `{${quant.min},}`; | ||
} | ||
else { | ||
value = `{${quant.min},${quant.max}}`; | ||
} | ||
if (!quant.greedy) { | ||
return `${value}?`; | ||
} | ||
return value; | ||
} | ||
exports.quantToString = quantToString; | ||
function canUnwrapped(node, text) { | ||
@@ -268,0 +358,0 @@ const parent = node.parent; |
@@ -463,4 +463,4 @@ "use strict"; | ||
function isCoveredAltNodes(leftNodes, rightNodes, options) { | ||
const left = options.canOmitRight ? omitEnds(leftNodes) : leftNodes; | ||
const right = options.canOmitRight ? omitEnds(rightNodes) : rightNodes; | ||
const left = options.canOmitRight ? omitEnds(leftNodes) : [...leftNodes]; | ||
const right = options.canOmitRight ? omitEnds(rightNodes) : [...rightNodes]; | ||
while (left.length && right.length) { | ||
@@ -467,0 +467,0 @@ const le = left.shift(); |
@@ -7,2 +7,3 @@ "use strict"; | ||
exports.rules = void 0; | ||
const confusing_quantifier_1 = __importDefault(require("../rules/confusing-quantifier")); | ||
const letter_case_1 = __importDefault(require("../rules/letter-case")); | ||
@@ -14,2 +15,3 @@ const match_any_1 = __importDefault(require("../rules/match-any")); | ||
const no_dupe_disjunctions_1 = __importDefault(require("../rules/no-dupe-disjunctions")); | ||
const no_empty_alternative_1 = __importDefault(require("../rules/no-empty-alternative")); | ||
const no_empty_group_1 = __importDefault(require("../rules/no-empty-group")); | ||
@@ -19,2 +21,3 @@ const no_empty_lookarounds_assertion_1 = __importDefault(require("../rules/no-empty-lookarounds-assertion")); | ||
const no_invisible_character_1 = __importDefault(require("../rules/no-invisible-character")); | ||
const no_lazy_ends_1 = __importDefault(require("../rules/no-lazy-ends")); | ||
const no_legacy_features_1 = __importDefault(require("../rules/no-legacy-features")); | ||
@@ -32,2 +35,3 @@ const no_octal_1 = __importDefault(require("../rules/no-octal")); | ||
const no_useless_two_nums_quantifier_1 = __importDefault(require("../rules/no-useless-two-nums-quantifier")); | ||
const optimal_lookaround_quantifier_1 = __importDefault(require("../rules/optimal-lookaround-quantifier")); | ||
const order_in_character_class_1 = __importDefault(require("../rules/order-in-character-class")); | ||
@@ -48,2 +52,3 @@ const prefer_character_class_1 = __importDefault(require("../rules/prefer-character-class")); | ||
exports.rules = [ | ||
confusing_quantifier_1.default, | ||
letter_case_1.default, | ||
@@ -55,2 +60,3 @@ match_any_1.default, | ||
no_dupe_disjunctions_1.default, | ||
no_empty_alternative_1.default, | ||
no_empty_group_1.default, | ||
@@ -60,2 +66,3 @@ no_empty_lookarounds_assertion_1.default, | ||
no_invisible_character_1.default, | ||
no_lazy_ends_1.default, | ||
no_legacy_features_1.default, | ||
@@ -73,2 +80,3 @@ no_octal_1.default, | ||
no_useless_two_nums_quantifier_1.default, | ||
optimal_lookaround_quantifier_1.default, | ||
order_in_character_class_1.default, | ||
@@ -75,0 +83,0 @@ prefer_character_class_1.default, |
{ | ||
"name": "eslint-plugin-regexp", | ||
"version": "0.7.5", | ||
"version": "0.8.0", | ||
"description": "ESLint plugin for finding RegExp mistakes and RegExp style guide violations.", | ||
@@ -39,3 +39,6 @@ "main": "dist/index.js", | ||
], | ||
"author": "Yosuke Ota", | ||
"author": "Yosuke Ota (https://github.com/ota-meshi)", | ||
"contributors": [ | ||
"Michael Schmidt (https://github.com/RunDevelopment)" | ||
], | ||
"license": "MIT", | ||
@@ -63,3 +66,3 @@ "bugs": { | ||
"eslint-plugin-eslint-comments": "^3.2.0", | ||
"eslint-plugin-eslint-plugin": "^2.3.0", | ||
"eslint-plugin-eslint-plugin": "^3.0.0", | ||
"eslint-plugin-json-schema-validator": "^1.0.0", | ||
@@ -90,4 +93,6 @@ "eslint-plugin-jsonc": "^1.0.0", | ||
"jsdoctypeparser": "^9.0.0", | ||
"refa": "^0.7.1", | ||
"regexp-ast-analysis": "^0.1.1", | ||
"regexpp": "^3.1.0" | ||
} | ||
} |
@@ -70,3 +70,3 @@ # Introduction | ||
- `plugin:regexp/recommended` ... This is the recommended configuration for this plugin. | ||
- `plugin:regexp/recommended` ... This is the recommended configuration for this plugin. | ||
See [lib/configs/recommended.ts](https://github.com/ota-meshi/eslint-plugin-regexp/blob/master/lib/configs/recommended.ts) for details. | ||
@@ -80,3 +80,3 @@ | ||
The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench :wrench: below. | ||
The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench :wrench: below. | ||
The rules with the following star :star: are included in the `plugin:regexp/recommended` config. | ||
@@ -88,2 +88,3 @@ | ||
|:--------|:------------|:---| | ||
| [regexp/confusing-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/confusing-quantifier.html) | disallow confusing quantifiers | | | ||
| [regexp/letter-case](https://ota-meshi.github.io/eslint-plugin-regexp/rules/letter-case.html) | enforce into your favorite case | :wrench: | | ||
@@ -95,2 +96,3 @@ | [regexp/match-any](https://ota-meshi.github.io/eslint-plugin-regexp/rules/match-any.html) | enforce match any character style | :star::wrench: | | ||
| [regexp/no-dupe-disjunctions](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-dupe-disjunctions.html) | disallow duplicate disjunctions | | | ||
| [regexp/no-empty-alternative](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-alternative.html) | disallow alternatives without elements | | | ||
| [regexp/no-empty-group](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-group.html) | disallow empty group | :star: | | ||
@@ -100,2 +102,3 @@ | [regexp/no-empty-lookarounds-assertion](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-empty-lookarounds-assertion.html) | disallow empty lookahead assertion or empty lookbehind assertion | :star: | | ||
| [regexp/no-invisible-character](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-invisible-character.html) | disallow invisible raw character | :star::wrench: | | ||
| [regexp/no-lazy-ends](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-lazy-ends.html) | disallow lazy quantifiers at the end of an expression | | | ||
| [regexp/no-legacy-features](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-legacy-features.html) | disallow legacy RegExp features | | | ||
@@ -112,3 +115,4 @@ | [regexp/no-octal](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-octal.html) | disallow octal escape sequence | :star: | | ||
| [regexp/no-useless-range](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-range.html) | disallow unnecessary range of characters by using a hyphen | :wrench: | | ||
| [regexp/no-useless-two-nums-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-two-nums-quantifier.html) | disallow unnecessary `{n,m}` quantifier | :star: | | ||
| [regexp/no-useless-two-nums-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/no-useless-two-nums-quantifier.html) | disallow unnecessary `{n,m}` quantifier | :star::wrench: | | ||
| [regexp/optimal-lookaround-quantifier](https://ota-meshi.github.io/eslint-plugin-regexp/rules/optimal-lookaround-quantifier.html) | disallow the alternatives of lookarounds that end with a non-constant quantifier | | | ||
| [regexp/order-in-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/order-in-character-class.html) | enforces elements order in character class | :wrench: | | ||
@@ -146,4 +150,6 @@ | [regexp/prefer-character-class](https://ota-meshi.github.io/eslint-plugin-regexp/rules/prefer-character-class.html) | enforce using character class | :wrench: | | ||
- `npm test` runs tests and measures coverage. | ||
- `npm run update` runs in order to update readme and recommended configuration. | ||
- `npm test` runs tests and measures coverage. | ||
- `npm run update` runs in order to update readme and recommended configuration. | ||
- `npm run new [new rule name]` runs to create the files needed for the new rule. | ||
- `npm run docs:watch` starts the website locally. | ||
@@ -150,0 +156,0 @@ <!--DOCS_IGNORE_END--> |
318126
2.68%78
5.41%7908
3.05%154
4.05%7
40%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed