UNPKG

eslint-plugin-formatjs

Version:
145 lines (144 loc) 5.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.rule = exports.name = void 0; const icu_messageformat_parser_1 = require("@formatjs/icu-messageformat-parser"); const context_compat_1 = require("../context-compat"); const util_1 = require("../util"); function isAstValid(ast) { for (const element of ast) { switch (element.type) { case icu_messageformat_parser_1.TYPE.literal: if (/\s{2,}/gm.test(element.value)) { return false; } break; case icu_messageformat_parser_1.TYPE.argument: case icu_messageformat_parser_1.TYPE.date: case icu_messageformat_parser_1.TYPE.literal: case icu_messageformat_parser_1.TYPE.number: case icu_messageformat_parser_1.TYPE.pound: case icu_messageformat_parser_1.TYPE.time: break; case icu_messageformat_parser_1.TYPE.plural: case icu_messageformat_parser_1.TYPE.select: { for (const option of Object.values(element.options)) { if (!isAstValid(option.value)) { return false; } } break; } case icu_messageformat_parser_1.TYPE.tag: return isAstValid(element.children); } } return true; } function trimMultiWhitespaces(message, ast) { const literalElements = []; const collectLiteralElements = (elements) => { for (const element of elements) { switch (element.type) { case icu_messageformat_parser_1.TYPE.literal: literalElements.push(element); break; case icu_messageformat_parser_1.TYPE.argument: case icu_messageformat_parser_1.TYPE.date: case icu_messageformat_parser_1.TYPE.literal: case icu_messageformat_parser_1.TYPE.number: case icu_messageformat_parser_1.TYPE.pound: case icu_messageformat_parser_1.TYPE.time: break; case icu_messageformat_parser_1.TYPE.plural: case icu_messageformat_parser_1.TYPE.select: { for (const option of Object.values(element.options)) { collectLiteralElements(option.value); } break; } case icu_messageformat_parser_1.TYPE.tag: collectLiteralElements(element.children); break; } } }; collectLiteralElements(ast); // Surgically trim whitespaces in the literal element ranges. // This is to preserve the original whitespaces and newlines info that are lost to parsing. let trimmedFragments = []; let currentOffset = 0; for (const literal of literalElements) { const { start, end } = literal.location; const startOffset = start.offset; const endOffset = end.offset; trimmedFragments.push(message.slice(currentOffset, startOffset)); trimmedFragments.push(message.slice(startOffset, endOffset).replace(/\s{2,}/gm, ' ')); currentOffset = endOffset; } trimmedFragments.push(message.slice(currentOffset)); return trimmedFragments.join(''); } function checkNode(context, node) { const msgs = (0, util_1.extractMessages)(node, (0, util_1.getSettings)(context)); for (const [{ message: { defaultMessage }, messageNode, },] of msgs) { if (!defaultMessage || !messageNode) { continue; } let ast; try { ast = (0, icu_messageformat_parser_1.parse)(defaultMessage, { captureLocation: true }); } catch (e) { context.report({ node: messageNode, messageId: 'parserError', data: { message: e instanceof Error ? e.message : String(e) }, }); return; } if (!isAstValid(ast)) { const newMessage = (0, util_1.patchMessage)(messageNode, ast, trimMultiWhitespaces); context.report({ node: messageNode, messageId: 'noMultipleWhitespaces', fix: newMessage !== null ? fixer => fixer.replaceText(messageNode, newMessage) : null, }); } } } exports.name = 'no-multiple-whitespaces'; exports.rule = { meta: { type: 'problem', docs: { description: 'Prevents usage of multiple consecutive whitespaces in message', url: 'https://formatjs.github.io/docs/tooling/linter#no-multiple-whitespaces', }, messages: { noMultipleWhitespaces: 'Multiple consecutive whitespaces are not allowed', parserError: '{{message}}', }, fixable: 'code', schema: [], }, defaultOptions: [], create(context) { const callExpressionVisitor = (node) => checkNode(context, node); const parserServices = (0, context_compat_1.getParserServices)(context); //@ts-expect-error defineTemplateBodyVisitor exists in Vue parser if (parserServices?.defineTemplateBodyVisitor) { //@ts-expect-error return parserServices.defineTemplateBodyVisitor({ CallExpression: callExpressionVisitor, }, { CallExpression: callExpressionVisitor, }); } return { JSXOpeningElement: (node) => checkNode(context, node), CallExpression: callExpressionVisitor, }; }, };