UNPKG

eslint-plugin-unicorn

Version:
139 lines (118 loc) • 3.15 kB
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;