eslint-plugin-lingui
Version:
ESLint plugin for Lingui
122 lines • 5.49 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = exports.name = void 0;
const utils_1 = require("@typescript-eslint/utils");
const create_rule_1 = require("../create-rule");
const helpers_1 = require("../helpers");
exports.name = 'require-explicit-id';
exports.rule = (0, create_rule_1.createRule)({
name: exports.name,
meta: {
docs: {
description: "enforce 'id' property or attribute for Lingui macros",
recommended: 'error',
},
messages: {
default: "Trans component requires an explicit 'id' attribute",
missingIdCall: "Macro function call requires an explicit 'id' property",
noIdInTaggedTemplate: "Tagged template literal doesn't support 'id'. Use {{ fn }}({ id: '...', message: '...' }) instead",
invalidPattern: "'id' must match one of the patterns: {{ patterns }}",
},
schema: [
{
type: 'object',
properties: {
patterns: {
type: 'array',
items: {
type: 'string',
},
},
flags: {
type: 'string',
},
},
additionalProperties: false,
},
],
type: 'problem',
},
defaultOptions: [],
create: function (context) {
var _a;
const { options: [option], } = context;
const rulePatterns = (_a = option === null || option === void 0 ? void 0 : option.patterns) === null || _a === void 0 ? void 0 : _a.map((pattern) => new RegExp(pattern, option === null || option === void 0 ? void 0 : option.flags));
function validatePattern(node, idValue) {
if (!(rulePatterns === null || rulePatterns === void 0 ? void 0 : rulePatterns.length)) {
return;
}
if (!rulePatterns.some((pattern) => pattern.test(idValue))) {
context.report({
node,
messageId: 'invalidPattern',
data: { patterns: option.patterns.join(', ') },
});
}
}
return {
[helpers_1.LinguiTransQuery](node) {
const idAttr = node.openingElement.attributes.find((attr) => attr.type === utils_1.TSESTree.AST_NODE_TYPES.JSXAttribute &&
attr.name.type === utils_1.TSESTree.AST_NODE_TYPES.JSXIdentifier &&
attr.name.name === 'id');
if (!idAttr) {
context.report({
node,
messageId: 'default',
});
return;
}
// Only validate string literal values; skip complex expressions silently
let idValue = null;
if (idAttr.value &&
idAttr.value.type === utils_1.TSESTree.AST_NODE_TYPES.Literal &&
typeof idAttr.value.value === 'string') {
idValue = idAttr.value.value;
}
else if (idAttr.value &&
idAttr.value.type === utils_1.TSESTree.AST_NODE_TYPES.JSXExpressionContainer &&
idAttr.value.expression.type === utils_1.TSESTree.AST_NODE_TYPES.Literal &&
typeof idAttr.value.expression.value === 'string') {
// Handle id={"msg.hello"} the same as id="msg.hello"
idValue = idAttr.value.expression.value;
}
if (idValue == null) {
return;
}
validatePattern(idAttr, idValue);
},
[helpers_1.LinguiTaggedTemplateExpressionMessageQuery](node) {
const parent = node.parent;
const fn = parent.tag.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier ? parent.tag.name : 'function';
context.report({
node: parent,
messageId: 'noIdInTaggedTemplate',
data: { fn },
});
},
[helpers_1.LinguiCallExpressionQuery](node) {
const arg = node.arguments[0];
if (!arg || arg.type !== utils_1.TSESTree.AST_NODE_TYPES.ObjectExpression) {
return;
}
const idProp = arg.properties.find((prop) => prop.type === utils_1.TSESTree.AST_NODE_TYPES.Property &&
((prop.key.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier && prop.key.name === 'id') ||
(prop.key.type === utils_1.TSESTree.AST_NODE_TYPES.Literal && prop.key.value === 'id')));
if (!idProp) {
context.report({
node,
messageId: 'missingIdCall',
});
return;
}
// Only validate string literal values; skip expressions silently
if (idProp.value.type !== utils_1.TSESTree.AST_NODE_TYPES.Literal ||
typeof idProp.value.value !== 'string') {
return;
}
validatePattern(idProp, idProp.value.value);
},
};
},
});
//# sourceMappingURL=require-explicit-id.js.map