Support msg({...}) descriptor form and add auto-fix
- Allow msg() function call form: _(msg({message: 'Hello'}))
- Add auto-fix for string literals: _('Bad') -> _(msg`Bad`)
- Add auto-fix for untagged templates: _(`Bad`) -> _(msg`Bad`)
- No auto-fix for variables/function calls (not safely fixable)
https://claude.ai/code/session_01JMXXPUgAHiSBGmfGwUojKy
This commit is contained in:
@@ -57,79 +57,128 @@ const position = 1
|
||||
const x = _(selectOrdinal(position, {one: '#st', two: '#nd', few: '#rd', other: '#th'}))
|
||||
`,
|
||||
},
|
||||
// Different function named _ (not from useLingui context, but rule doesn't track that)
|
||||
// This is fine - the rule just checks the pattern
|
||||
// msg function call with object (descriptor form)
|
||||
{
|
||||
code: `
|
||||
const _ = (x) => x
|
||||
const x = _(someValue)
|
||||
const {_} = useLingui()
|
||||
const x = _(msg({message: 'Hello'}))
|
||||
`,
|
||||
},
|
||||
// msg function call with object and context
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _(msg({message: 'Hello', context: 'greeting'}))
|
||||
`,
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
// Plain string literal (single quotes)
|
||||
// Plain string literal (single quotes) - with auto-fix
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _('Bad')
|
||||
`,
|
||||
output: `
|
||||
const {_} = useLingui()
|
||||
const x = _(msg\`Bad\`)
|
||||
`,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Plain string literal (double quotes)
|
||||
// Plain string literal (double quotes) - with auto-fix
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _("Bad")
|
||||
`,
|
||||
output: `
|
||||
const {_} = useLingui()
|
||||
const x = _(msg\`Bad\`)
|
||||
`,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Template literal without msg tag
|
||||
// Template literal without msg tag - with auto-fix
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _(\`Bad\`)
|
||||
`,
|
||||
output: `
|
||||
const {_} = useLingui()
|
||||
const x = _(msg\`Bad\`)
|
||||
`,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Variable/identifier
|
||||
// Template literal with interpolation - with auto-fix
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const name = 'World'
|
||||
const x = _(\`Hello \${name}\`)
|
||||
`,
|
||||
output: `
|
||||
const {_} = useLingui()
|
||||
const name = 'World'
|
||||
const x = _(msg\`Hello \${name}\`)
|
||||
`,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// String with backticks that need escaping
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _('Use \\\`code\\\` here')
|
||||
`,
|
||||
output: `
|
||||
const {_} = useLingui()
|
||||
const x = _(msg\`Use \\\`code\\\` here\`)
|
||||
`,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Variable/identifier - no auto-fix possible
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const message = 'Hello'
|
||||
const x = _(message)
|
||||
`,
|
||||
output: null,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Arbitrary function call
|
||||
// Arbitrary function call - no auto-fix possible
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _(getMessage())
|
||||
`,
|
||||
output: null,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Empty call
|
||||
// Empty call - no auto-fix possible
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _()
|
||||
`,
|
||||
output: null,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Tagged template with wrong tag
|
||||
// Tagged template with wrong tag - no auto-fix (would need to replace tag)
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _(html\`Hello\`)
|
||||
`,
|
||||
output: null,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
// Number literal
|
||||
// Number literal - no auto-fix possible
|
||||
{
|
||||
code: `
|
||||
const {_} = useLingui()
|
||||
const x = _(123)
|
||||
`,
|
||||
output: null,
|
||||
errors: [{messageId: 'missingMsg'}],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,9 +11,10 @@ module.exports = {
|
||||
'Enforce that Lingui _() function is called with msg`` template literal or plural/select macros',
|
||||
recommended: true,
|
||||
},
|
||||
fixable: 'code',
|
||||
messages: {
|
||||
missingMsg:
|
||||
'Lingui _() must be called with msg`...` template literal or plural/select/selectOrdinal. Example: _(msg`Hello`)',
|
||||
'Lingui _() must be called with msg`...` or msg({...}) or plural/select/selectOrdinal. Example: _(msg`Hello`)',
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
@@ -21,11 +22,45 @@ module.exports = {
|
||||
create(context) {
|
||||
// Valid Lingui macro functions that can be passed to _()
|
||||
const VALID_MACRO_FUNCTIONS = new Set([
|
||||
'msg',
|
||||
'plural',
|
||||
'select',
|
||||
'selectOrdinal',
|
||||
])
|
||||
|
||||
/**
|
||||
* Escape backticks and backslashes for template literal
|
||||
*/
|
||||
function escapeForTemplateLiteral(str) {
|
||||
return str.replace(/\\`/g, '`').replace(/`/g, '\\`')
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a fixer for the given argument
|
||||
* Returns null if we can't safely fix it
|
||||
*/
|
||||
function getFixer(firstArg) {
|
||||
const sourceCode = context.sourceCode ?? context.getSourceCode()
|
||||
|
||||
// Fix string literals: _('foo') -> _(msg`foo`)
|
||||
if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') {
|
||||
const escaped = escapeForTemplateLiteral(firstArg.value)
|
||||
return function (fixer) {
|
||||
return fixer.replaceText(firstArg, 'msg`' + escaped + '`')
|
||||
}
|
||||
}
|
||||
|
||||
// Fix untagged template literals: _(`foo`) -> _(msg`foo`)
|
||||
if (firstArg.type === 'TemplateLiteral') {
|
||||
const text = sourceCode.getText(firstArg)
|
||||
return function (fixer) {
|
||||
return fixer.replaceText(firstArg, 'msg' + text)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
CallExpression(node) {
|
||||
// Check if this is a call to _()
|
||||
@@ -53,7 +88,7 @@ module.exports = {
|
||||
return
|
||||
}
|
||||
|
||||
// Valid: _(plural(...)), _(select(...)), _(selectOrdinal(...))
|
||||
// Valid: _(msg(...)), _(plural(...)), _(select(...)), _(selectOrdinal(...))
|
||||
if (
|
||||
firstArg.type === 'CallExpression' &&
|
||||
firstArg.callee.type === 'Identifier' &&
|
||||
@@ -63,9 +98,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
// Everything else is invalid
|
||||
const fix = getFixer(firstArg)
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'missingMsg',
|
||||
fix,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user