187 lines
3.4 KiB
JavaScript
187 lines
3.4 KiB
JavaScript
'use strict';
|
|
const escapeString = require('./utils/escape-string.js');
|
|
const escapeTemplateElementRaw = require('./utils/escape-template-element-raw.js');
|
|
const {replaceTemplateElement} = require('./fix/index.js');
|
|
|
|
const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.';
|
|
const SUGGESTION_MESSAGE_ID = 'replace';
|
|
const messages = {
|
|
[SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.',
|
|
};
|
|
|
|
const ignoredIdentifier = new Set([
|
|
'gql',
|
|
'html',
|
|
'svg',
|
|
]);
|
|
|
|
const ignoredMemberExpressionObject = new Set([
|
|
'styled',
|
|
]);
|
|
|
|
const isIgnoredTag = node => {
|
|
if (!node.parent || !node.parent.parent || !node.parent.parent.tag) {
|
|
return false;
|
|
}
|
|
|
|
const {tag} = node.parent.parent;
|
|
|
|
if (tag.type === 'Identifier' && ignoredIdentifier.has(tag.name)) {
|
|
return true;
|
|
}
|
|
|
|
if (tag.type === 'MemberExpression') {
|
|
const {object} = tag;
|
|
if (
|
|
object.type === 'Identifier'
|
|
&& ignoredMemberExpressionObject.has(object.name)
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
function getReplacements(patterns) {
|
|
return Object.entries(patterns)
|
|
.map(([match, options]) => {
|
|
if (typeof options === 'string') {
|
|
options = {
|
|
suggest: options,
|
|
};
|
|
}
|
|
|
|
return {
|
|
match,
|
|
regex: new RegExp(match, 'gu'),
|
|
fix: true,
|
|
...options,
|
|
};
|
|
});
|
|
}
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const {patterns} = {
|
|
patterns: {},
|
|
...context.options[0],
|
|
};
|
|
const replacements = getReplacements(patterns);
|
|
|
|
if (replacements.length === 0) {
|
|
return;
|
|
}
|
|
|
|
return {
|
|
'Literal, TemplateElement'(node) {
|
|
const {type, value, raw} = node;
|
|
|
|
let string;
|
|
if (type === 'Literal') {
|
|
string = value;
|
|
} else if (!isIgnoredTag(node)) {
|
|
string = value.raw;
|
|
}
|
|
|
|
if (!string || typeof string !== 'string') {
|
|
return;
|
|
}
|
|
|
|
const replacement = replacements.find(({regex}) => regex.test(string));
|
|
|
|
if (!replacement) {
|
|
return;
|
|
}
|
|
|
|
const {fix: autoFix, message = defaultMessage, match, suggest, regex} = replacement;
|
|
const problem = {
|
|
node,
|
|
message,
|
|
data: {
|
|
match,
|
|
suggest,
|
|
},
|
|
};
|
|
|
|
const fixed = string.replace(regex, suggest);
|
|
const fix = type === 'Literal'
|
|
? fixer => fixer.replaceText(
|
|
node,
|
|
escapeString(fixed, raw[0]),
|
|
)
|
|
: fixer => replaceTemplateElement(
|
|
fixer,
|
|
node,
|
|
escapeTemplateElementRaw(fixed),
|
|
);
|
|
|
|
if (autoFix) {
|
|
problem.fix = fix;
|
|
} else {
|
|
problem.suggest = [
|
|
{
|
|
messageId: SUGGESTION_MESSAGE_ID,
|
|
fix,
|
|
},
|
|
];
|
|
}
|
|
|
|
return problem;
|
|
},
|
|
};
|
|
};
|
|
|
|
const schema = [
|
|
{
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
patterns: {
|
|
type: 'object',
|
|
additionalProperties: {
|
|
anyOf: [
|
|
{
|
|
type: 'string',
|
|
},
|
|
{
|
|
type: 'object',
|
|
required: [
|
|
'suggest',
|
|
],
|
|
properties: {
|
|
suggest: {
|
|
type: 'string',
|
|
},
|
|
fix: {
|
|
type: 'boolean',
|
|
// Default: true
|
|
},
|
|
message: {
|
|
type: 'string',
|
|
// Default: ''
|
|
},
|
|
},
|
|
additionalProperties: false,
|
|
},
|
|
],
|
|
}},
|
|
},
|
|
},
|
|
];
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Enforce better string content.',
|
|
},
|
|
fixable: 'code',
|
|
hasSuggestions: true,
|
|
schema,
|
|
messages,
|
|
},
|
|
};
|