eslint-plugin-regexp
Version:
ESLint plugin for finding RegExp mistakes and RegExp style guide violations.
157 lines (156 loc) • 6.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const regex_syntax_1 = require("../utils/regex-syntax");
const CASE_SCHEMA = ["lowercase", "uppercase", "ignore"];
const DEFAULTS = {
caseInsensitive: "lowercase",
unicodeEscape: "lowercase",
hexadecimalEscape: "lowercase",
controlEscape: "uppercase",
};
function parseOptions(option) {
if (!option) {
return DEFAULTS;
}
return {
caseInsensitive: option.caseInsensitive || DEFAULTS.caseInsensitive,
unicodeEscape: option.unicodeEscape || DEFAULTS.unicodeEscape,
hexadecimalEscape: option.hexadecimalEscape || DEFAULTS.hexadecimalEscape,
controlEscape: option.controlEscape || DEFAULTS.controlEscape,
};
}
const CODE_POINT_CASE_CHECKER = {
lowercase: utils_1.isLowercaseLetter,
uppercase: utils_1.isUppercaseLetter,
};
const STRING_CASE_CHECKER = {
lowercase: (s) => s.toLowerCase() === s,
uppercase: (s) => s.toUpperCase() === s,
};
const CONVERTER = {
lowercase: (s) => s.toLowerCase(),
uppercase: (s) => s.toUpperCase(),
};
exports.default = (0, utils_1.createRule)("letter-case", {
meta: {
docs: {
description: "enforce into your favorite case",
category: "Stylistic Issues",
recommended: false,
},
fixable: "code",
schema: [
{
type: "object",
properties: {
caseInsensitive: { enum: CASE_SCHEMA },
unicodeEscape: { enum: CASE_SCHEMA },
hexadecimalEscape: { enum: CASE_SCHEMA },
controlEscape: { enum: CASE_SCHEMA },
},
additionalProperties: false,
},
],
messages: {
unexpected: "'{{char}}' is not in {{case}}",
},
type: "layout",
},
create(context) {
const options = parseOptions(context.options[0]);
function report({ node, getRegexpLocation, fixReplaceNode }, reportNode, letterCase, convertText) {
context.report({
node,
loc: getRegexpLocation(reportNode),
messageId: "unexpected",
data: {
char: reportNode.raw,
case: letterCase,
},
fix: fixReplaceNode(reportNode, () => convertText(CONVERTER[letterCase])),
});
}
function verifyCharacterInCaseInsensitive(regexpContext, cNode) {
if (cNode.parent.type === "CharacterClassRange" ||
options.caseInsensitive === "ignore") {
return;
}
if (CODE_POINT_CASE_CHECKER[options.caseInsensitive](cNode.value) ||
!(0, utils_1.isLetter)(cNode.value)) {
return;
}
report(regexpContext, cNode, options.caseInsensitive, (converter) => converter(String.fromCodePoint(cNode.value)));
}
function verifyCharacterClassRangeInCaseInsensitive(regexpContext, ccrNode) {
if (options.caseInsensitive === "ignore") {
return;
}
if (CODE_POINT_CASE_CHECKER[options.caseInsensitive](ccrNode.min.value) ||
!(0, utils_1.isLetter)(ccrNode.min.value) ||
CODE_POINT_CASE_CHECKER[options.caseInsensitive](ccrNode.max.value) ||
!(0, utils_1.isLetter)(ccrNode.max.value)) {
return;
}
report(regexpContext, ccrNode, options.caseInsensitive, (converter) => `${converter(String.fromCodePoint(ccrNode.min.value))}-${converter(String.fromCodePoint(ccrNode.max.value))}`);
}
function verifyCharacterInUnicodeEscape(regexpContext, cNode) {
if (options.unicodeEscape === "ignore") {
return;
}
const parts = /^(?<prefix>\\u\{?)(?<code>.*?)(?<suffix>\}?)$/u.exec(cNode.raw);
if (STRING_CASE_CHECKER[options.unicodeEscape](parts.groups.code)) {
return;
}
report(regexpContext, cNode, options.unicodeEscape, (converter) => `${parts.groups.prefix}${converter(parts.groups.code)}${parts.groups.suffix}`);
}
function verifyCharacterInHexadecimalEscape(regexpContext, cNode) {
if (options.hexadecimalEscape === "ignore") {
return;
}
const parts = /^\\x(?<code>.*)$/u.exec(cNode.raw);
if (STRING_CASE_CHECKER[options.hexadecimalEscape](parts.groups.code)) {
return;
}
report(regexpContext, cNode, options.hexadecimalEscape, (converter) => `\\x${converter(parts.groups.code)}`);
}
function verifyCharacterInControl(regexpContext, cNode) {
if (options.controlEscape === "ignore") {
return;
}
const parts = /^\\c(?<code>.*)$/u.exec(cNode.raw);
if (STRING_CASE_CHECKER[options.controlEscape](parts.groups.code)) {
return;
}
report(regexpContext, cNode, options.controlEscape, (converter) => `\\c${converter(parts.groups.code)}`);
}
function createVisitor(regexpContext) {
const { flags } = regexpContext;
return Object.assign({ onCharacterEnter(cNode) {
if (flags.ignoreCase) {
verifyCharacterInCaseInsensitive(regexpContext, cNode);
}
const escapeKind = (0, regex_syntax_1.getEscapeSequenceKind)(cNode.raw);
if (escapeKind === regex_syntax_1.EscapeSequenceKind.unicode ||
escapeKind === regex_syntax_1.EscapeSequenceKind.unicodeCodePoint) {
verifyCharacterInUnicodeEscape(regexpContext, cNode);
}
if (escapeKind === regex_syntax_1.EscapeSequenceKind.hexadecimal) {
verifyCharacterInHexadecimalEscape(regexpContext, cNode);
}
if (escapeKind === regex_syntax_1.EscapeSequenceKind.control) {
verifyCharacterInControl(regexpContext, cNode);
}
} }, (flags.ignoreCase
? {
onCharacterClassRangeEnter(ccrNode) {
verifyCharacterClassRangeInCaseInsensitive(regexpContext, ccrNode);
},
}
: {}));
}
return (0, utils_1.defineRegexpVisitor)(context, {
createVisitor,
});
},
});
;