eslint-plugin-unicorn
Version:
More than 300 powerful ESLint rules
139 lines (118 loc) • 3.15 kB
JavaScript
import {isStringLiteral} from './ast/index.js';
import {fixSpaceAroundKeyword} from './fix/index.js';
import isJestInlineSnapshot from './shared/is-jest-inline-snapshot.js';
import {isStringLiteralRequired} from './utils/index.js';
const MESSAGE_ID = 'prefer-string-repeat';
const messages = {
[MESSAGE_ID]: 'Use `String#repeat()` for repeated whitespace.',
};
const singleWhitespace = /^\s$/u;
const namedEscapes = new Map([
['\t', String.raw`\t`],
['\n', String.raw`\n`],
['\r', String.raw`\r`],
['\f', String.raw`\f`],
]);
// Escape a single whitespace character into a readable single-quoted string literal.
function quoteWhitespace(character) {
if (character === ' ') {
return '\' \'';
}
const escape = namedEscapes.get(character)
?? String.raw`\u{${character.codePointAt(0).toString(16).toUpperCase()}}`;
return `'${escape}'`;
}
function getRepeatedWhitespace(value, minimumRepetitions) {
const characters = [...value];
if (characters.length < minimumRepetitions) {
return;
}
const [character] = characters;
if (!singleWhitespace.test(character)) {
return;
}
if (characters.every(element => element === character)) {
return {
character,
count: characters.length,
};
}
}
function getProblem(node, value, context, minimumRepetitions) {
const repeatedWhitespace = getRepeatedWhitespace(value, minimumRepetitions);
if (!repeatedWhitespace) {
return;
}
const replacement = `${quoteWhitespace(repeatedWhitespace.character)}.repeat(${repeatedWhitespace.count})`;
return {
node,
messageId: MESSAGE_ID,
* fix(fixer) {
yield fixSpaceAroundKeyword(fixer, node, context);
yield fixer.replaceText(node, replacement);
},
};
}
/**
@param {import('eslint').Rule.RuleContext} context
*/
const create = context => {
const {minimumRepetitions} = context.options[0];
context.on('Literal', node => {
if (!isStringLiteral(node) || isStringLiteralRequired(node) || isJestInlineSnapshot(node)) {
return;
}
return getProblem(node, node.value, context, minimumRepetitions);
});
context.on('TemplateLiteral', node => {
if (
node.parent.type === 'TaggedTemplateExpression'
|| node.expressions.length > 0
|| isStringLiteralRequired(node)
|| isJestInlineSnapshot(node)
) {
return;
}
// An invalid escape sequence is only allowed in a tagged template, so `cooked` is always a string here
const [{value}] = node.quasis;
return getProblem(node, value.cooked, context, minimumRepetitions);
});
};
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
minimumRepetitions: {
type: 'integer',
minimum: 2,
description: 'The minimum number of repeated whitespace characters before `String#repeat()` is enforced.',
},
},
},
];
/**
@type {import('eslint').Rule.RuleModule}
*/
const config = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `String#repeat()` for repeated whitespace.',
recommended: 'unopinionated',
},
fixable: 'code',
schema,
defaultOptions: [
{
minimumRepetitions: 3,
},
],
messages,
languages: [
'js/js',
],
},
};
export default config;