eslint-plugin-regexp
Version:
ESLint plugin for finding RegExp mistakes and RegExp style guide violations.
223 lines (222 loc) • 7.86 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.PatternSource = exports.PatternReplaceRange = void 0;
const utils_1 = require("./utils");
class PatternReplaceRange {
constructor(range, type) {
if (!range || range[0] < 0 || range[0] > range[1]) {
throw new Error(`Invalid range: ${JSON.stringify(range)}`);
}
this.range = range;
this.type = type;
}
static fromLiteral(node, sourceCode, nodeRange, range) {
if (!node.range) {
return null;
}
const start = range.start - nodeRange.start;
const end = range.end - nodeRange.start;
if ((0, utils_1.isRegexpLiteral)(node)) {
const nodeStart = node.range[0] + "/".length;
return new PatternReplaceRange([nodeStart + start, nodeStart + end], "RegExp");
}
if ((0, utils_1.isStringLiteral)(node)) {
const astRange = (0, utils_1.getStringValueRange)(sourceCode, node, start, end);
if (astRange) {
const quote = sourceCode.text[node.range[0]];
return new PatternReplaceRange(astRange, quote === "'" ? "SingleQuotedString" : "DoubleQuotedString");
}
}
return null;
}
getAstLocation(sourceCode) {
return (0, utils_1.astRangeToLocation)(sourceCode, this.range);
}
escape(text) {
if (this.type === "DoubleQuotedString" ||
this.type === "SingleQuotedString") {
const base = text
.replace(/\\/gu, "\\\\")
.replace(/\n/gu, "\\n")
.replace(/\r/gu, "\\r")
.replace(/\t/gu, "\\t");
if (this.type === "DoubleQuotedString") {
return base.replace(/"/gu, '\\"');
}
return base.replace(/'/gu, "\\'");
}
return text.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r");
}
replace(fixer, text) {
return fixer.replaceTextRange(this.range, this.escape(text));
}
remove(fixer) {
return fixer.removeRange(this.range);
}
insertAfter(fixer, text) {
return fixer.insertTextAfterRange(this.range, this.escape(text));
}
insertBefore(fixer, text) {
return fixer.insertTextBeforeRange(this.range, this.escape(text));
}
}
exports.PatternReplaceRange = PatternReplaceRange;
class PatternSegment {
constructor(sourceCode, node, value, start) {
this.sourceCode = sourceCode;
this.node = node;
this.value = value;
this.start = start;
this.end = start + value.length;
}
contains(range) {
return this.start <= range.start && range.end <= this.end;
}
getOwnedRegExpLiteral() {
if ((0, utils_1.isRegexpLiteral)(this.node)) {
return this.node;
}
if (this.node.type === "MemberExpression" &&
this.node.object.type !== "Super" &&
(0, utils_1.isRegexpLiteral)(this.node.object) &&
(0, utils_1.getPropertyName)(this.node) === "source") {
return this.node.object;
}
return null;
}
getReplaceRange(range) {
if (!this.contains(range)) {
return null;
}
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);
}
return null;
}
getAstRange(range) {
const replaceRange = this.getReplaceRange(range);
if (replaceRange) {
return replaceRange.range;
}
return this.node.range;
}
}
class PatternSource {
isStringValue() {
return this.regexpValue === null;
}
constructor(sourceCode, node, value, segments, regexpValue) {
this.sourceCode = sourceCode;
this.node = node;
this.value = value;
this.segments = segments;
this.regexpValue = regexpValue;
}
static fromExpression(context, expression) {
expression = (0, utils_1.dereferenceOwnedVariable)(context, expression);
if ((0, utils_1.isRegexpLiteral)(expression)) {
return PatternSource.fromRegExpLiteral(context, expression);
}
const sourceCode = context.sourceCode;
const flat = flattenPlus(context, expression);
const items = [];
let value = "";
for (const e of flat) {
const staticValue = (0, utils_1.getStaticValue)(context, e);
if (!staticValue) {
return null;
}
if (flat.length === 1 && staticValue.value instanceof RegExp) {
return PatternSource.fromRegExpObject(context, e, staticValue.value.source, staticValue.value.flags);
}
if (typeof staticValue.value !== "string") {
return null;
}
items.push(new PatternSegment(sourceCode, e, staticValue.value, value.length));
value += staticValue.value;
}
return new PatternSource(sourceCode, expression, value, items, null);
}
static fromRegExpObject(context, expression, source, flags) {
const sourceCode = context.sourceCode;
return new PatternSource(sourceCode, expression, source, [new PatternSegment(sourceCode, expression, source, 0)], {
source,
flags,
ownedNode: null,
});
}
static fromRegExpLiteral(context, expression) {
const sourceCode = context.sourceCode;
return new PatternSource(sourceCode, expression, expression.regex.pattern, [
new PatternSegment(sourceCode, expression, expression.regex.pattern, 0),
], {
source: expression.regex.pattern,
flags: expression.regex.flags,
ownedNode: expression,
});
}
getSegment(range) {
const segments = this.getSegments(range);
if (segments.length === 1) {
return segments[0];
}
return null;
}
getSegments(range) {
return this.segments.filter((item) => item.start < range.end && range.start < item.end);
}
getReplaceRange(range) {
const segment = this.getSegment(range);
if (segment) {
return segment.getReplaceRange(range);
}
return null;
}
getAstRange(range) {
const overlapping = this.getSegments(range);
if (overlapping.length === 1) {
return overlapping[0].getAstRange(range);
}
let min = Infinity;
let max = -Infinity;
for (const item of overlapping) {
min = Math.min(min, item.node.range[0]);
max = Math.max(max, item.node.range[1]);
}
if (min > max) {
return this.node.range;
}
return [min, max];
}
getAstLocation(range) {
return (0, utils_1.astRangeToLocation)(this.sourceCode, this.getAstRange(range));
}
getOwnedRegExpLiterals() {
const literals = [];
for (const segment of this.segments) {
const regexp = segment.getOwnedRegExpLiteral();
if (regexp) {
literals.push(regexp);
}
}
return literals;
}
}
exports.PatternSource = PatternSource;
function flattenPlus(context, e) {
if (e.type === "BinaryExpression" && e.operator === "+") {
return [
...flattenPlus(context, e.left),
...flattenPlus(context, e.right),
];
}
const deRef = (0, utils_1.dereferenceOwnedVariable)(context, e);
if (deRef !== e) {
return flattenPlus(context, deRef);
}
return [e];
}
;