@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/lib/index.js CHANGED
@@ -1,447 +1,355 @@
1
- // package.json
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
- name: "@nkzw/eslint-plugin-fbtee",
4
- version: "0.2.2",
5
- description: "",
6
- keywords: [],
7
- repository: {
8
- type: "git",
9
- url: "git+https://github.com/nkzw-tech/fbtee.git",
10
- directory: "packages/eslint-plugin-fbtee"
11
- },
12
- license: "MIT",
13
- type: "module",
14
- exports: {
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
- // src/utils.tsx
31
- import { ESLintUtils } from "@typescript-eslint/utils";
32
- var createRule = ESLintUtils.RuleCreator(
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
- if (object.type === "JSXMemberExpression") {
37
- return `${resolveMemberExpressions(object.object, object.property)}.${property.name}`;
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
- if (node.type === "JSXFragment") {
43
- return "<>";
44
- }
45
- const { name } = node.openingElement;
46
- switch (name.type) {
47
- case "JSXIdentifier": {
48
- return name.name;
49
- }
50
- case "JSXMemberExpression": {
51
- const { object, property } = name;
52
- return resolveMemberExpressions(object, property);
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
- return node.children.map((child) => {
64
- if (child.type === "JSXText") {
65
- return child.value.trim();
66
- }
67
- if (child.type === "JSXExpressionContainer") {
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
- if (node.operator !== "+") {
75
- return null;
76
- }
77
- const leftValue = resolveNodeValue(node.left);
78
- const rightValue = resolveNodeValue(node.right);
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
- if (node.type === "Literal" && typeof node.value === "string") {
86
- return node.value;
87
- }
88
- if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
89
- return node.quasis.map((quasi) => quasi.value.raw).join("");
90
- }
91
- if (node.type === "BinaryExpression") {
92
- return resolveBinaryExpression(node);
93
- }
94
- if (node.type === "JSXExpressionContainer") {
95
- return resolveNodeValue(node.expression);
96
- }
97
- if (node.type === "ConditionalExpression") {
98
- const consequent = resolveNodeValue(node.consequent);
99
- const alternate = resolveNodeValue(node.alternate);
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
- if (prop.name.type === "JSXNamespacedName") {
111
- return `${prop.name.namespace.name}:${prop.name.name.name}`;
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
- let current = node.parent;
117
- while (current) {
118
- if (isFbtNode(current)) {
119
- return true;
120
- }
121
- current = current.parent;
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
- if (!node) {
127
- return null;
128
- }
129
- if (node.type === "JSXElement") {
130
- const name = elementType(node);
131
- return name === "fbt" || name === "fbs" || name?.startsWith("fbt:");
132
- }
133
- if (node.type === "CallExpression" && node.callee.type === "Identifier") {
134
- const name = node.callee.name;
135
- return name === "fbt" || name === "fbs";
136
- }
137
- if (node.type === "MemberExpression" && node.object.type === "CallExpression" && node.object.callee.type === "Identifier") {
138
- const name = node.object.callee.name;
139
- return name === "fbt" || name === "fbs";
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
- // src/rules/no-empty-strings.tsx
118
+ //#endregion
119
+ //#region src/rules/no-empty-strings.tsx
145
120
  var no_empty_strings_default = createRule({
146
- create(context) {
147
- return {
148
- CallExpression(node) {
149
- if (!isFbtNode(node)) {
150
- return;
151
- }
152
- const [text] = node.arguments;
153
- if (text && text.type !== "SpreadElement" && isEmptyString(text)) {
154
- context.report({
155
- messageId: "emptyString",
156
- node: text
157
- });
158
- }
159
- },
160
- JSXElement(node) {
161
- if (!isFbtNode(node)) {
162
- return;
163
- }
164
- if (node.openingElement.name.type === "JSXNamespacedName") {
165
- return;
166
- }
167
- const hasTextContent = validateChildren(node);
168
- if (!hasTextContent) {
169
- context.report({
170
- messageId: "jsxEmptyString",
171
- node
172
- });
173
- }
174
- }
175
- };
176
- },
177
- defaultOptions: [],
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
- let hasTextContent = false;
193
- for (const child of node.children) {
194
- if (child.type === "JSXText" && child.value.trim() !== "") {
195
- hasTextContent = true;
196
- }
197
- if (child.type === "JSXExpressionContainer") {
198
- if (child.expression.type === "JSXEmptyExpression") {
199
- continue;
200
- }
201
- if (child.expression.type === "Identifier" && child.expression.name !== "undefined") {
202
- hasTextContent = true;
203
- continue;
204
- }
205
- const value = resolveNodeValue(child.expression)?.trim();
206
- if (value) {
207
- hasTextContent = true;
208
- }
209
- }
210
- if (child.type === "JSXElement" || child.type === "JSXFragment") {
211
- const childName = elementType(child);
212
- if (/^[A-Z]/.test(childName) || childName.startsWith("fbt:")) {
213
- hasTextContent = true;
214
- continue;
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
- return resolveNodeValue(node)?.trim() === "";
180
+ return resolveNodeValue(node)?.trim() === "";
226
181
  }
227
182
 
228
- // src/rules/no-unhelpful-desc.tsx
183
+ //#endregion
184
+ //#region src/rules/no-unhelpful-desc.tsx
229
185
  var no_unhelpful_desc_default = createRule({
230
- create(context) {
231
- return {
232
- CallExpression(node) {
233
- if (!isFbtNode(node)) {
234
- return;
235
- }
236
- const [textArg, descArg] = node.arguments;
237
- const desc = descArg && descArg.type !== "SpreadElement" ? resolveNodeValue(descArg)?.trim() : null;
238
- if (!desc) {
239
- context.report({
240
- messageId: "emptyDesc",
241
- node: descArg || node
242
- });
243
- return;
244
- }
245
- if (desc.length <= 4) {
246
- context.report({
247
- messageId: "shortDesc",
248
- node: descArg || node
249
- });
250
- return;
251
- }
252
- const text = textArg && textArg.type !== "SpreadElement" ? resolveNodeValue(textArg)?.trim() : null;
253
- if (desc.toLowerCase() === text?.toLowerCase()) {
254
- context.report({
255
- messageId: "duplicateDesc",
256
- node: descArg || node
257
- });
258
- }
259
- },
260
- JSXAttribute(node) {
261
- if (!isFbtNode(node.parent.parent)) {
262
- return;
263
- }
264
- if (node.name.name !== "desc" || !node.value) {
265
- return;
266
- }
267
- if (node.value.type === "JSXExpressionContainer" && (node.value.expression.type === "Identifier" || node.value.expression.type === "CallExpression")) {
268
- return;
269
- }
270
- const desc = resolveNodeValue(node.value)?.trim();
271
- if (!desc) {
272
- context.report({
273
- messageId: "jsxEmptyDesc",
274
- node: node.value
275
- });
276
- return;
277
- }
278
- if (desc.length <= 4) {
279
- context.report({
280
- messageId: "shortDesc",
281
- node: node.value
282
- });
283
- return;
284
- }
285
- const textContent = resolveJsxElementTextContent(node.parent.parent);
286
- if (desc.toLowerCase() === textContent.toLowerCase()) {
287
- context.report({
288
- messageId: "duplicateDesc",
289
- node: node.value
290
- });
291
- }
292
- }
293
- };
294
- },
295
- defaultOptions: [],
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
- // src/rules/no-untranslated-strings.tsx
313
- var attributes = /* @__PURE__ */ new Set([
314
- "title",
315
- "placeholder",
316
- "alt",
317
- "label",
318
- "aria-label",
319
- "aria-errormessage"
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
- create(context, options) {
323
- const ignoredWords = new Set(
324
- options[0].ignoredWords.map((s) => s.trim().toLowerCase())
325
- );
326
- function isIgnoredWord(value) {
327
- return ignoredWords.has(value.trim().toLowerCase());
328
- }
329
- return {
330
- JSXAttribute(node) {
331
- const propName = getPropName(node);
332
- if (!attributes.has(propName)) {
333
- return;
334
- }
335
- const value = node.value ? resolveNodeValue(node.value) : null;
336
- if (!value) {
337
- return;
338
- }
339
- if (isIgnoredWord(value)) {
340
- return;
341
- }
342
- if (node.value?.type === "JSXExpressionContainer" && isFbtNode(node.value.expression)) {
343
- return;
344
- }
345
- context.report({
346
- messageId: "unwrappedString",
347
- node
348
- });
349
- },
350
- JSXExpressionContainer(node) {
351
- if (node.parent.type === "JSXAttribute") {
352
- return;
353
- }
354
- const value = resolveNodeValue(node.expression);
355
- if (!value) {
356
- return;
357
- }
358
- if (isIgnoredWord(value)) {
359
- return;
360
- }
361
- if (!hasFbtParent(node)) {
362
- context.report({
363
- messageId: "unwrappedString",
364
- node
365
- });
366
- }
367
- },
368
- JSXText(node) {
369
- const value = node.value.trim();
370
- if (!value) {
371
- return;
372
- }
373
- if (isIgnoredWord(value)) {
374
- return;
375
- }
376
- if (!hasFbtParent(node)) {
377
- const startOffset = node.range[0] + node.value.indexOf(value);
378
- const endOffset = startOffset + value.length;
379
- context.report({
380
- loc: {
381
- end: context.sourceCode.getLocFromIndex(endOffset),
382
- start: context.sourceCode.getLocFromIndex(startOffset)
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
- // src/index.tsx
419
- var index_default = {
420
- configs: {
421
- recommended: {
422
- rules: {
423
- "@nkzw/fbtee/no-empty-strings": 2,
424
- "@nkzw/fbtee/no-unhelpful-desc": 2
425
- }
426
- },
427
- strict: {
428
- rules: {
429
- "@nkzw/fbtee/no-empty-strings": 2,
430
- "@nkzw/fbtee/no-unhelpful-desc": 2,
431
- "@nkzw/fbtee/no-untranslated-strings": 2
432
- }
433
- }
434
- },
435
- meta: {
436
- name: package_default.name,
437
- version: package_default.version
438
- },
439
- rules: {
440
- "no-empty-strings": no_empty_strings_default,
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 };