186 lines
3.3 KiB
JavaScript
186 lines
3.3 KiB
JavaScript
'use strict';
|
|
const getDocumentationUrl = require('./utils/get-documentation-url');
|
|
const quoteString = require('./utils/quote-string');
|
|
const replaceTemplateElement = require('./utils/replace-template-element');
|
|
const escapeTemplateElementRaw = require('./utils/escape-template-element-raw');
|
|
|
|
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;
|
|
};
|
|
|
|
const defaultMessage = 'Prefer `{{suggest}}` over `{{match}}`.';
|
|
const SUGGESTION_MESSAGE_ID = 'replace';
|
|
|
|
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
|
|
};
|
|
});
|
|
}
|
|
|
|
const create = context => {
|
|
const {patterns} = {
|
|
patterns: {},
|
|
...context.options[0]
|
|
};
|
|
const replacements = getReplacements(patterns);
|
|
|
|
if (replacements.length === 0) {
|
|
return {};
|
|
}
|
|
|
|
return {
|
|
'Literal, TemplateElement': node => {
|
|
const {type} = node;
|
|
|
|
let string;
|
|
if (type === 'Literal') {
|
|
string = node.value;
|
|
} else if (!isIgnoredTag(node)) {
|
|
string = node.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} = replacement;
|
|
const messageData = {
|
|
match,
|
|
suggest
|
|
};
|
|
const problem = {
|
|
node,
|
|
message,
|
|
data: messageData
|
|
};
|
|
|
|
const fixed = string.replace(replacement.regex, suggest);
|
|
const fix = type === 'Literal' ?
|
|
fixer => fixer.replaceText(
|
|
node,
|
|
quoteString(fixed, node.raw[0])
|
|
) :
|
|
fixer => replaceTemplateElement(
|
|
fixer,
|
|
node,
|
|
escapeTemplateElementRaw(fixed)
|
|
);
|
|
|
|
if (autoFix) {
|
|
problem.fix = fix;
|
|
} else {
|
|
problem.suggest = [
|
|
{
|
|
messageId: SUGGESTION_MESSAGE_ID,
|
|
data: messageData,
|
|
fix
|
|
}
|
|
];
|
|
}
|
|
|
|
context.report(problem);
|
|
}
|
|
};
|
|
};
|
|
|
|
const schema = [
|
|
{
|
|
type: 'object',
|
|
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
|
|
}
|
|
]
|
|
}}
|
|
},
|
|
additionalProperties: false
|
|
}
|
|
];
|
|
|
|
module.exports = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
url: getDocumentationUrl(__filename)
|
|
},
|
|
fixable: 'code',
|
|
schema,
|
|
messages: {
|
|
[SUGGESTION_MESSAGE_ID]: 'Replace `{{match}}` with `{{suggest}}`.'
|
|
}
|
|
}
|
|
};
|