@nkzw/eslint-plugin-fbtee 0.2.2 → 1.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/lib/index.cjs CHANGED
@@ -1,470 +1,378 @@
1
- "use strict";
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
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
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 __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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
- // src/index.tsx
21
- var index_exports = {};
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
- // package.json
26
+ //#region package.json
27
+ var name = "@nkzw/eslint-plugin-fbtee";
28
+ var version = "1.0.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.35.1" };
45
+ var devDependencies = { "@typescript-eslint/rule-tester": "^8.35.1" };
28
46
  var package_default = {
29
- name: "@nkzw/eslint-plugin-fbtee",
30
- version: "0.2.2",
31
- description: "",
32
- keywords: [],
33
- repository: {
34
- type: "git",
35
- url: "git+https://github.com/nkzw-tech/fbtee.git",
36
- directory: "packages/eslint-plugin-fbtee"
37
- },
38
- license: "MIT",
39
- type: "module",
40
- exports: {
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.29.0"
50
- },
51
- devDependencies: {
52
- "@typescript-eslint/rule-tester": "^8.29.0"
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
- // src/utils.tsx
57
- var import_utils = require("@typescript-eslint/utils");
58
- var createRule = import_utils.ESLintUtils.RuleCreator(
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
- if (object.type === "JSXMemberExpression") {
63
- return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
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
- if (node.type === "JSXFragment") {
69
- return "<>";
70
- }
71
- const { name } = node.openingElement;
72
- switch (name.type) {
73
- case "JSXIdentifier": {
74
- return name.name;
75
- }
76
- case "JSXMemberExpression": {
77
- const { object, property } = name;
78
- return resolveMemberExpressions(object, property);
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
- return node.children.map((child) => {
90
- if (child.type === "JSXText") {
91
- return child.value.trim();
92
- }
93
- if (child.type === "JSXExpressionContainer") {
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
- if (node.operator !== "+") {
101
- return null;
102
- }
103
- const leftValue = resolveNodeValue(node.left);
104
- const rightValue = resolveNodeValue(node.right);
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
- if (node.type === "Literal" && typeof node.value === "string") {
112
- return node.value;
113
- }
114
- if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
115
- return node.quasis.map((quasi) => quasi.value.raw).join("");
116
- }
117
- if (node.type === "BinaryExpression") {
118
- return resolveBinaryExpression(node);
119
- }
120
- if (node.type === "JSXExpressionContainer") {
121
- return resolveNodeValue(node.expression);
122
- }
123
- if (node.type === "ConditionalExpression") {
124
- const consequent = resolveNodeValue(node.consequent);
125
- const alternate = resolveNodeValue(node.alternate);
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
- if (prop.name.type === "JSXNamespacedName") {
137
- return `${prop.name.namespace.name}:${prop.name.name.name}`;
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
- let current = node.parent;
143
- while (current) {
144
- if (isFbtNode(current)) {
145
- return true;
146
- }
147
- current = current.parent;
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
- if (!node) {
153
- return null;
154
- }
155
- if (node.type === "JSXElement") {
156
- const name = elementType(node);
157
- return name === "fbt" || name === "fbs" || name?.startsWith("fbt:");
158
- }
159
- if (node.type === "CallExpression" && node.callee.type === "Identifier") {
160
- const name = node.callee.name;
161
- return name === "fbt" || name === "fbs";
162
- }
163
- if (node.type === "MemberExpression" && node.object.type === "CallExpression" && node.object.callee.type === "Identifier") {
164
- const name = node.object.callee.name;
165
- return name === "fbt" || name === "fbs";
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
- // src/rules/no-empty-strings.tsx
141
+ //#endregion
142
+ //#region src/rules/no-empty-strings.tsx
171
143
  var no_empty_strings_default = createRule({
172
- create(context) {
173
- return {
174
- CallExpression(node) {
175
- if (!isFbtNode(node)) {
176
- return;
177
- }
178
- const [text] = node.arguments;
179
- if (text && text.type !== "SpreadElement" && isEmptyString(text)) {
180
- context.report({
181
- messageId: "emptyString",
182
- node: text
183
- });
184
- }
185
- },
186
- JSXElement(node) {
187
- if (!isFbtNode(node)) {
188
- return;
189
- }
190
- if (node.openingElement.name.type === "JSXNamespacedName") {
191
- return;
192
- }
193
- const hasTextContent = validateChildren(node);
194
- if (!hasTextContent) {
195
- context.report({
196
- messageId: "jsxEmptyString",
197
- node
198
- });
199
- }
200
- }
201
- };
202
- },
203
- defaultOptions: [],
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
- let hasTextContent = false;
219
- for (const child of node.children) {
220
- if (child.type === "JSXText" && child.value.trim() !== "") {
221
- hasTextContent = true;
222
- }
223
- if (child.type === "JSXExpressionContainer") {
224
- if (child.expression.type === "JSXEmptyExpression") {
225
- continue;
226
- }
227
- if (child.expression.type === "Identifier" && child.expression.name !== "undefined") {
228
- hasTextContent = true;
229
- continue;
230
- }
231
- const value = resolveNodeValue(child.expression)?.trim();
232
- if (value) {
233
- hasTextContent = true;
234
- }
235
- }
236
- if (child.type === "JSXElement" || child.type === "JSXFragment") {
237
- const childName = elementType(child);
238
- if (/^[A-Z]/.test(childName) || childName.startsWith("fbt:")) {
239
- hasTextContent = true;
240
- continue;
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
- return resolveNodeValue(node)?.trim() === "";
203
+ return resolveNodeValue(node)?.trim() === "";
252
204
  }
253
205
 
254
- // src/rules/no-unhelpful-desc.tsx
206
+ //#endregion
207
+ //#region src/rules/no-unhelpful-desc.tsx
255
208
  var no_unhelpful_desc_default = createRule({
256
- create(context) {
257
- return {
258
- CallExpression(node) {
259
- if (!isFbtNode(node)) {
260
- return;
261
- }
262
- const [textArg, descArg] = node.arguments;
263
- const desc = descArg && descArg.type !== "SpreadElement" ? resolveNodeValue(descArg)?.trim() : null;
264
- if (!desc) {
265
- context.report({
266
- messageId: "emptyDesc",
267
- node: descArg || node
268
- });
269
- return;
270
- }
271
- if (desc.length <= 4) {
272
- context.report({
273
- messageId: "shortDesc",
274
- node: descArg || node
275
- });
276
- return;
277
- }
278
- const text = textArg && textArg.type !== "SpreadElement" ? resolveNodeValue(textArg)?.trim() : null;
279
- if (desc.toLowerCase() === text?.toLowerCase()) {
280
- context.report({
281
- messageId: "duplicateDesc",
282
- node: descArg || node
283
- });
284
- }
285
- },
286
- JSXAttribute(node) {
287
- if (!isFbtNode(node.parent.parent)) {
288
- return;
289
- }
290
- if (node.name.name !== "desc" || !node.value) {
291
- return;
292
- }
293
- if (node.value.type === "JSXExpressionContainer" && (node.value.expression.type === "Identifier" || node.value.expression.type === "CallExpression")) {
294
- return;
295
- }
296
- const desc = resolveNodeValue(node.value)?.trim();
297
- if (!desc) {
298
- context.report({
299
- messageId: "jsxEmptyDesc",
300
- node: node.value
301
- });
302
- return;
303
- }
304
- if (desc.length <= 4) {
305
- context.report({
306
- messageId: "shortDesc",
307
- node: node.value
308
- });
309
- return;
310
- }
311
- const textContent = resolveJsxElementTextContent(node.parent.parent);
312
- if (desc.toLowerCase() === textContent.toLowerCase()) {
313
- context.report({
314
- messageId: "duplicateDesc",
315
- node: node.value
316
- });
317
- }
318
- }
319
- };
320
- },
321
- defaultOptions: [],
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
- // src/rules/no-untranslated-strings.tsx
339
- var attributes = /* @__PURE__ */ new Set([
340
- "title",
341
- "placeholder",
342
- "alt",
343
- "label",
344
- "aria-label",
345
- "aria-errormessage"
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
- create(context, options) {
349
- const ignoredWords = new Set(
350
- options[0].ignoredWords.map((s) => s.trim().toLowerCase())
351
- );
352
- function isIgnoredWord(value) {
353
- return ignoredWords.has(value.trim().toLowerCase());
354
- }
355
- return {
356
- JSXAttribute(node) {
357
- const propName = getPropName(node);
358
- if (!attributes.has(propName)) {
359
- return;
360
- }
361
- const value = node.value ? resolveNodeValue(node.value) : null;
362
- if (!value) {
363
- return;
364
- }
365
- if (isIgnoredWord(value)) {
366
- return;
367
- }
368
- if (node.value?.type === "JSXExpressionContainer" && isFbtNode(node.value.expression)) {
369
- return;
370
- }
371
- context.report({
372
- messageId: "unwrappedString",
373
- node
374
- });
375
- },
376
- JSXExpressionContainer(node) {
377
- if (node.parent.type === "JSXAttribute") {
378
- return;
379
- }
380
- const value = resolveNodeValue(node.expression);
381
- if (!value) {
382
- return;
383
- }
384
- if (isIgnoredWord(value)) {
385
- return;
386
- }
387
- if (!hasFbtParent(node)) {
388
- context.report({
389
- messageId: "unwrappedString",
390
- node
391
- });
392
- }
393
- },
394
- JSXText(node) {
395
- const value = node.value.trim();
396
- if (!value) {
397
- return;
398
- }
399
- if (isIgnoredWord(value)) {
400
- return;
401
- }
402
- if (!hasFbtParent(node)) {
403
- const startOffset = node.range[0] + node.value.indexOf(value);
404
- const endOffset = startOffset + value.length;
405
- context.report({
406
- loc: {
407
- end: context.sourceCode.getLocFromIndex(endOffset),
408
- start: context.sourceCode.getLocFromIndex(startOffset)
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
- // src/index.tsx
445
- var index_default = {
446
- configs: {
447
- recommended: {
448
- rules: {
449
- "@nkzw/fbtee/no-empty-strings": 2,
450
- "@nkzw/fbtee/no-unhelpful-desc": 2
451
- }
452
- },
453
- strict: {
454
- rules: {
455
- "@nkzw/fbtee/no-empty-strings": 2,
456
- "@nkzw/fbtee/no-unhelpful-desc": 2,
457
- "@nkzw/fbtee/no-untranslated-strings": 2
458
- }
459
- }
460
- },
461
- meta: {
462
- name: package_default.name,
463
- version: package_default.version
464
- },
465
- rules: {
466
- "no-empty-strings": no_empty_strings_default,
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;