diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts index 60c610471..19561057b 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.spec.ts @@ -9,7 +9,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "PascalCase.tsx", rule: "kebab-case", @@ -24,7 +24,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "camelCase.tsx", rule: "PascalCase", @@ -39,7 +39,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "kebab-case.tsx", rule: "PascalCase", @@ -54,7 +54,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "snake_case.tsx", rule: "PascalCase", @@ -69,7 +69,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "PascalCase.tsx", rule: "camelCase", @@ -84,7 +84,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "kebab-case.tsx", rule: "camelCase", @@ -99,7 +99,7 @@ ruleTester.run(RULE_NAME, rule, { code, errors: [ { - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", data: { name: "snake_case.tsx", rule: "camelCase", diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts index c86c69590..3f0c2367c 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/filename.ts @@ -16,8 +16,7 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - // | "filenameCaseMismatch" - | "filenameCaseMismatchSuggestion" + | "filenameCaseMismatch" | "filenameEmpty"; type Case = "camelCase" | "kebab-case" | "PascalCase" | "snake_case"; @@ -86,9 +85,7 @@ export default createRule({ description: "enforce naming convention for JSX filenames", }, messages: { - // filenameCaseMismatch: "A file with name '{{name}}' does not match {{rule}}.", - filenameCaseMismatchSuggestion: - "A file with name '{{name}}' does not match {{rule}}. Should rename to '{{suggestion}}'.", + filenameCaseMismatch: "A file with name '{{name}}' does not match {{rule}}. Should rename to '{{suggestion}}'.", filenameEmpty: "A file must have non-empty name.", }, schema, @@ -138,7 +135,7 @@ export default createRule({ return; } context.report({ - messageId: "filenameCaseMismatchSuggestion", + messageId: "filenameCaseMismatch", node, data: { name: context.filename, diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.spec.ts index 2a4348731..53eb55a30 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.spec.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.spec.ts @@ -5,65 +5,65 @@ ruleTester.run(RULE_NAME, rule, { invalid: [ { code: /* tsx */ `[];`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[];`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[, ];`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2 ,3].map(function(x) { return });`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2 ,3].map(x => );`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2 ,3].map(x => x && );`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: '[1, 2 ,3].map(x => x ? : );', - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: '[1, 2 ,3].map(x => x ? : );', - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2 ,3].map(x => { return });`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `Array.from([1, 2 ,3], function(x) { return });`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `Array.from([1, 2 ,3], (x => { return }));`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `Array.from([1, 2 ,3], (x => ));`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2, 3]?.map(x => )`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2, 3]?.map(x => )`, - errors: [{ messageId: "noMissingKey" }], + errors: [{ messageId: "missingKey" }], }, { code: /* tsx */ `[1, 2, 3].map(x => <>{x});`, errors: [ { - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", }, ], }, @@ -71,7 +71,7 @@ ruleTester.run(RULE_NAME, rule, { code: /* tsx */ `[<>];`, errors: [ { - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", }, ], }, @@ -94,8 +94,8 @@ ruleTester.run(RULE_NAME, rule, { }; `, errors: [ - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, ], }, { @@ -121,10 +121,10 @@ ruleTester.run(RULE_NAME, rule, { }; `, errors: [ - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, ], }, { @@ -144,9 +144,9 @@ ruleTester.run(RULE_NAME, rule, { }; `, errors: [ - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, - { messageId: "noMissingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, + { messageId: "missingKey" }, ], }, { @@ -176,13 +176,13 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", }, { - messageId: "noMissingKey", + messageId: "missingKey", }, { - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", }, ], }, diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.ts index 3c7207351..e3d0e442d 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-key.ts @@ -15,8 +15,8 @@ export const RULE_FEATURES = [ ] as const satisfies RuleFeature[]; export type MessageID = - | "noMissingKey" - | "noMissingKeyWithFragment"; + | "missingKey" + | "unexpectedFragmentSyntax"; export default createRule<[], MessageID>({ meta: { @@ -26,8 +26,8 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noMissingKey: "Missing 'key' for element when rendering list.", - noMissingKeyWithFragment: "Use fragment component instead of '<>' because it does not support `key`.", + missingKey: "Missing 'key' for element when rendering list.", + unexpectedFragmentSyntax: "Use fragment component instead of '<>' because it does not support `key`.", }, schema: [], }, @@ -40,7 +40,7 @@ export default createRule<[], MessageID>({ const initialScope = context.sourceCode.getScope(node); if (!JSX.hasAttribute("key", node.openingElement.attributes, initialScope)) { return { - messageId: "noMissingKey", + messageId: "missingKey", node, } as const; } @@ -48,7 +48,7 @@ export default createRule<[], MessageID>({ } case T.JSXFragment: { return { - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", node, } as const; } @@ -104,7 +104,7 @@ export default createRule<[], MessageID>({ for (const element of elements) { if (!JSX.hasAttribute("key", element.openingElement.attributes, initialScope)) { context.report({ - messageId: "noMissingKey", + messageId: "missingKey", node: element, }); } @@ -146,7 +146,7 @@ export default createRule<[], MessageID>({ } if (node.parent.type === T.ArrayExpression) { context.report({ - messageId: "noMissingKeyWithFragment", + messageId: "unexpectedFragmentSyntax", node, }); } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.spec.ts index 285a5c37d..cad22c539 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.spec.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.spec.ts @@ -10,7 +10,13 @@ ruleTester.run(RULE_NAME, rule, { return ; } `, - errors: [{ messageId: "noUnstableContextValueWithIdentifier" }], + errors: [{ + messageId: "unstableContextValue", + data: { + type: "object expression", + suggestion: "Consider wrapping it in a useMemo hook.", + }, + }], }, { code: /* tsx */ ` @@ -21,7 +27,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noUnstableContextValueWithIdentifier", + messageId: "unstableContextValue", + data: { + type: "array expression", + suggestion: "Consider wrapping it in a useMemo hook.", + }, }, ], }, @@ -34,7 +44,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noUnstableContextValueWithIdentifier", + messageId: "unstableContextValue", + data: { + type: "new expression", + suggestion: "Consider wrapping it in a useMemo hook.", + }, }, ], }, @@ -47,7 +61,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noUnstableContextValueWithFunction", + messageId: "unstableContextValue", + data: { + type: "arrow function expression", + suggestion: "Consider wrapping it in a useCallback hook.", + }, }, ], }, @@ -62,7 +80,11 @@ ruleTester.run(RULE_NAME, rule, { `, errors: [ { - messageId: "noUnstableContextValueWithIdentifier", + messageId: "unstableContextValue", + data: { + type: "object expression", + suggestion: "Consider wrapping it in a useMemo hook.", + }, }, ], }, diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts index bee8c18f9..2732b169b 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts @@ -13,10 +13,7 @@ export const RULE_FEATURES = [ "CHK", ] as const satisfies RuleFeature[]; -export type MessageID = - | "noUnstableContextValue" - | "noUnstableContextValueWithFunction" - | "noUnstableContextValueWithIdentifier"; +export type MessageID = "unstableContextValue"; export default createRule<[], MessageID>({ meta: { @@ -26,12 +23,8 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noUnstableContextValue: - "A/an '{{type}}' passed as the value prop to the context provider should not be constructed. It will change on every render.", - noUnstableContextValueWithFunction: - "A/an '{{type}}' passed as the value prop to the context provider should not be constructed. It will change on every render. Consider wrapping it in a useCallback hook.", - noUnstableContextValueWithIdentifier: - "A/an '{{type}}' passed as the value prop to the context provider should not be constructed. It will change on every render. Consider wrapping it in a useMemo hook.", + unstableContextValue: + "A/an '{{type}}' passed as the value prop to the context provider should not be constructed. It will change on every render. {{suggestion}}", }, schema: [], }, @@ -75,14 +68,15 @@ export default createRule<[], MessageID>({ for (const { node: component } of components) { for (const construction of constructions.get(component) ?? []) { const { kind, node: constructionNode } = construction; - const messageId = kind.startsWith("Function") - ? "noUnstableContextValueWithFunction" - : "noUnstableContextValueWithIdentifier"; + const suggestion = kind.startsWith("Function") + ? "Consider wrapping it in a useCallback hook." + : "Consider wrapping it in a useMemo hook."; context.report({ - messageId, + messageId: "unstableContextValue", node: constructionNode, data: { type: AST.toReadableNodeType(constructionNode), + suggestion, }, }); } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.spec.ts index 3fabe74a3..01bd42229 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.spec.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.spec.ts @@ -7,33 +7,79 @@ ruleTester.run(RULE_NAME, rule, { invalid: [ { code: /* tsx */ `<>`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [{ + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }], output: null, }, { code: /* tsx */ `

<>foo

`, errors: [ - { type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }, - { type: T.JSXFragment, messageId: "noUselessFragment" }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, ], output: /* tsx */ `

foo

`, }, { code: /* tsx */ `

moo<>foo

`, errors: [ - { type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }, - { type: T.JSXFragment, messageId: "noUselessFragment" }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, ], output: "

moofoo

", }, { code: /* tsx */ `

<>{meow}

`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + ], output: "

{meow}

", }, { code: /* tsx */ `<>
`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: /* tsx */ `
`, }, { @@ -42,14 +88,30 @@ ruleTester.run(RULE_NAME, rule, {
`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: /* tsx */ `
`, }, { code: /* tsx */ ``, - errors: [{ type: T.JSXElement, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: null, }, { @@ -58,27 +120,63 @@ ruleTester.run(RULE_NAME, rule, { `, - errors: [{ type: T.JSXElement, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: /* tsx */ ` `, }, { code: /* tsx */ `<>foo`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: null, }, { code: /* tsx */ `
<>foo
`, errors: [ - { type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }, - { type: T.JSXFragment, messageId: "noUselessFragment" }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, ], output: "
foo
", }, { - code: '
<>{"a"}{"b"}
', - errors: [{ type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }], + code: /* tsx */ `
<>{"a"}{"b"}
`, + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + ], output: '
{"a"}{"b"}
', }, { @@ -89,7 +187,15 @@ ruleTester.run(RULE_NAME, rule, { <>{"a"}{"b"} `, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + ], output: /* tsx */ `
@@ -99,8 +205,16 @@ ruleTester.run(RULE_NAME, rule, { `, }, { - code: '
{"a"}{"b"}
', - errors: [{ type: T.JSXElement, messageId: "noUselessFragmentInBuiltIn" }], + code: /* tsx */ `
{"a"}{"b"}
`, + errors: [ + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + ], output: '
{"a"}{"b"}
', }, { @@ -115,8 +229,20 @@ ruleTester.run(RULE_NAME, rule, {
`, errors: [ - { type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }, - { type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, ], output: /* tsx */ `
@@ -127,64 +253,123 @@ ruleTester.run(RULE_NAME, rule, { `, }, { - code: '
a <>{""}{""} a
', - errors: [{ type: T.JSXFragment, messageId: "noUselessFragmentInBuiltIn" }], + code: /* tsx */ `
a <>{""}{""} a
`, + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + ], output: '
a {""}{""} a
', }, { - code: /* tsx */ ` - const Comp = () => ( - - - - ); - `, + code: /* tsx */ `const Comp = () => ();`, errors: [ - { type: T.JSXElement, messageId: "noUselessFragmentInBuiltIn" }, - { type: T.JSXElement, messageId: "noUselessFragment" }, + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "placed inside a built-in component", + }, + }, + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, ], - // eslint-disable-next-line unicorn/template-indent - output: /* tsx */ ` - const Comp = () => ( - - - - ); - `, + output: /* tsx */ `const Comp = () => ();`, }, // Ensure allowExpressions still catches expected violations { code: /* tsx */ `<>{moo}`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], output: /* tsx */ `{moo}`, }, { code: /* tsx */ `<>{moo}`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], options: [{ allowExpressions: false }], }, { code: /* tsx */ `<>{moo}`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], options: [{ allowExpressions: false }], }, { code: /* tsx */ `<>{moo}`, - errors: [{ type: T.JSXElement, messageId: "noUselessFragment" }, { - type: T.JSXFragment, - messageId: "noUselessFragment", - }], + errors: [ + { + type: T.JSXElement, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], options: [{ allowExpressions: false }], output: /* tsx */ `<>{moo}`, }, { code: /* tsx */ `baz}/>`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], options: [{ allowExpressions: false }], }, { code: /* tsx */ `<>`, - errors: [{ type: T.JSXFragment, messageId: "noUselessFragment" }], + errors: [ + { + type: T.JSXFragment, + messageId: "uselessFragment", + data: { + reason: "contains less than two children", + }, + }, + ], options: [{ allowExpressions: false }], }, ], diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.ts index 11e69200a..d7fb15788 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.ts @@ -15,9 +15,7 @@ export const RULE_FEATURES = [ "CFG", ] as const satisfies RuleFeature[]; -export type MessageID = - | "noUselessFragment" - | "noUselessFragmentInBuiltIn"; +export type MessageID = "uselessFragment"; type Options = [ { @@ -86,11 +84,25 @@ function checkAndReport( } // report if the fragment is placed inside a built-in component (e.g.
<>
) if (JSX.isBuiltInElement(node.parent)) { - context.report({ messageId: "noUselessFragmentInBuiltIn", node, fix }); + context.report({ + messageId: "uselessFragment", + node, + data: { + reason: "placed inside a built-in component", + }, + fix, + }); } // report and return if the fragment has no children (e.g. <>) if (node.children.length === 0) { - context.report({ messageId: "noUselessFragment", node, fix }); + context.report({ + messageId: "uselessFragment", + node, + data: { + reason: "contains less than two children", + }, + fix, + }); return; } const isChildElement = AST.isOneOf([T.JSXElement, T.JSXFragment])(node.parent); @@ -105,7 +117,14 @@ function checkAndReport( // <>hello, world case !allowExpressions && isChildElement: { - context.report({ messageId: "noUselessFragment", node, fix }); + context.report({ + messageId: "uselessFragment", + node, + data: { + reason: "contains less than two children", + }, + fix, + }); return; } case !allowExpressions @@ -113,7 +132,14 @@ function checkAndReport( && node.children.length === 1: { // const foo = <>{children}; // return <>{children}; - context.report({ messageId: "noUselessFragment", node, fix }); + context.report({ + messageId: "uselessFragment", + node, + data: { + reason: "contains less than two children", + }, + fix, + }); return; } } @@ -123,7 +149,14 @@ function checkAndReport( case nonPaddingChildren.length === 0: case nonPaddingChildren.length === 1 && firstNonPaddingChild?.type !== T.JSXExpressionContainer: { - context.report({ messageId: "noUselessFragment", node, fix }); + context.report({ + messageId: "uselessFragment", + node, + data: { + reason: "contains less than two children", + }, + fix, + }); return; } } @@ -135,12 +168,11 @@ export default createRule({ type: "problem", defaultOptions: [...defaultOptions], docs: { - description: "disallow unnecessary fragments", + description: "disallow useless fragments", }, fixable: "code", messages: { - noUselessFragment: "A fragment contains less than two children is unnecessary.", - noUselessFragmentInBuiltIn: "A fragment placed inside a built-in component is unnecessary.", + uselessFragment: "A fragment {{reason}} is useless.", }, schema: [{ type: "object",