Bluesky app fork with some witchin' additions 馃挮
at main 110 lines 2.9 kB view raw
1'use strict' 2 3/** 4 * @type {import('eslint').Rule.RuleModule} 5 */ 6module.exports = { 7 meta: { 8 type: 'problem', 9 docs: { 10 description: 11 'Enforce that Lingui _() function is called with msg`` template literal or plural/select macros', 12 recommended: true, 13 }, 14 fixable: 'code', 15 messages: { 16 missingMsg: 17 'Lingui _() must be called with msg`...` or msg({...}) or plural/select/selectOrdinal. Example: _(msg`Hello`)', 18 }, 19 schema: [], 20 }, 21 22 create(context) { 23 // Valid Lingui macro functions that can be passed to _() 24 const VALID_MACRO_FUNCTIONS = new Set([ 25 'msg', 26 'plural', 27 'select', 28 'selectOrdinal', 29 ]) 30 31 /** 32 * Escape backticks and backslashes for template literal 33 */ 34 function escapeForTemplateLiteral(str) { 35 return str.replace(/\\`/g, '`').replace(/`/g, '\\`') 36 } 37 38 /** 39 * Try to get a fixer for the given argument 40 * Returns null if we can't safely fix it 41 */ 42 function getFixer(firstArg) { 43 const sourceCode = context.sourceCode ?? context.getSourceCode() 44 45 // Fix string literals: _('foo') -> _(msg`foo`) 46 if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') { 47 const escaped = escapeForTemplateLiteral(firstArg.value) 48 return function (fixer) { 49 return fixer.replaceText(firstArg, 'msg`' + escaped + '`') 50 } 51 } 52 53 // Fix untagged template literals: _(`foo`) -> _(msg`foo`) 54 if (firstArg.type === 'TemplateLiteral') { 55 const text = sourceCode.getText(firstArg) 56 return function (fixer) { 57 return fixer.replaceText(firstArg, 'msg' + text) 58 } 59 } 60 61 return null 62 } 63 64 return { 65 CallExpression(node) { 66 // Check if this is a call to _() 67 if (node.callee.type !== 'Identifier' || node.callee.name !== '_') { 68 return 69 } 70 71 // Must have at least one argument 72 if (node.arguments.length === 0) { 73 context.report({ 74 node, 75 messageId: 'missingMsg', 76 }) 77 return 78 } 79 80 const firstArg = node.arguments[0] 81 82 // Valid: _(msg`...`) 83 if ( 84 firstArg.type === 'TaggedTemplateExpression' && 85 firstArg.tag.type === 'Identifier' && 86 firstArg.tag.name === 'msg' 87 ) { 88 return 89 } 90 91 // Valid: _(msg(...)), _(plural(...)), _(select(...)), _(selectOrdinal(...)) 92 if ( 93 firstArg.type === 'CallExpression' && 94 firstArg.callee.type === 'Identifier' && 95 VALID_MACRO_FUNCTIONS.has(firstArg.callee.name) 96 ) { 97 return 98 } 99 100 // Everything else is invalid 101 const fix = getFixer(firstArg) 102 context.report({ 103 node, 104 messageId: 'missingMsg', 105 fix, 106 }) 107 }, 108 } 109 }, 110}