forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}