@nkzw/eslint-plugin-fbtee 1.3.1 → 1.4.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 CHANGED
@@ -605,7 +605,7 @@ const Root = () => {
605
605
 
606
606
  ## ESLint Plugin
607
607
 
608
- You can install the optional eslint plugin to catch common mistakes and enforce best practices when using **fbtee**:
608
+ You can install the optional ESLint plugin to catch common mistakes and enforce best practices when using **fbtee**:
609
609
 
610
610
  ```bash
611
611
  npm install -D @nkzw/eslint-plugin-fbtee
@@ -614,32 +614,56 @@ npm install -D @nkzw/eslint-plugin-fbtee
614
614
  Add the following configuration to your ESLint configuration:
615
615
 
616
616
  ```js
617
- {
618
- extends: ['plugin:@nkzw/eslint-plugin-fbtee/recommended'],
619
- plugins: ['@nkzw/eslint-plugin-fbtee'],
620
- }
617
+ import fbtee from '@nkzw/eslint-plugin-fbtee';
618
+
619
+ export default [
620
+ fbtee.configs.recommended,
621
+ {
622
+ plugins: {
623
+ '@nkzw/fbtee': fbtee,
624
+ },
625
+ },
626
+ ];
621
627
  ```
622
628
 
623
629
  If you want stricter enforcement of translation rules, you can use the strict configuration, which enables the `no-untranslated-strings` rule. This ensures that all strings in your codebase are marked for translation.
624
630
 
625
631
  ```js
626
- {
627
- extends: ['plugin:@nkzw/eslint-plugin-fbtee/strict'],
628
- plugins: ['@nkzw/eslint-plugin-fbtee'],
629
- }
632
+ import fbtee from '@nkzw/eslint-plugin-fbtee';
633
+
634
+ export default [
635
+ fbtee.configs.strict,
636
+ {
637
+ plugins: {
638
+ '@nkzw/fbtee': fbtee,
639
+ },
640
+ },
641
+ ];
630
642
  ```
631
643
 
632
644
  Alternatively, if you'd like more granular control over the rules:
633
645
 
634
646
  ```js
635
- {
636
- plugins: ['@nkzw/eslint-plugin-fbtee'],
637
- rules: {
638
- '@nkzw/fbtee/no-empty-strings': 'error',
639
- '@nkzw/fbtee/no-unhelpful-desc': 'error',
640
- '@nkzw/fbtee/no-untranslated-strings': 'error',
647
+ import fbtee from '@nkzw/eslint-plugin-fbtee';
648
+
649
+ export default [
650
+ fbtee,
651
+ {
652
+ plugins: {
653
+ '@nkzw/fbtee': fbtee,
654
+ },
655
+ rules: {
656
+ '@nkzw/fbtee/no-empty-strings': [
657
+ 'error',
658
+ {
659
+ ignoredWords: ['Banana', 'pnpm install fbtee'],
660
+ },
661
+ ],
662
+ '@nkzw/fbtee/no-unhelpful-desc': 'error',
663
+ '@nkzw/fbtee/no-untranslated-strings': 'error',
664
+ },
641
665
  },
642
- }
666
+ ];
643
667
  ```
644
668
 
645
669
  ## What's better about `fbtee` than `fbt`?
package/lib/index.cjs CHANGED
@@ -25,7 +25,7 @@ const __typescript_eslint_utils = __toESM(require("@typescript-eslint/utils"));
25
25
 
26
26
  //#region package.json
27
27
  var name = "@nkzw/eslint-plugin-fbtee";
28
- var version = "1.3.1";
28
+ var version = "1.3.2";
29
29
  var description = "";
30
30
  var keywords = [];
31
31
  var repository = {
@@ -41,8 +41,8 @@ var exports$1 = {
41
41
  };
42
42
  var main = "lib/index.cjs";
43
43
  var scripts = { "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts" };
44
- var dependencies = { "@typescript-eslint/utils": "^8.38.0" };
45
- var devDependencies = { "@typescript-eslint/rule-tester": "^8.38.0" };
44
+ var dependencies = { "@typescript-eslint/utils": "^8.42.0" };
45
+ var devDependencies = { "@typescript-eslint/rule-tester": "^8.42.0" };
46
46
  var package_default = {
47
47
  name,
48
48
  version,
@@ -284,12 +284,15 @@ const attributes = new Set([
284
284
  "aria-label",
285
285
  "aria-errormessage"
286
286
  ]);
287
+ const shouldIgnoreParent = (node) => {
288
+ const { parent } = node;
289
+ const openingElement = parent?.type === "JSXElement" ? parent?.openingElement : null;
290
+ return openingElement?.type === "JSXOpeningElement" && openingElement.name.type === "JSXIdentifier" && (openingElement.name.name === "pre" || openingElement.name.name === "code");
291
+ };
287
292
  var no_untranslated_strings_default = createRule({
288
293
  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
- }
294
+ const ignoredWords = new Set(options[0].ignoredWords.map((value) => value.trim().toLowerCase()));
295
+ const isIgnoredWord = (value) => ignoredWords.has(value.replaceAll(/\s+/g, " ").toLowerCase());
293
296
  return {
294
297
  JSXAttribute(node) {
295
298
  const propName = getPropName(node);
@@ -308,6 +311,7 @@ var no_untranslated_strings_default = createRule({
308
311
  const value = resolveNodeValue(node.expression);
309
312
  if (!value) return;
310
313
  if (isIgnoredWord(value)) return;
314
+ if (shouldIgnoreParent(node)) return;
311
315
  if (!hasFbtParent(node)) context.report({
312
316
  messageId: "unwrappedString",
313
317
  node
@@ -317,6 +321,7 @@ var no_untranslated_strings_default = createRule({
317
321
  const value = node.value.trim();
318
322
  if (!value) return;
319
323
  if (isIgnoredWord(value)) return;
324
+ if (shouldIgnoreParent(node)) return;
320
325
  if (!hasFbtParent(node)) {
321
326
  const startOffset = node.range[0] + node.value.indexOf(value);
322
327
  const endOffset = startOffset + value.length;
package/lib/index.d.cts CHANGED
@@ -29,5 +29,4 @@ declare const _default: {
29
29
  }], unknown, _typescript_eslint_utils_ts_eslint0.RuleListener>;
30
30
  };
31
31
  };
32
- //#endregion
33
- export { _default as default };
32
+ export = _default;
package/lib/index.js CHANGED
@@ -2,7 +2,7 @@ import { ESLintUtils } from "@typescript-eslint/utils";
2
2
 
3
3
  //#region package.json
4
4
  var name = "@nkzw/eslint-plugin-fbtee";
5
- var version = "1.3.1";
5
+ var version = "1.3.2";
6
6
  var description = "";
7
7
  var keywords = [];
8
8
  var repository = {
@@ -18,8 +18,8 @@ var exports = {
18
18
  };
19
19
  var main = "lib/index.cjs";
20
20
  var scripts = { "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts" };
21
- var dependencies = { "@typescript-eslint/utils": "^8.38.0" };
22
- var devDependencies = { "@typescript-eslint/rule-tester": "^8.38.0" };
21
+ var dependencies = { "@typescript-eslint/utils": "^8.42.0" };
22
+ var devDependencies = { "@typescript-eslint/rule-tester": "^8.42.0" };
23
23
  var package_default = {
24
24
  name,
25
25
  version,
@@ -261,12 +261,15 @@ const attributes = new Set([
261
261
  "aria-label",
262
262
  "aria-errormessage"
263
263
  ]);
264
+ const shouldIgnoreParent = (node) => {
265
+ const { parent } = node;
266
+ const openingElement = parent?.type === "JSXElement" ? parent?.openingElement : null;
267
+ return openingElement?.type === "JSXOpeningElement" && openingElement.name.type === "JSXIdentifier" && (openingElement.name.name === "pre" || openingElement.name.name === "code");
268
+ };
264
269
  var no_untranslated_strings_default = createRule({
265
270
  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
- }
271
+ const ignoredWords = new Set(options[0].ignoredWords.map((value) => value.trim().toLowerCase()));
272
+ const isIgnoredWord = (value) => ignoredWords.has(value.replaceAll(/\s+/g, " ").toLowerCase());
270
273
  return {
271
274
  JSXAttribute(node) {
272
275
  const propName = getPropName(node);
@@ -285,6 +288,7 @@ var no_untranslated_strings_default = createRule({
285
288
  const value = resolveNodeValue(node.expression);
286
289
  if (!value) return;
287
290
  if (isIgnoredWord(value)) return;
291
+ if (shouldIgnoreParent(node)) return;
288
292
  if (!hasFbtParent(node)) context.report({
289
293
  messageId: "unwrappedString",
290
294
  node
@@ -294,6 +298,7 @@ var no_untranslated_strings_default = createRule({
294
298
  const value = node.value.trim();
295
299
  if (!value) return;
296
300
  if (isIgnoredWord(value)) return;
301
+ if (shouldIgnoreParent(node)) return;
297
302
  if (!hasFbtParent(node)) {
298
303
  const startOffset = node.range[0] + node.value.indexOf(value);
299
304
  const endOffset = startOffset + value.length;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nkzw/eslint-plugin-fbtee",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -16,10 +16,10 @@
16
16
  },
17
17
  "main": "lib/index.cjs",
18
18
  "dependencies": {
19
- "@typescript-eslint/utils": "^8.38.0"
19
+ "@typescript-eslint/utils": "^8.42.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@typescript-eslint/rule-tester": "^8.38.0"
22
+ "@typescript-eslint/rule-tester": "^8.42.0"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsdown src/index.tsx -d lib --target=node22 --format=esm,cjs --clean --dts"
@@ -225,6 +225,28 @@ ruleTester.run('no-untranslated-strings', rule, {
225
225
  },
226
226
  ],
227
227
  },
228
+ {
229
+ code: `
230
+ <div>
231
+ <p>GitHub is cool</p>
232
+ <p>{\`GitHub
233
+ is cool\`}</p>
234
+ <p>
235
+ GitHub
236
+ is
237
+ cool
238
+ </p>
239
+ <p>
240
+ GitHub is cool
241
+ </p>
242
+ </div>
243
+ `,
244
+ options: [
245
+ {
246
+ ignoredWords: ['GitHub is cool'],
247
+ },
248
+ ],
249
+ },
228
250
  {
229
251
  code: `
230
252
  <textarea rows={2}></textarea>
@@ -275,5 +297,19 @@ ruleTester.run('no-untranslated-strings', rule, {
275
297
  const foo = helloGreeting || 'goodbye'
276
298
  `,
277
299
  },
300
+ {
301
+ code: `
302
+ <code>
303
+ console.log('Banana Banana Banana');
304
+ </code>
305
+ `,
306
+ },
307
+ {
308
+ code: `
309
+ <pre>{\`
310
+ console.log('Banana Banana Banana');
311
+ \`}</pre>
312
+ `,
313
+ },
278
314
  ],
279
315
  });
