@nkzw/eslint-plugin-fbtee 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/docs/rules/prefer-fbs.md +36 -0
- package/lib/index.d.mts +5 -2
- package/lib/index.mjs +56 -17
- package/package.json +3 -3
- package/src/__tests__/prefer-fbs-test.tsx +58 -0
- package/src/index.tsx +4 -0
- package/src/rules/prefer-fbs.tsx +94 -0
package/README.md
CHANGED
|
@@ -392,6 +392,10 @@ export default [
|
|
|
392
392
|
|
|
393
393
|
Use `fbtee.configs.strict.rules` to also check for untranslated text.
|
|
394
394
|
|
|
395
|
+
## Migration to fbtee 6
|
|
396
|
+
|
|
397
|
+
`fbt()` now returns strings for text-only messages and real React Fragments for rich messages. Its return type is `string | ReactElement`; use `fbs()` for string-only props, attributes, and storage. The `FbtResult` runtime class and its custom serialization methods are removed. See the [React result migration guide](docs/react-results.md) for compatibility details, tests, and production benchmarks.
|
|
398
|
+
|
|
395
399
|
## Migration from fbtee 4
|
|
396
400
|
|
|
397
401
|
fbtee 5 combines `@nkzw/fbtee-cli` and `@nkzw/oxc-transform-fbtee` into `@nkzw/fbtee-compiler`:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Prefer the string API over coercing React translations (@nkzw/fbtee/prefer-fbs)
|
|
2
|
+
|
|
3
|
+
Use `fbs()` when translated text is required. `fbt()` may return a React element, which `String()` silently converts to `[object Object]`. This rule is enabled in the recommended and strict configurations and does not require type information.
|
|
4
|
+
|
|
5
|
+
## Rule details
|
|
6
|
+
|
|
7
|
+
Examples of incorrect code:
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
String(fbt('Hello', 'Greeting'));
|
|
11
|
+
String(fbt.c('Save'));
|
|
12
|
+
String(<fbt desc="Greeting">Hello</fbt>);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Use the string API directly:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
fbs('Hello', 'Greeting');
|
|
19
|
+
fbs.c('Save');
|
|
20
|
+
fbs(['Hello ', fbs.param('name', name)], 'Greeting');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The rule recognizes the conventional `fbt` binding, aliases imported from `fbtee` or `fbtee/server`, and common-string calls. Parentheses and TypeScript assertions do not hide a direct call. A locally shadowed `String` function is ignored.
|
|
24
|
+
|
|
25
|
+
There is no automatic fix: migrating a call can require adding an `fbs` import, updating nested helpers, and reviewing parameter values. `fbs.param()` accepts strings; do not blindly rename helpers that receive numbers or React nodes.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
This is a direct-expression check. It deliberately does not trace variables, returned values, or translator aliases created through assignments:
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
const message = fbt('Hello', 'Greeting');
|
|
33
|
+
String(message);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It also does not analyze template interpolation, `.toString()`, or JSON serialization. Review those separately during migration. This rule does not change runtime behavior or add runtime overhead.
|
package/lib/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
//#region src/index.d.ts
|
|
2
1
|
declare const _default: {
|
|
3
2
|
configs: {
|
|
4
3
|
recommended: {
|
|
5
4
|
rules: {
|
|
6
5
|
'@nkzw/fbtee/no-empty-strings': number;
|
|
7
6
|
'@nkzw/fbtee/no-unhelpful-desc': number;
|
|
7
|
+
'@nkzw/fbtee/prefer-fbs': number;
|
|
8
8
|
};
|
|
9
9
|
};
|
|
10
10
|
strict: {
|
|
@@ -12,6 +12,7 @@ declare const _default: {
|
|
|
12
12
|
'@nkzw/fbtee/no-empty-strings': number;
|
|
13
13
|
'@nkzw/fbtee/no-unhelpful-desc': number;
|
|
14
14
|
'@nkzw/fbtee/no-untranslated-strings': number;
|
|
15
|
+
'@nkzw/fbtee/prefer-fbs': number;
|
|
15
16
|
};
|
|
16
17
|
};
|
|
17
18
|
};
|
|
@@ -31,7 +32,9 @@ declare const _default: {
|
|
|
31
32
|
}], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
32
33
|
name: string;
|
|
33
34
|
};
|
|
35
|
+
'prefer-fbs': import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferFbs", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
36
|
+
name: string;
|
|
37
|
+
};
|
|
34
38
|
};
|
|
35
39
|
};
|
|
36
|
-
//#endregion
|
|
37
40
|
export { _default as default };
|
package/lib/index.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
-
|
|
1
|
+
import { ASTUtils, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
// package.json
|
|
3
3
|
var name = "@nkzw/eslint-plugin-fbtee";
|
|
4
|
-
var version = "
|
|
5
|
-
|
|
6
|
-
//#region src/utils.tsx
|
|
4
|
+
var version = "6.0.0";
|
|
5
|
+
// src/utils.tsx
|
|
7
6
|
const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/nkzw-tech/fbtee/blob/main/packages/eslint-plugin-fbtee/docs/rules/${name}.md`);
|
|
8
7
|
function resolveMemberExpressions(object, property) {
|
|
9
8
|
if (object.type === "JSXMemberExpression") return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
|
|
@@ -81,8 +80,7 @@ function isFbtNode(node) {
|
|
|
81
80
|
}
|
|
82
81
|
return false;
|
|
83
82
|
}
|
|
84
|
-
|
|
85
|
-
//#region src/rules/no-empty-strings.tsx
|
|
83
|
+
// src/rules/no-empty-strings.tsx
|
|
86
84
|
var no_empty_strings_default = createRule({
|
|
87
85
|
create(context) {
|
|
88
86
|
return {
|
|
@@ -143,8 +141,7 @@ function validateChildren(node) {
|
|
|
143
141
|
function isEmptyString(node) {
|
|
144
142
|
return resolveNodeValue(node)?.trim() === "";
|
|
145
143
|
}
|
|
146
|
-
|
|
147
|
-
//#region src/rules/no-unhelpful-desc.tsx
|
|
144
|
+
// src/rules/no-unhelpful-desc.tsx
|
|
148
145
|
var no_unhelpful_desc_default = createRule({
|
|
149
146
|
create(context) {
|
|
150
147
|
return {
|
|
@@ -213,8 +210,7 @@ var no_unhelpful_desc_default = createRule({
|
|
|
213
210
|
},
|
|
214
211
|
name: "no-unhelpful-desc"
|
|
215
212
|
});
|
|
216
|
-
|
|
217
|
-
//#region src/rules/no-untranslated-strings.tsx
|
|
213
|
+
// src/rules/no-untranslated-strings.tsx
|
|
218
214
|
const attributes = /* @__PURE__ */ new Set([
|
|
219
215
|
"title",
|
|
220
216
|
"placeholder",
|
|
@@ -292,18 +288,61 @@ var no_untranslated_strings_default = createRule({
|
|
|
292
288
|
},
|
|
293
289
|
name: "no-untranslated-strings"
|
|
294
290
|
});
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
// src/rules/prefer-fbs.tsx
|
|
292
|
+
function unwrap(node) {
|
|
293
|
+
while (node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression" || node.type === "TSSatisfiesExpression" || node.type === "ChainExpression") node = node.expression;
|
|
294
|
+
return node;
|
|
295
|
+
}
|
|
296
|
+
var prefer_fbs_default = createRule({
|
|
297
|
+
create(context) {
|
|
298
|
+
function isFbt(node) {
|
|
299
|
+
if (node.type !== "Identifier") return false;
|
|
300
|
+
const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
|
|
301
|
+
const imported = variable?.defs.find((definition) => definition.type === "ImportBinding");
|
|
302
|
+
if (imported) return imported.parent.type === "ImportDeclaration" && (imported.parent.source.value === "fbtee" || imported.parent.source.value === "fbtee/server") && (imported.node.type === "ImportDefaultSpecifier" || imported.node.type === "ImportSpecifier" && (imported.node.imported.type === "Identifier" ? imported.node.imported.name : imported.node.imported.value) === "fbt");
|
|
303
|
+
return node.name === "fbt" && !variable?.defs.some((definition) => definition.type === "FunctionName");
|
|
304
|
+
}
|
|
305
|
+
return { CallExpression(node) {
|
|
306
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "String") return;
|
|
307
|
+
if (ASTUtils.findVariable(context.sourceCode.getScope(node), "String")?.defs.length || !node.arguments[0]) return;
|
|
308
|
+
const argument = unwrap(node.arguments[0]);
|
|
309
|
+
if (argument.type === "JSXElement" && argument.openingElement.name.type === "JSXIdentifier" && argument.openingElement.name.name === "fbt") {
|
|
310
|
+
context.report({
|
|
311
|
+
messageId: "preferFbs",
|
|
312
|
+
node
|
|
313
|
+
});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (argument.type !== "CallExpression") return;
|
|
317
|
+
const callee = unwrap(argument.callee);
|
|
318
|
+
if (isFbt(callee) || callee.type === "MemberExpression" && isFbt(unwrap(callee.object)) && (!callee.computed && callee.property.type === "Identifier" && callee.property.name === "c" || callee.computed && callee.property.type === "Literal" && callee.property.value === "c")) context.report({
|
|
319
|
+
messageId: "preferFbs",
|
|
320
|
+
node
|
|
321
|
+
});
|
|
322
|
+
} };
|
|
323
|
+
},
|
|
324
|
+
defaultOptions: [],
|
|
325
|
+
meta: {
|
|
326
|
+
docs: { description: "Require fbs() instead of String(fbt(...)) for translated text." },
|
|
327
|
+
messages: { preferFbs: "Use fbs() or fbs.c() instead of String() on an fbt result. Rich fbt results stringify to \"[object Object]\"." },
|
|
328
|
+
schema: [],
|
|
329
|
+
type: "problem"
|
|
330
|
+
},
|
|
331
|
+
name: "prefer-fbs"
|
|
332
|
+
});
|
|
333
|
+
// src/index.tsx
|
|
297
334
|
var src_default = {
|
|
298
335
|
configs: {
|
|
299
336
|
recommended: { rules: {
|
|
300
337
|
"@nkzw/fbtee/no-empty-strings": 2,
|
|
301
|
-
"@nkzw/fbtee/no-unhelpful-desc": 2
|
|
338
|
+
"@nkzw/fbtee/no-unhelpful-desc": 2,
|
|
339
|
+
"@nkzw/fbtee/prefer-fbs": 2
|
|
302
340
|
} },
|
|
303
341
|
strict: { rules: {
|
|
304
342
|
"@nkzw/fbtee/no-empty-strings": 2,
|
|
305
343
|
"@nkzw/fbtee/no-unhelpful-desc": 2,
|
|
306
|
-
"@nkzw/fbtee/no-untranslated-strings": 2
|
|
344
|
+
"@nkzw/fbtee/no-untranslated-strings": 2,
|
|
345
|
+
"@nkzw/fbtee/prefer-fbs": 2
|
|
307
346
|
} }
|
|
308
347
|
},
|
|
309
348
|
meta: {
|
|
@@ -313,8 +352,8 @@ var src_default = {
|
|
|
313
352
|
rules: {
|
|
314
353
|
"no-empty-strings": no_empty_strings_default,
|
|
315
354
|
"no-unhelpful-desc": no_unhelpful_desc_default,
|
|
316
|
-
"no-untranslated-strings": no_untranslated_strings_default
|
|
355
|
+
"no-untranslated-strings": no_untranslated_strings_default,
|
|
356
|
+
"prefer-fbs": prefer_fbs_default
|
|
317
357
|
}
|
|
318
358
|
};
|
|
319
|
-
//#endregion
|
|
320
359
|
export { src_default as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkzw/eslint-plugin-fbtee",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"import": "./lib/index.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@typescript-eslint/utils": "^8.
|
|
18
|
+
"@typescript-eslint/utils": "^8.72.2"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@typescript-eslint/rule-tester": "^8.
|
|
21
|
+
"@typescript-eslint/rule-tester": "^8.72.2"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsdown src/index.tsx -d lib --target=node22 --format=esm --clean --dts"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RuleTester } from '@typescript-eslint/rule-tester';
|
|
2
|
+
import plugin from '../index.tsx';
|
|
3
|
+
import rule from '../rules/prefer-fbs.tsx';
|
|
4
|
+
|
|
5
|
+
const ruleTester = new RuleTester({
|
|
6
|
+
languageOptions: {
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaFeatures: { jsx: true },
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
ruleTester.run('prefer-fbs', rule, {
|
|
14
|
+
invalid: [
|
|
15
|
+
"String(fbt('Hello', 'Greeting'));",
|
|
16
|
+
"String(fbt.c('Save'));",
|
|
17
|
+
"String(fbt['c']('Save'));",
|
|
18
|
+
"String((fbt('Hello', 'Greeting')));",
|
|
19
|
+
"String(fbt('Hello', 'Greeting') as unknown);",
|
|
20
|
+
"String(fbt('Hello', 'Greeting')!);",
|
|
21
|
+
"String(fbt('Hello', 'Greeting') satisfies unknown);",
|
|
22
|
+
"String(fbt?.('Hello', 'Greeting'));",
|
|
23
|
+
"String(fbt(['Hello ', fbt.param('name', <b>Alice</b>)], 'Greeting'));",
|
|
24
|
+
'String(<fbt desc="Greeting">Hello</fbt>);',
|
|
25
|
+
"import { fbt } from 'fbtee'; String(fbt('Hello', 'Greeting'));",
|
|
26
|
+
"import { fbt as translate } from 'fbtee'; String(translate('Hello', 'Greeting'));",
|
|
27
|
+
"import { fbt as translate } from 'fbtee/server'; String(translate.c('Save'));",
|
|
28
|
+
"import translate from 'fbtee'; String(translate('Hello', 'Greeting'));",
|
|
29
|
+
"const { fbt } = useFbt(); String(fbt('Hello', 'Greeting'));",
|
|
30
|
+
"function message(fbt) { return String(fbt('Hello', 'Greeting')); }",
|
|
31
|
+
].map((code) => ({ code, errors: [{ messageId: 'preferFbs' as const }], output: null })),
|
|
32
|
+
valid: [
|
|
33
|
+
"fbs('Hello', 'Greeting');",
|
|
34
|
+
"fbs.c('Save');",
|
|
35
|
+
"String(fbs('Hello', 'Greeting'));",
|
|
36
|
+
"String(fbs.c('Save'));",
|
|
37
|
+
'String(<fbs desc="Greeting">Hello</fbs>);',
|
|
38
|
+
"String(fbt.param('name', 'Alice'));",
|
|
39
|
+
"String(fbt._('Hello'));",
|
|
40
|
+
"String(other('Hello'));",
|
|
41
|
+
"import { fbs as fbt } from 'fbtee'; String(fbt('Hello', 'Greeting'));",
|
|
42
|
+
"import { fbt } from 'other'; String(fbt('Hello', 'Greeting'));",
|
|
43
|
+
"import fbt = require('other'); String(fbt('Hello'));",
|
|
44
|
+
"function fbt(text) { return text; } String(fbt('Hello'));",
|
|
45
|
+
"function example(String) { return String(fbt('Hello', 'Greeting')); }",
|
|
46
|
+
"const String = value => value; String(fbt('Hello', 'Greeting'));",
|
|
47
|
+
"import { fbt as translate } from 'fbtee'; function example(translate) { return String(translate('Hello')); }",
|
|
48
|
+
"const result = fbt('Hello', 'Greeting'); String(result);",
|
|
49
|
+
"const translate = fbt; String(translate('Hello', 'Greeting'));",
|
|
50
|
+
'String();',
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('prefer-fbs is enabled in the recommended and strict configs', () => {
|
|
55
|
+
expect(plugin.rules['prefer-fbs']).toBe(rule);
|
|
56
|
+
expect(plugin.configs.recommended.rules['@nkzw/fbtee/prefer-fbs']).toBe(2);
|
|
57
|
+
expect(plugin.configs.strict.rules['@nkzw/fbtee/prefer-fbs']).toBe(2);
|
|
58
|
+
});
|
package/src/index.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import packageJson from '../package.json' with { type: 'json' };
|
|
|
2
2
|
import noEmptyStringsRule from './rules/no-empty-strings.tsx';
|
|
3
3
|
import noUnhelpfulDesc from './rules/no-unhelpful-desc.tsx';
|
|
4
4
|
import noUntranslatedStrings from './rules/no-untranslated-strings.tsx';
|
|
5
|
+
import preferFbs from './rules/prefer-fbs.tsx';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
configs: {
|
|
@@ -9,6 +10,7 @@ export default {
|
|
|
9
10
|
rules: {
|
|
10
11
|
'@nkzw/fbtee/no-empty-strings': 2,
|
|
11
12
|
'@nkzw/fbtee/no-unhelpful-desc': 2,
|
|
13
|
+
'@nkzw/fbtee/prefer-fbs': 2,
|
|
12
14
|
},
|
|
13
15
|
},
|
|
14
16
|
strict: {
|
|
@@ -16,6 +18,7 @@ export default {
|
|
|
16
18
|
'@nkzw/fbtee/no-empty-strings': 2,
|
|
17
19
|
'@nkzw/fbtee/no-unhelpful-desc': 2,
|
|
18
20
|
'@nkzw/fbtee/no-untranslated-strings': 2,
|
|
21
|
+
'@nkzw/fbtee/prefer-fbs': 2,
|
|
19
22
|
},
|
|
20
23
|
},
|
|
21
24
|
},
|
|
@@ -27,5 +30,6 @@ export default {
|
|
|
27
30
|
'no-empty-strings': noEmptyStringsRule,
|
|
28
31
|
'no-unhelpful-desc': noUnhelpfulDesc,
|
|
29
32
|
'no-untranslated-strings': noUntranslatedStrings,
|
|
33
|
+
'prefer-fbs': preferFbs,
|
|
30
34
|
},
|
|
31
35
|
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ASTUtils, TSESTree } from '@typescript-eslint/utils';
|
|
2
|
+
import { createRule } from '../utils.tsx';
|
|
3
|
+
|
|
4
|
+
function unwrap(node: TSESTree.Node): TSESTree.Node {
|
|
5
|
+
while (
|
|
6
|
+
node.type === 'TSAsExpression' ||
|
|
7
|
+
node.type === 'TSTypeAssertion' ||
|
|
8
|
+
node.type === 'TSNonNullExpression' ||
|
|
9
|
+
node.type === 'TSSatisfiesExpression' ||
|
|
10
|
+
node.type === 'ChainExpression'
|
|
11
|
+
) {
|
|
12
|
+
node = node.expression;
|
|
13
|
+
}
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default createRule<[], 'preferFbs'>({
|
|
18
|
+
create(context) {
|
|
19
|
+
function isFbt(node: TSESTree.Node): boolean {
|
|
20
|
+
if (node.type !== 'Identifier') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const variable = ASTUtils.findVariable(context.sourceCode.getScope(node), node.name);
|
|
24
|
+
const imported = variable?.defs.find((definition) => definition.type === 'ImportBinding');
|
|
25
|
+
if (imported) {
|
|
26
|
+
return (
|
|
27
|
+
imported.parent.type === 'ImportDeclaration' &&
|
|
28
|
+
(imported.parent.source.value === 'fbtee' ||
|
|
29
|
+
imported.parent.source.value === 'fbtee/server') &&
|
|
30
|
+
(imported.node.type === 'ImportDefaultSpecifier' ||
|
|
31
|
+
(imported.node.type === 'ImportSpecifier' &&
|
|
32
|
+
(imported.node.imported.type === 'Identifier'
|
|
33
|
+
? imported.node.imported.name
|
|
34
|
+
: imported.node.imported.value) === 'fbt'))
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return (
|
|
38
|
+
node.name === 'fbt' &&
|
|
39
|
+
!variable?.defs.some((definition) => definition.type === 'FunctionName')
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
CallExpression(node) {
|
|
45
|
+
if (node.callee.type !== 'Identifier' || node.callee.name !== 'String') {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const string = ASTUtils.findVariable(context.sourceCode.getScope(node), 'String');
|
|
49
|
+
if (string?.defs.length || !node.arguments[0]) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const argument = unwrap(node.arguments[0]);
|
|
53
|
+
if (
|
|
54
|
+
argument.type === 'JSXElement' &&
|
|
55
|
+
argument.openingElement.name.type === 'JSXIdentifier' &&
|
|
56
|
+
argument.openingElement.name.name === 'fbt'
|
|
57
|
+
) {
|
|
58
|
+
context.report({ messageId: 'preferFbs', node });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (argument.type !== 'CallExpression') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const callee = unwrap(argument.callee);
|
|
65
|
+
if (
|
|
66
|
+
isFbt(callee) ||
|
|
67
|
+
(callee.type === 'MemberExpression' &&
|
|
68
|
+
isFbt(unwrap(callee.object)) &&
|
|
69
|
+
((!callee.computed &&
|
|
70
|
+
callee.property.type === 'Identifier' &&
|
|
71
|
+
callee.property.name === 'c') ||
|
|
72
|
+
(callee.computed &&
|
|
73
|
+
callee.property.type === 'Literal' &&
|
|
74
|
+
callee.property.value === 'c')))
|
|
75
|
+
) {
|
|
76
|
+
context.report({ messageId: 'preferFbs', node });
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
defaultOptions: [],
|
|
82
|
+
meta: {
|
|
83
|
+
docs: {
|
|
84
|
+
description: 'Require fbs() instead of String(fbt(...)) for translated text.',
|
|
85
|
+
},
|
|
86
|
+
messages: {
|
|
87
|
+
preferFbs:
|
|
88
|
+
'Use fbs() or fbs.c() instead of String() on an fbt result. Rich fbt results stringify to "[object Object]".',
|
|
89
|
+
},
|
|
90
|
+
schema: [],
|
|
91
|
+
type: 'problem',
|
|
92
|
+
},
|
|
93
|
+
name: 'prefer-fbs',
|
|
94
|
+
});
|