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