111 lines
2.8 KiB
JavaScript
111 lines
2.8 KiB
JavaScript
'use strict'
|
|
|
|
/**
|
|
* @type {import('eslint').Rule.RuleModule}
|
|
*/
|
|
module.exports = {
|
|
meta: {
|
|
type: 'problem',
|
|
docs: {
|
|
description:
|
|
'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`...` or msg({...}) or plural/select/selectOrdinal. Example: _(msg`Hello`)',
|
|
},
|
|
schema: [],
|
|
},
|
|
|
|
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 _()
|
|
if (node.callee.type !== 'Identifier' || node.callee.name !== '_') {
|
|
return
|
|
}
|
|
|
|
// Must have at least one argument
|
|
if (node.arguments.length === 0) {
|
|
context.report({
|
|
node,
|
|
messageId: 'missingMsg',
|
|
})
|
|
return
|
|
}
|
|
|
|
const firstArg = node.arguments[0]
|
|
|
|
// Valid: _(msg`...`)
|
|
if (
|
|
firstArg.type === 'TaggedTemplateExpression' &&
|
|
firstArg.tag.type === 'Identifier' &&
|
|
firstArg.tag.name === 'msg'
|
|
) {
|
|
return
|
|
}
|
|
|
|
// Valid: _(msg(...)), _(plural(...)), _(select(...)), _(selectOrdinal(...))
|
|
if (
|
|
firstArg.type === 'CallExpression' &&
|
|
firstArg.callee.type === 'Identifier' &&
|
|
VALID_MACRO_FUNCTIONS.has(firstArg.callee.name)
|
|
) {
|
|
return
|
|
}
|
|
|
|
// Everything else is invalid
|
|
const fix = getFixer(firstArg)
|
|
context.report({
|
|
node,
|
|
messageId: 'missingMsg',
|
|
fix,
|
|
})
|
|
},
|
|
}
|
|
},
|
|
}
|