@nkzw/eslint-plugin-fbtee 0.2.2 → 0.3.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 +5 -5
- package/lib/index.cjs +335 -427
- package/lib/index.d.cts +28 -27
- package/lib/index.d.ts +28 -27
- package/lib/index.js +320 -412
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -1,447 +1,355 @@
|
|
|
1
|
-
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
|
|
3
|
+
//#region package.json
|
|
4
|
+
var name = "@nkzw/eslint-plugin-fbtee";
|
|
5
|
+
var version = "0.3.0";
|
|
6
|
+
var description = "";
|
|
7
|
+
var keywords = [];
|
|
8
|
+
var repository = {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/nkzw-tech/fbtee.git",
|
|
11
|
+
"directory": "packages/eslint-plugin-fbtee"
|
|
12
|
+
};
|
|
13
|
+
var license = "MIT";
|
|
14
|
+
var type = "module";
|
|
15
|
+
var exports = {
|
|
16
|
+
"import": "./lib/index.js",
|
|
17
|
+
"require": "./lib/index.cjs"
|
|
18
|
+
};
|
|
19
|
+
var main = "lib/index.cjs";
|
|
20
|
+
var scripts = { "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts" };
|
|
21
|
+
var dependencies = { "@typescript-eslint/utils": "^8.34.1" };
|
|
22
|
+
var devDependencies = { "@typescript-eslint/rule-tester": "^8.34.1" };
|
|
2
23
|
var package_default = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import: "./lib/index.js",
|
|
16
|
-
require: "./lib/index.cjs"
|
|
17
|
-
},
|
|
18
|
-
main: "lib/index.cjs",
|
|
19
|
-
scripts: {
|
|
20
|
-
build: "tsup src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --no-splitting --dts"
|
|
21
|
-
},
|
|
22
|
-
dependencies: {
|
|
23
|
-
"@typescript-eslint/utils": "^8.29.0"
|
|
24
|
-
},
|
|
25
|
-
devDependencies: {
|
|
26
|
-
"@typescript-eslint/rule-tester": "^8.29.0"
|
|
27
|
-
}
|
|
24
|
+
name,
|
|
25
|
+
version,
|
|
26
|
+
description,
|
|
27
|
+
keywords,
|
|
28
|
+
repository,
|
|
29
|
+
license,
|
|
30
|
+
type,
|
|
31
|
+
exports,
|
|
32
|
+
main,
|
|
33
|
+
scripts,
|
|
34
|
+
dependencies,
|
|
35
|
+
devDependencies
|
|
28
36
|
};
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(name) => `https://github.com/nkzw-tech/fbtee/blob/main/packages/eslint-plugin-fbtee/docs/rules/${name}.md`
|
|
34
|
-
);
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils.tsx
|
|
40
|
+
const createRule = ESLintUtils.RuleCreator((name$1) => `https://github.com/nkzw-tech/fbtee/blob/main/packages/eslint-plugin-fbtee/docs/rules/${name$1}.md`);
|
|
35
41
|
function resolveMemberExpressions(object, property) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
return `${object.name}.${property.name}`;
|
|
42
|
+
if (object.type === "JSXMemberExpression") return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
|
|
43
|
+
return `${object.name}.${property.name}`;
|
|
40
44
|
}
|
|
41
45
|
function elementType(node) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
case "JSXNamespacedName": {
|
|
55
|
-
return `${name.namespace.name}:${name.name.name}`;
|
|
56
|
-
}
|
|
57
|
-
default: {
|
|
58
|
-
throw new Error("Unsupported JSX node type.");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
46
|
+
if (node.type === "JSXFragment") return "<>";
|
|
47
|
+
const { name: name$1 } = node.openingElement;
|
|
48
|
+
switch (name$1.type) {
|
|
49
|
+
case "JSXIdentifier": return name$1.name;
|
|
50
|
+
case "JSXMemberExpression": {
|
|
51
|
+
const { object, property } = name$1;
|
|
52
|
+
return resolveMemberExpressions(object, property);
|
|
53
|
+
}
|
|
54
|
+
case "JSXNamespacedName": return `${name$1.namespace.name}:${name$1.name.name}`;
|
|
55
|
+
default: throw new Error("Unsupported JSX node type.");
|
|
56
|
+
}
|
|
61
57
|
}
|
|
62
58
|
function resolveJsxElementTextContent(node) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return resolveNodeValue(child.expression) || "";
|
|
69
|
-
}
|
|
70
|
-
return "";
|
|
71
|
-
}).join("").trim();
|
|
59
|
+
return node.children.map((child) => {
|
|
60
|
+
if (child.type === "JSXText") return child.value.trim();
|
|
61
|
+
if (child.type === "JSXExpressionContainer") return resolveNodeValue(child.expression) || "";
|
|
62
|
+
return "";
|
|
63
|
+
}).join("").trim();
|
|
72
64
|
}
|
|
73
65
|
function resolveBinaryExpression(node) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (leftValue !== null && rightValue !== null) {
|
|
80
|
-
return leftValue + rightValue;
|
|
81
|
-
}
|
|
82
|
-
return null;
|
|
66
|
+
if (node.operator !== "+") return null;
|
|
67
|
+
const leftValue = resolveNodeValue(node.left);
|
|
68
|
+
const rightValue = resolveNodeValue(node.right);
|
|
69
|
+
if (leftValue !== null && rightValue !== null) return leftValue + rightValue;
|
|
70
|
+
return null;
|
|
83
71
|
}
|
|
84
72
|
function resolveNodeValue(node) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return consequent || alternate || null;
|
|
101
|
-
}
|
|
102
|
-
if (node.type === "LogicalExpression") {
|
|
103
|
-
const left = resolveNodeValue(node.left);
|
|
104
|
-
const right = resolveNodeValue(node.right);
|
|
105
|
-
return left || right || null;
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
73
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
74
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) return node.quasis.map((quasi) => quasi.value.raw).join("");
|
|
75
|
+
if (node.type === "BinaryExpression") return resolveBinaryExpression(node);
|
|
76
|
+
if (node.type === "JSXExpressionContainer") return resolveNodeValue(node.expression);
|
|
77
|
+
if (node.type === "ConditionalExpression") {
|
|
78
|
+
const consequent = resolveNodeValue(node.consequent);
|
|
79
|
+
const alternate = resolveNodeValue(node.alternate);
|
|
80
|
+
return consequent || alternate || null;
|
|
81
|
+
}
|
|
82
|
+
if (node.type === "LogicalExpression") {
|
|
83
|
+
const left = resolveNodeValue(node.left);
|
|
84
|
+
const right = resolveNodeValue(node.right);
|
|
85
|
+
return left || right || null;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
108
88
|
}
|
|
109
89
|
function getPropName(prop) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
return prop.name.name;
|
|
90
|
+
if (prop.name.type === "JSXNamespacedName") return `${prop.name.namespace.name}:${prop.name.name.name}`;
|
|
91
|
+
return prop.name.name;
|
|
114
92
|
}
|
|
115
93
|
function hasFbtParent(node) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
return false;
|
|
94
|
+
let current = node.parent;
|
|
95
|
+
while (current) {
|
|
96
|
+
if (isFbtNode(current)) return true;
|
|
97
|
+
current = current.parent;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
124
100
|
}
|
|
125
101
|
function isFbtNode(node) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
return false;
|
|
102
|
+
if (!node) return null;
|
|
103
|
+
if (node.type === "JSXElement") {
|
|
104
|
+
const name$1 = elementType(node);
|
|
105
|
+
return name$1 === "fbt" || name$1 === "fbs" || name$1?.startsWith("fbt:");
|
|
106
|
+
}
|
|
107
|
+
if (node.type === "CallExpression" && node.callee.type === "Identifier") {
|
|
108
|
+
const name$1 = node.callee.name;
|
|
109
|
+
return name$1 === "fbt" || name$1 === "fbs";
|
|
110
|
+
}
|
|
111
|
+
if (node.type === "MemberExpression" && node.object.type === "CallExpression" && node.object.callee.type === "Identifier") {
|
|
112
|
+
const name$1 = node.object.callee.name;
|
|
113
|
+
return name$1 === "fbt" || name$1 === "fbs";
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
142
116
|
}
|
|
143
117
|
|
|
144
|
-
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/rules/no-empty-strings.tsx
|
|
145
120
|
var no_empty_strings_default = createRule({
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
meta: {
|
|
179
|
-
docs: {
|
|
180
|
-
description: "Disallow empty strings in fbt elements or function calls."
|
|
181
|
-
},
|
|
182
|
-
messages: {
|
|
183
|
-
emptyString: "Empty strings are not allowed in fbt() and fbs() function arguments.",
|
|
184
|
-
jsxEmptyString: "Empty strings are not allowed as children of `<fbt>` and `<fbs>` tags."
|
|
185
|
-
},
|
|
186
|
-
schema: [],
|
|
187
|
-
type: "problem"
|
|
188
|
-
},
|
|
189
|
-
name: "no-empty-strings"
|
|
121
|
+
create(context) {
|
|
122
|
+
return {
|
|
123
|
+
CallExpression(node) {
|
|
124
|
+
if (!isFbtNode(node)) return;
|
|
125
|
+
const [text] = node.arguments;
|
|
126
|
+
if (text && text.type !== "SpreadElement" && isEmptyString(text)) context.report({
|
|
127
|
+
messageId: "emptyString",
|
|
128
|
+
node: text
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
JSXElement(node) {
|
|
132
|
+
if (!isFbtNode(node)) return;
|
|
133
|
+
if (node.openingElement.name.type === "JSXNamespacedName") return;
|
|
134
|
+
const hasTextContent = validateChildren(node);
|
|
135
|
+
if (!hasTextContent) context.report({
|
|
136
|
+
messageId: "jsxEmptyString",
|
|
137
|
+
node
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
defaultOptions: [],
|
|
143
|
+
meta: {
|
|
144
|
+
docs: { description: "Disallow empty strings in fbt elements or function calls." },
|
|
145
|
+
messages: {
|
|
146
|
+
emptyString: "Empty strings are not allowed in fbt() and fbs() function arguments.",
|
|
147
|
+
jsxEmptyString: "Empty strings are not allowed as children of `<fbt>` and `<fbs>` tags."
|
|
148
|
+
},
|
|
149
|
+
schema: [],
|
|
150
|
+
type: "problem"
|
|
151
|
+
},
|
|
152
|
+
name: "no-empty-strings"
|
|
190
153
|
});
|
|
191
154
|
function validateChildren(node) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
hasTextContent ||= validateChildren(child);
|
|
217
|
-
}
|
|
218
|
-
if (hasTextContent) {
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return hasTextContent;
|
|
155
|
+
let hasTextContent = false;
|
|
156
|
+
for (const child of node.children) {
|
|
157
|
+
if (child.type === "JSXText" && child.value.trim() !== "") hasTextContent = true;
|
|
158
|
+
if (child.type === "JSXExpressionContainer") {
|
|
159
|
+
if (child.expression.type === "JSXEmptyExpression") continue;
|
|
160
|
+
if (child.expression.type === "Identifier" && child.expression.name !== "undefined") {
|
|
161
|
+
hasTextContent = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const value = resolveNodeValue(child.expression)?.trim();
|
|
165
|
+
if (value) hasTextContent = true;
|
|
166
|
+
}
|
|
167
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment") {
|
|
168
|
+
const childName = elementType(child);
|
|
169
|
+
if (/^[A-Z]/.test(childName) || childName.startsWith("fbt:")) {
|
|
170
|
+
hasTextContent = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
hasTextContent ||= validateChildren(child);
|
|
174
|
+
}
|
|
175
|
+
if (hasTextContent) break;
|
|
176
|
+
}
|
|
177
|
+
return hasTextContent;
|
|
223
178
|
}
|
|
224
179
|
function isEmptyString(node) {
|
|
225
|
-
|
|
180
|
+
return resolveNodeValue(node)?.trim() === "";
|
|
226
181
|
}
|
|
227
182
|
|
|
228
|
-
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/rules/no-unhelpful-desc.tsx
|
|
229
185
|
var no_unhelpful_desc_default = createRule({
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
meta: {
|
|
297
|
-
docs: {
|
|
298
|
-
description: "Enforce meaningful and non-empty descriptions in <fbt> elements and fbt()/fbs() function calls."
|
|
299
|
-
},
|
|
300
|
-
messages: {
|
|
301
|
-
duplicateDesc: 'The "desc" attribute or argument should not be a duplicate of the text content.',
|
|
302
|
-
emptyDesc: 'The "desc" argument in fbt() and fbs() must not be empty.',
|
|
303
|
-
jsxEmptyDesc: 'The "desc" attribute in <fbt> must not be empty.',
|
|
304
|
-
shortDesc: 'The "desc" attribute or argument is too short. Use a more descriptive and meaningful explanation.'
|
|
305
|
-
},
|
|
306
|
-
schema: [],
|
|
307
|
-
type: "problem"
|
|
308
|
-
},
|
|
309
|
-
name: "no-unhelpful-desc"
|
|
186
|
+
create(context) {
|
|
187
|
+
return {
|
|
188
|
+
CallExpression(node) {
|
|
189
|
+
if (!isFbtNode(node)) return;
|
|
190
|
+
const [textArg, descArg] = node.arguments;
|
|
191
|
+
const desc = descArg && descArg.type !== "SpreadElement" ? resolveNodeValue(descArg)?.trim() : null;
|
|
192
|
+
if (!desc) {
|
|
193
|
+
context.report({
|
|
194
|
+
messageId: "emptyDesc",
|
|
195
|
+
node: descArg || node
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (desc.length <= 4) {
|
|
200
|
+
context.report({
|
|
201
|
+
messageId: "shortDesc",
|
|
202
|
+
node: descArg || node
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const text = textArg && textArg.type !== "SpreadElement" ? resolveNodeValue(textArg)?.trim() : null;
|
|
207
|
+
if (desc.toLowerCase() === text?.toLowerCase()) context.report({
|
|
208
|
+
messageId: "duplicateDesc",
|
|
209
|
+
node: descArg || node
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
JSXAttribute(node) {
|
|
213
|
+
if (!isFbtNode(node.parent.parent)) return;
|
|
214
|
+
if (node.name.name !== "desc" || !node.value) return;
|
|
215
|
+
if (node.value.type === "JSXExpressionContainer" && (node.value.expression.type === "Identifier" || node.value.expression.type === "CallExpression")) return;
|
|
216
|
+
const desc = resolveNodeValue(node.value)?.trim();
|
|
217
|
+
if (!desc) {
|
|
218
|
+
context.report({
|
|
219
|
+
messageId: "jsxEmptyDesc",
|
|
220
|
+
node: node.value
|
|
221
|
+
});
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (desc.length <= 4) {
|
|
225
|
+
context.report({
|
|
226
|
+
messageId: "shortDesc",
|
|
227
|
+
node: node.value
|
|
228
|
+
});
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const textContent = resolveJsxElementTextContent(node.parent.parent);
|
|
232
|
+
if (desc.toLowerCase() === textContent.toLowerCase()) context.report({
|
|
233
|
+
messageId: "duplicateDesc",
|
|
234
|
+
node: node.value
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
},
|
|
239
|
+
defaultOptions: [],
|
|
240
|
+
meta: {
|
|
241
|
+
docs: { description: "Enforce meaningful and non-empty descriptions in <fbt> elements and fbt()/fbs() function calls." },
|
|
242
|
+
messages: {
|
|
243
|
+
duplicateDesc: "The \"desc\" attribute or argument should not be a duplicate of the text content.",
|
|
244
|
+
emptyDesc: "The \"desc\" argument in fbt() and fbs() must not be empty.",
|
|
245
|
+
jsxEmptyDesc: "The \"desc\" attribute in <fbt> must not be empty.",
|
|
246
|
+
shortDesc: "The \"desc\" attribute or argument is too short. Use a more descriptive and meaningful explanation."
|
|
247
|
+
},
|
|
248
|
+
schema: [],
|
|
249
|
+
type: "problem"
|
|
250
|
+
},
|
|
251
|
+
name: "no-unhelpful-desc"
|
|
310
252
|
});
|
|
311
253
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region src/rules/no-untranslated-strings.tsx
|
|
256
|
+
const attributes = new Set([
|
|
257
|
+
"title",
|
|
258
|
+
"placeholder",
|
|
259
|
+
"alt",
|
|
260
|
+
"label",
|
|
261
|
+
"aria-label",
|
|
262
|
+
"aria-errormessage"
|
|
320
263
|
]);
|
|
321
264
|
var no_untranslated_strings_default = createRule({
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
messageId: "unwrappedString"
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
},
|
|
390
|
-
defaultOptions: [{ ignoredWords: [] }],
|
|
391
|
-
meta: {
|
|
392
|
-
docs: {
|
|
393
|
-
description: "Enforce text content to be wrapped with `<fbt>`, `fbt()` or `fbs()`"
|
|
394
|
-
},
|
|
395
|
-
messages: {
|
|
396
|
-
unwrappedString: "Text content must be wrapped in `<fbt>`, `fbt()` or `fbs()`"
|
|
397
|
-
},
|
|
398
|
-
schema: [
|
|
399
|
-
{
|
|
400
|
-
additionalProperties: false,
|
|
401
|
-
properties: {
|
|
402
|
-
ignoredWords: {
|
|
403
|
-
items: {
|
|
404
|
-
type: "string"
|
|
405
|
-
},
|
|
406
|
-
type: "array",
|
|
407
|
-
uniqueItems: true
|
|
408
|
-
}
|
|
409
|
-
},
|
|
410
|
-
type: "object"
|
|
411
|
-
}
|
|
412
|
-
],
|
|
413
|
-
type: "problem"
|
|
414
|
-
},
|
|
415
|
-
name: "no-untranslated-strings"
|
|
265
|
+
create(context, options) {
|
|
266
|
+
const ignoredWords = new Set(options[0].ignoredWords.map((s) => s.trim().toLowerCase()));
|
|
267
|
+
function isIgnoredWord(value) {
|
|
268
|
+
return ignoredWords.has(value.trim().toLowerCase());
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
JSXAttribute(node) {
|
|
272
|
+
const propName = getPropName(node);
|
|
273
|
+
if (!attributes.has(propName)) return;
|
|
274
|
+
const value = node.value ? resolveNodeValue(node.value) : null;
|
|
275
|
+
if (!value) return;
|
|
276
|
+
if (isIgnoredWord(value)) return;
|
|
277
|
+
if (node.value?.type === "JSXExpressionContainer" && isFbtNode(node.value.expression)) return;
|
|
278
|
+
context.report({
|
|
279
|
+
messageId: "unwrappedString",
|
|
280
|
+
node
|
|
281
|
+
});
|
|
282
|
+
},
|
|
283
|
+
JSXExpressionContainer(node) {
|
|
284
|
+
if (node.parent.type === "JSXAttribute") return;
|
|
285
|
+
const value = resolveNodeValue(node.expression);
|
|
286
|
+
if (!value) return;
|
|
287
|
+
if (isIgnoredWord(value)) return;
|
|
288
|
+
if (!hasFbtParent(node)) context.report({
|
|
289
|
+
messageId: "unwrappedString",
|
|
290
|
+
node
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
JSXText(node) {
|
|
294
|
+
const value = node.value.trim();
|
|
295
|
+
if (!value) return;
|
|
296
|
+
if (isIgnoredWord(value)) return;
|
|
297
|
+
if (!hasFbtParent(node)) {
|
|
298
|
+
const startOffset = node.range[0] + node.value.indexOf(value);
|
|
299
|
+
const endOffset = startOffset + value.length;
|
|
300
|
+
context.report({
|
|
301
|
+
loc: {
|
|
302
|
+
end: context.sourceCode.getLocFromIndex(endOffset),
|
|
303
|
+
start: context.sourceCode.getLocFromIndex(startOffset)
|
|
304
|
+
},
|
|
305
|
+
messageId: "unwrappedString"
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
defaultOptions: [{ ignoredWords: [] }],
|
|
312
|
+
meta: {
|
|
313
|
+
docs: { description: "Enforce text content to be wrapped with `<fbt>`, `fbt()` or `fbs()`" },
|
|
314
|
+
messages: { unwrappedString: "Text content must be wrapped in `<fbt>`, `fbt()` or `fbs()`" },
|
|
315
|
+
schema: [{
|
|
316
|
+
additionalProperties: false,
|
|
317
|
+
properties: { ignoredWords: {
|
|
318
|
+
items: { type: "string" },
|
|
319
|
+
type: "array",
|
|
320
|
+
uniqueItems: true
|
|
321
|
+
} },
|
|
322
|
+
type: "object"
|
|
323
|
+
}],
|
|
324
|
+
type: "problem"
|
|
325
|
+
},
|
|
326
|
+
name: "no-untranslated-strings"
|
|
416
327
|
});
|
|
417
328
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
"no-unhelpful-desc": no_unhelpful_desc_default,
|
|
442
|
-
"no-untranslated-strings": no_untranslated_strings_default
|
|
443
|
-
}
|
|
444
|
-
};
|
|
445
|
-
export {
|
|
446
|
-
index_default as default
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/index.tsx
|
|
331
|
+
var src_default = {
|
|
332
|
+
configs: {
|
|
333
|
+
recommended: { rules: {
|
|
334
|
+
"@nkzw/fbtee/no-empty-strings": 2,
|
|
335
|
+
"@nkzw/fbtee/no-unhelpful-desc": 2
|
|
336
|
+
} },
|
|
337
|
+
strict: { rules: {
|
|
338
|
+
"@nkzw/fbtee/no-empty-strings": 2,
|
|
339
|
+
"@nkzw/fbtee/no-unhelpful-desc": 2,
|
|
340
|
+
"@nkzw/fbtee/no-untranslated-strings": 2
|
|
341
|
+
} }
|
|
342
|
+
},
|
|
343
|
+
meta: {
|
|
344
|
+
name: package_default.name,
|
|
345
|
+
version: package_default.version
|
|
346
|
+
},
|
|
347
|
+
rules: {
|
|
348
|
+
"no-empty-strings": no_empty_strings_default,
|
|
349
|
+
"no-unhelpful-desc": no_unhelpful_desc_default,
|
|
350
|
+
"no-untranslated-strings": no_untranslated_strings_default
|
|
351
|
+
}
|
|
447
352
|
};
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
export { src_default as default };
|