@putout/plugin-remove-useless-escape
Version:
🐊Putout plugin adds ability to find and remove useless escape
176 lines (131 loc) • 4.17 kB
JavaScript
import emojiRegex from 'emoji-regex';
import {types, operator} from 'putout';
const {replaceWith} = operator;
const {regExpLiteral} = types;
const {assign} = Object;
export const report = () => 'Unnecessary escape character';
export const fix = (path) => {
if (path.isStringLiteral()) {
const {raw} = path.node;
path.node.raw = unEscape(raw);
return;
}
if (path.isRegExpLiteral()) {
const {pattern, flags} = path.node;
const unescaped = unescapeRegExp(pattern);
const raw = `/${unescaped}/`;
const regExpNode = assign(regExpLiteral(unescaped, flags), {
value: unescaped,
raw,
extra: {
raw,
rawValue: unescaped,
},
});
replaceWith(path, regExpNode);
return;
}
for (const tmpl of path.node.quasis) {
const {raw} = tmpl.value;
tmpl.value.raw = unEscape(raw);
}
};
export const traverse = ({push}) => ({
'RegExpLiteral'(path) {
const {raw} = path.node;
if (isEscapedRegExp(raw))
push(path);
},
'"__"'(path) {
const {raw} = path.node;
if (isEscaped(raw))
push(path);
},
'`__`'(path) {
for (const tmpl of path.node.quasis) {
const {raw} = tmpl.value;
if (isEscaped(raw))
return push(path);
if (hasQuote(raw))
return push(path);
if (raw.includes('\\"') && !raw.includes(`\\\\"`))
return push(path);
if (raw.includes(`\\'`) && !raw.includes(`\\\\'`))
return push(path);
}
},
});
const createCheckRegExp = (a) => RegExp(`^((?!\\\\).)*\\\\${a}.`);
const match = (a) => a.match(emojiRegex()) || [];
const hasA = (a) => /\\\^/.test(a);
const hasDoubleQuote = (a) => createCheckRegExp('"').test(a);
const hasQuote = (a) => createCheckRegExp(`'`).test(a);
const hasComa = (a) => createCheckRegExp(',').test(a);
const hasEmoji = (a) => {
for (const emoji of match(a)) {
if (a.includes(`\\${emoji}`))
return true;
}
return false;
};
function isEscaped(raw) {
if (!raw)
return false;
if (!raw.includes('\\'))
return false;
if (raw.includes('\\h') && !raw.includes('\\\\h'))
return true;
if (/\\\\/g.test(raw))
return false;
if (/\\\//g.test(raw))
return true;
if (raw.includes('\\$'))
return true;
if (raw.includes('\\.'))
return true;
if (raw.includes('\\{'))
return true;
if (raw.includes('\\+') && !raw.includes('\\\\+'))
return true;
if (hasDoubleQuote(raw))
return true;
if (hasEmoji(raw))
return true;
if (hasA(raw))
return true;
return hasComa(raw);
}
const createEncodedRegExp = (a) => RegExp(`\\\\${a}`, 'g');
function unEscape(raw) {
raw = raw
.replaceAll(`\\'`, `'`)
.replaceAll('\\/', '/')
.replaceAll('\\+', '+')
.replace(createEncodedRegExp(`"`), '"')
.replaceAll('\\^', '^')
.replaceAll('\\$', '$')
.replaceAll('\\{', '{')
.replaceAll('\\.', '.')
.replace(/(\\),/, ',')
.replaceAll('\\h', 'h');
for (const emoji of match(raw)) {
raw = raw.replace(createEncodedRegExp(emoji), emoji);
}
return raw;
}
const unescapeRegExp = (raw) => raw
.replaceAll('\\:', ':')
.replaceAll('\\+\\/', '+/')
.replaceAll('\\,', ',')
.replaceAll('\\`', '`');
const is = (a) => (b) => b.includes(`\\${a}`) && !b.includes(`\\\\${a}`);
const isRegExpColon = is(':');
const isComa = is(',');
const isRegExpSlash = (a) => a.includes('\\\\\\\\');
function isEscapedRegExp(raw) {
if (raw.includes('\\/'))
return false;
if (raw.includes('\\`'))
return true;
return isRegExpColon(raw) || isRegExpSlash(raw) || isComa(raw);
}