@@ -1,3 +1,4 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
1
2
  import {
2
3
  createRule,
3
4
  getPropName,
@@ -17,15 +18,25 @@ const attributes = new Set([
17
18
  'aria-errormessage',
18
19
  ]);
19
20
 
21
+ const shouldIgnoreParent = (node: TSESTree.Node) => {
22
+ const { parent } = node;
23
+ const openingElement =
24
+ parent?.type === 'JSXElement' ? parent?.openingElement : null;
25
+ return (
26
+ openingElement?.type === 'JSXOpeningElement' &&
27
+ openingElement.name.type === 'JSXIdentifier' &&
28
+ (openingElement.name.name === 'pre' || openingElement.name.name === 'code')
29
+ );
30
+ };
31
+
20
32
  export default createRule<Options, 'unwrappedString'>({
21
33
  create(context, options) {
22
34
  const ignoredWords = new Set(
23
- options[0].ignoredWords.map((s) => s.trim().toLowerCase()),
35
+ options[0].ignoredWords.map((value) => value.trim().toLowerCase()),
24
36
  );
25
37
 
26
- function isIgnoredWord(value: string) {
27
- return ignoredWords.has(value.trim().toLowerCase());
28
- }
38
+ const isIgnoredWord = (value: string) =>
39
+ ignoredWords.has(value.replaceAll(/\s+/g, ' ').toLowerCase());
29
40
 
30
41
  return {
31
42
  JSXAttribute(node) {
@@ -73,6 +84,10 @@ export default createRule<Options, 'unwrappedString'>({
73
84
  return;
74
85
  }
75
86
 
87
+ if (shouldIgnoreParent(node)) {
88
+ return;
89
+ }
90
+
76
91
  if (!hasFbtParent(node)) {
77
92
  context.report({
78
93
  messageId: 'unwrappedString',
@@ -92,6 +107,10 @@ export default createRule<Options, 'unwrappedString'>({
92
107
  return;
93
108
  }
94
109
 
110
+ if (shouldIgnoreParent(node)) {
111
+ return;
112
+ }
113
+
95
114
  if (!hasFbtParent(node)) {
96
115
  const startOffset = node.range[0] + node.value.indexOf(value);
97
116
  const endOffset = startOffset + value.length;