eslint-plugin-formatjs
Version:
ESLint plugin for formatjs
145 lines (144 loc) • 5.74 kB
JavaScript
;
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,
};
},
};