eslint-plugin-regexp
Version:
ESLint plugin for finding RegExp mistakes and RegExp style guide violations.
213 lines (212 loc) • 10.1 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const regex_syntax_1 = require("../utils/regex-syntax");
const ESCAPES_OUTSIDE_CHARACTER_CLASS = new Set("$()*+./?[{|");
const ESCAPES_OUTSIDE_CHARACTER_CLASS_WITH_U = new Set([
...ESCAPES_OUTSIDE_CHARACTER_CLASS,
"}",
]);
exports.default = (0, utils_1.createRule)("no-useless-character-class", {
meta: {
docs: {
description: "disallow character class with one character",
category: "Best Practices",
recommended: true,
},
fixable: "code",
schema: [
{
type: "object",
properties: {
ignores: {
type: "array",
items: {
type: "string",
minLength: 1,
},
uniqueItems: true,
},
},
additionalProperties: false,
},
],
messages: {
unexpectedCharacterClassWith: "Unexpected character class with one {{type}}. Can remove brackets{{additional}}.",
unexpectedUnnecessaryNestingCharacterClass: "Unexpected unnecessary nesting character class. Can remove brackets.",
},
type: "suggestion",
},
create(context) {
var _a, _b;
const ignores = (_b = (_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.ignores) !== null && _b !== void 0 ? _b : ["="];
function createVisitor({ node, pattern, flags, fixReplaceNode, getRegexpLocation, }) {
const characterClassStack = [];
return {
onExpressionCharacterClassEnter(eccNode) {
characterClassStack.push(eccNode);
},
onExpressionCharacterClassLeave() {
characterClassStack.pop();
},
onCharacterClassEnter(ccNode) {
characterClassStack.push(ccNode);
},
onCharacterClassLeave(ccNode) {
var _a, _b;
characterClassStack.pop();
if (ccNode.negate) {
return;
}
let messageId, messageData;
const unwrapped = ccNode.elements.map((_e, index) => {
var _a, _b;
const element = ccNode.elements[index];
return ((_b = (_a = (index === 0
? getEscapedFirstRawIfNeeded(element)
: null)) !== null && _a !== void 0 ? _a : (index === ccNode.elements.length - 1
? getEscapedLastRawIfNeeded(element)
: null)) !== null && _b !== void 0 ? _b : element.raw);
});
if (ccNode.elements.length !== 1 &&
ccNode.parent.type === "CharacterClass") {
messageId = "unexpectedUnnecessaryNestingCharacterClass";
messageData = {
type: "unnecessary nesting character class",
};
if (!ccNode.elements.length) {
const nextElement = ccNode.parent.elements[ccNode.parent.elements.indexOf(ccNode) + 1];
if (nextElement &&
isNeedEscapedForFirstElement(nextElement)) {
unwrapped.push("\\");
}
}
}
else {
if (ccNode.elements.length !== 1) {
return;
}
const element = ccNode.elements[0];
if (ignores.length > 0 &&
ignores.includes(element.raw)) {
return;
}
if (element.type === "Character") {
if (element.raw === "\\b") {
return;
}
if (/^\\\d+$/u.test(element.raw) &&
!element.raw.startsWith("\\0")) {
return;
}
if (ignores.length > 0 &&
ignores.includes(String.fromCodePoint(element.value))) {
return;
}
if (!(0, utils_1.canUnwrapped)(ccNode, element.raw)) {
return;
}
messageData = { type: "character" };
}
else if (element.type === "CharacterClassRange") {
if (element.min.value !== element.max.value) {
return;
}
messageData = {
type: "character class range",
additional: " and range",
};
unwrapped[0] =
(_b = (_a = getEscapedFirstRawIfNeeded(element.min)) !== null && _a !== void 0 ? _a : getEscapedLastRawIfNeeded(element.min)) !== null && _b !== void 0 ? _b : element.min.raw;
}
else if (element.type === "ClassStringDisjunction") {
if (!characterClassStack.length) {
return;
}
messageData = { type: "string literal" };
}
else if (element.type === "CharacterSet") {
messageData = { type: "character class escape" };
}
else if (element.type === "CharacterClass" ||
element.type === "ExpressionCharacterClass") {
messageData = { type: "character class" };
}
else {
return;
}
messageId = "unexpectedCharacterClassWith";
}
context.report({
node,
loc: getRegexpLocation(ccNode),
messageId,
data: {
type: messageData.type,
additional: messageData.additional || "",
},
fix: fixReplaceNode(ccNode, unwrapped.join("")),
});
function isNeedEscapedForFirstElement(element) {
const char = element.type === "Character"
? element.raw
: element.type === "CharacterClassRange"
? element.min.raw
: null;
if (char == null) {
return false;
}
if (characterClassStack.length) {
if (regex_syntax_1.RESERVED_DOUBLE_PUNCTUATOR_CHARS.has(char) &&
pattern[ccNode.start - 1] === char) {
return true;
}
return (char === "^" &&
ccNode.parent.type === "CharacterClass" &&
ccNode.parent.elements[0] === ccNode);
}
return (flags.unicode
? ESCAPES_OUTSIDE_CHARACTER_CLASS_WITH_U
: ESCAPES_OUTSIDE_CHARACTER_CLASS).has(char);
}
function needEscapedForLastElement(element) {
const char = element.type === "Character"
? element.raw
: element.type === "CharacterClassRange"
? element.max.raw
: null;
if (char == null) {
return false;
}
if (characterClassStack.length) {
return (regex_syntax_1.RESERVED_DOUBLE_PUNCTUATOR_CHARS.has(char) &&
pattern[ccNode.end] === char);
}
return false;
}
function getEscapedFirstRawIfNeeded(firstElement) {
if (isNeedEscapedForFirstElement(firstElement)) {
return `\\${firstElement.raw}`;
}
return null;
}
function getEscapedLastRawIfNeeded(lastElement) {
if (needEscapedForLastElement(lastElement)) {
const lastRaw = lastElement.type === "Character"
? lastElement.raw
: lastElement.type === "CharacterClassRange"
? lastElement.max.raw
: "";
const prefix = lastElement.raw.slice(0, -lastRaw.length);
return `${prefix}\\${lastRaw}`;
}
return null;
}
},
};
}
return (0, utils_1.defineRegexpVisitor)(context, {
createVisitor,
});
},
});
;