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