Skip to content

Commit c33e2d5

Browse files
authored
feat(no-expression-in-message): add support for ph and explicit labels (#100)
1 parent fec90fe commit c33e2d5

File tree

2 files changed

+68
-12
lines changed

2 files changed

+68
-12
lines changed

src/rules/no-expression-in-message.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export const rule = createRule({
1616
},
1717
messages: {
1818
default: 'Should be ${variable}, not ${object.property} or ${myFunction()}',
19+
multiplePlaceholders:
20+
'Invalid placeholder: Expected an object with a single key-value pair, but found multiple keys',
1921
},
2022
schema: [
2123
{
@@ -29,27 +31,43 @@ export const rule = createRule({
2931

3032
defaultOptions: [],
3133
create: function (context) {
32-
const linguiMacroFunctionNames = ['plural', 'select', 'selectOrdinal']
34+
const linguiMacroFunctionNames = ['plural', 'select', 'selectOrdinal', 'ph']
3335

3436
function checkExpressionsInTplLiteral(node: TSESTree.TemplateLiteral) {
35-
node.expressions.forEach((expression) => {
36-
if (expression.type === TSESTree.AST_NODE_TYPES.Identifier) {
37-
return
38-
}
37+
node.expressions.forEach((expression) => checkExpression(expression))
38+
}
39+
40+
function checkExpression(expression: TSESTree.Expression) {
41+
if (expression.type === TSESTree.AST_NODE_TYPES.Identifier) {
42+
return
43+
}
44+
45+
const isCallToLinguiMacro =
46+
expression.type === TSESTree.AST_NODE_TYPES.CallExpression &&
47+
expression.callee.type === TSESTree.AST_NODE_TYPES.Identifier &&
48+
linguiMacroFunctionNames.includes(expression.callee.name)
49+
50+
if (isCallToLinguiMacro) {
51+
return
52+
}
3953

40-
const isCallToLinguiMacro =
41-
expression.type === TSESTree.AST_NODE_TYPES.CallExpression &&
42-
expression.callee.type === TSESTree.AST_NODE_TYPES.Identifier &&
43-
linguiMacroFunctionNames.includes(expression.callee.name)
54+
const isExplicitLabel = expression.type === TSESTree.AST_NODE_TYPES.ObjectExpression
4455

45-
if (isCallToLinguiMacro) {
56+
if (isExplicitLabel) {
57+
// there can be only one key in the object
58+
if (expression.properties.length === 1) {
4659
return
4760
}
48-
4961
context.report({
5062
node: expression,
51-
messageId: 'default',
63+
messageId: 'multiplePlaceholders',
5264
})
65+
return
66+
}
67+
68+
context.report({
69+
node: expression,
70+
messageId: 'default',
5371
})
5472
}
5573

@@ -76,6 +94,16 @@ export const rule = createRule({
7694
return checkExpressionsInTplLiteral(node)
7795
}
7896

97+
if (node.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
98+
// <Trans>Hello {{name: obj.prop}}</Trans>
99+
return checkExpression(node)
100+
}
101+
102+
if (node.type === TSESTree.AST_NODE_TYPES.CallExpression) {
103+
// <Trans>Hello {ph({name: obj.prop})}</Trans>
104+
return checkExpression(node)
105+
}
106+
79107
if (node.type !== TSESTree.AST_NODE_TYPES.Identifier) {
80108
context.report({
81109
node,

tests/src/rules/no-expression-in-message.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ ruleTester.run(name, rule, {
8787
name: 'Strings as children are preserved',
8888
code: '<Trans>{"hello {count, plural, one {world} other {worlds}}"}</Trans>',
8989
},
90+
{
91+
code: 't`hello ${{name: obj.prop}}`',
92+
},
93+
{
94+
code: 't`hello ${ph({name: obj.prop})}`',
95+
},
96+
{
97+
code: '<Trans>hello {{name: obj.prop}}</Trans>',
98+
},
99+
{
100+
code: '<Trans>hello {ph({name: obj.prop})}</Trans>',
101+
},
90102
],
91103
invalid: [
92104
{
@@ -131,5 +143,21 @@ ruleTester.run(name, rule, {
131143
code: 't`hello ${func()}?`',
132144
errors: [{ messageId: 'default' }],
133145
},
146+
{
147+
code: 't`hello ${{name: obj.foo, surname: obj.bar}}`',
148+
errors: [{ messageId: 'multiplePlaceholders' }],
149+
},
150+
{
151+
code: 't`hello ${greeting({name: obj.prop})}`',
152+
errors: [{ messageId: 'default' }],
153+
},
154+
{
155+
code: '<Trans>hello {{name: obj.foo, surname: obj.bar}}</Trans>',
156+
errors: [{ messageId: 'multiplePlaceholders' }],
157+
},
158+
{
159+
code: '<Trans>hello {greeting({name: obj.prop})}</Trans>',
160+
errors: [{ messageId: 'default' }],
161+
},
134162
],
135163
})

0 commit comments

Comments
 (0)