From 40cdaaac1e5cf4591b7c72b45f71e3ed325241ac Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Fri, 18 Oct 2024 11:59:06 -0500 Subject: [PATCH 01/14] create exportOnly rule for no-multi-comp --- docs/rules/no-multi-comp.md | 4 + lib/rules/no-multi-comp.js | 75 +++++++++++++++++- tests/lib/rules/no-multi-comp.js | 126 +++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 4 deletions(-) diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index 00067c89d6..6741b189ef 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -69,6 +69,10 @@ class HelloJohn extends React.Component { module.exports = HelloJohn; ``` +### `exportOnly` + +When `true` the rule will ignore components which are not exported, which allows you to define components as long as they are only used within a private scope. + ## When Not To Use It If you prefer to declare multiple components per file you can disable this rule. diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index 8cf73c90bc..c6b9164686 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -17,6 +17,7 @@ const report = require('../util/report'); const messages = { onlyOneComponent: 'Declare only one React component per file', + onlyOneExportedComponent: 'Declare only one exported React component per file', }; /** @type {import('eslint').Rule.RuleModule} */ @@ -38,6 +39,10 @@ module.exports = { default: false, type: 'boolean', }, + exportOnly: { + default: false, + type: 'boolean', + }, }, additionalProperties: false, }], @@ -46,6 +51,43 @@ module.exports = { create: Components.detect((context, components, utils) => { const configuration = context.options[0] || {}; const ignoreStateless = configuration.ignoreStateless || false; + const exportOnly = configuration.exportOnly || false; + + const exportedComponents = new Set(); // Track exported components + const validIdentifiers = ['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']; + + /** + * Given an export declaration, find the export name. + * @param {Object} node + * @returns {string} + */ + function getExportedComponentName(node) { + if (node.declaration.type === 'ClassDeclaration') { + return node.declaration.id.name; + } + for (const declarator of node.declaration.declarations || []) { + const type = declarator.init.type; + if (validIdentifiers.find(type)) { + return declarator.id.name; + } + } + } + + /** + * Given a React component, find the exported name. + * @param {Object} component + * @returns {string} + */ + function findComponentIdentifierFromComponent(component) { + let name; + if (component.node.parent) { + name = component.node.parent.id.name; + } + if (!name) { + name = component.node.id.name; + } + return name; + } /** * Checks if the component is ignored @@ -61,7 +103,16 @@ module.exports = { ); } - return { + /** + * Checks if the component is exported, if exportOnly is set + * @param {Object} component The component being checked. + * @returns {boolean} True if the component is exported or exportOnly is false + */ + function isExported(component) { + return !exportOnly && exportedComponents.has(findComponentIdentifierFromComponent(component)); + } + + const rule = { 'Program:exit'() { if (components.length() <= 1) { return; @@ -69,13 +120,29 @@ module.exports = { values(components.list()) .filter((component) => !isIgnored(component)) + .filter((component) => isExported(component)) .slice(1) .forEach((component) => { - report(context, messages.onlyOneComponent, 'onlyOneComponent', { - node: component.node, - }); + report(context, + exportOnly ? messages.onlyOneExportedComponent : messages.onlyOneComponent, + exportOnly ? 'onlyOneExportedComponent' : 'onlyOneComponent', + { + node: component.node, + }); }); }, }; + + if (exportOnly) { + rule.ExportNamedDeclaration = (node) => { + exportedComponents.add(getExportedComponentName(node)); + }; + + rule.ExportDefaultDeclaration = (node) => { + exportedComponents.add(getExportedComponentName(node)); + }; + } + + return rule; }), }; diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index db6f09db2c..0f14b3ebe2 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -265,6 +265,69 @@ ruleTester.run('no-multi-comp', rule, { export default MenuList `, }, + { + code: ` + const componentOne = () => <>; + const componentTwo = () => <>; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + export const componentOne = () => <>; + const componentTwo = () => <>; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + const componentOne = () => <>; + const componentTwo = () => <>; + module.exports = { componentOne }; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + const componentOne = () => <>; + const componentTwo = () => <>; + export default componentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + function componentOne() { return <> }; + const componentTwo = () => <>; + export default componentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + function componentOne() { return <> }; + function componentTwo() { return <> }; + export default componentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React, {Component} from "react"; + export class componentOne extends Component() { render() { return <>; }}; + function componentTwo() { return <> }; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React, {Component} from "react"; + class componentOne extends Component() { render() { return <>; }}; + function componentTwo() { return <> }; + export default componentOne; + `, + options: [{ exportOnly: true }], + }, ]), invalid: parsers.all([ @@ -612,5 +675,68 @@ ruleTester.run('no-multi-comp', rule, { }, errors: [{ messageId: 'onlyOneComponent' }], }, + { + code: ` + export const componentOne = () => <>; + export const componentTwo = () => <>; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + const componentOne = () => <>; + const componentTwo = () => <>; + module.exports = { componentOne, componentTwo }; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + const componentOne = () => <>; + export const componentTwo = () => <>; + export default componentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + export function componentOne() { return <> }; + export const componentTwo = () => <>; + export default componentTwo; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + function componentOne() { return <> }; + export function componentTwo() { return <> }; + export default componentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React, {Component} from "react"; + export class componentOne extends Component() { render() { return <>; }}; + export function componentTwo() { return <> }; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React, {Component} from "react"; + class componentOne extends Component() { render() { return <>; }}; + export function componentTwo() { return <> }; + export default componentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, ]), }); From a4fb0a4af55d6e6d2773de1fdfea72da9c612179 Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Fri, 18 Oct 2024 12:18:55 -0500 Subject: [PATCH 02/14] flesh out tests and docs --- docs/rules/no-multi-comp.md | 45 ++++++ tests/lib/rules/no-multi-comp.js | 270 ++++++++++++++++++++++++++----- 2 files changed, 275 insertions(+), 40 deletions(-) diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index 6741b189ef..3f4e5c756e 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -73,6 +73,51 @@ module.exports = HelloJohn; When `true` the rule will ignore components which are not exported, which allows you to define components as long as they are only used within a private scope. +Examples of **correct** code for this rule: + +```jsx +export function Hello(props) { + return
Hello {props.name}
; +} +function HelloAgain(props) { + return
Hello again {props.name}
; +} +``` + +```jsx +function Hello(props) { + return
Hello {props.name}
; +} +class HelloJohn extends React.Component { + render() { + return ; + } +} +module.exports = HelloJohn; +``` + +Examples of **incorrect** code for this rule: + +```jsx +export function Hello(props) { + return
Hello {props.name}
; +} +export function HelloAgain(props) { + return
Hello again {props.name}
; +} +``` + +```jsx +function Hello(props) { + return
Hello {props.name}
; +} +function HelloAgain(props) { + return
Hello again {props.name}
; +} +module.exports = {Hello, HelloAgain} +``` + + ## When Not To Use It If you prefer to declare multiple components per file you can disable this rule. diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 0f14b3ebe2..09d2142fc2 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -267,64 +267,145 @@ ruleTester.run('no-multi-comp', rule, { }, { code: ` - const componentOne = () => <>; - const componentTwo = () => <>; + const ComponentOne = () => <>; + const ComponentTwo = () => <>; `, options: [{ exportOnly: true }], }, { code: ` - export const componentOne = () => <>; - const componentTwo = () => <>; + export const ComponentOne = () => <>; + const ComponentTwo = () => <>; `, options: [{ exportOnly: true }], }, { code: ` - const componentOne = () => <>; - const componentTwo = () => <>; - module.exports = { componentOne }; + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + module.exports = { ComponentOne }; `, options: [{ exportOnly: true }], }, { code: ` - const componentOne = () => <>; - const componentTwo = () => <>; - export default componentOne; + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + export default ComponentOne; `, options: [{ exportOnly: true }], }, { code: ` - function componentOne() { return <> }; - const componentTwo = () => <>; - export default componentOne; + function ComponentOne() { return <> }; + const ComponentTwo = () => <>; + export default ComponentOne; `, options: [{ exportOnly: true }], }, { code: ` - function componentOne() { return <> }; - function componentTwo() { return <> }; - export default componentOne; + function ComponentOne() { return <> }; + function ComponentTwo() { return <> }; + export default ComponentOne; `, options: [{ exportOnly: true }], }, { code: ` import React, {Component} from "react"; - export class componentOne extends Component() { render() { return <>; }}; - function componentTwo() { return <> }; + export class ComponentOne extends Component() { render() { return <>; }}; + function ComponentTwo() { return <> }; `, options: [{ exportOnly: true }], }, { code: ` import React, {Component} from "react"; - class componentOne extends Component() { render() { return <>; }}; - function componentTwo() { return <> }; - export default componentOne; + class ComponentOne extends Component() { render() { return <>; }}; + function ComponentTwo() { return <> }; + export default ComponentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + export { ComponentOne }; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + export function ComponentOne() { return <>; } + function ComponentTwo() { return <>; } + `, + options: [{ exportOnly: true }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + module.exports = ComponentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + export default function() { return ; } + `, + options: [{ exportOnly: true }], + }, + { + code: ` + function ComponentOne() { return <>; } + const ComponentTwo = () => <>; + export { ComponentOne as default }; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React from 'react'; + export default class ComponentOne extends React.Component { + render() { return <>; } + } + class ComponentTwo extends React.Component { + render() { return <>; } + } + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React from 'react'; + class ComponentOne extends React.Component { + render() { return <>; } + } + class ComponentTwo extends React.Component { + render() { return <>; } + } + export { ComponentOne }; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React, { memo } from 'react'; + const ComponentOne = memo(() => <>); + const ComponentTwo = () => <>; + export default ComponentOne; + `, + options: [{ exportOnly: true }], + }, + { + code: ` + import React from "react"; + export default function Component(props) { return
{props.children}
; } + function ComponentTwo(props) { return
{props.children}
; } `, options: [{ exportOnly: true }], }, @@ -677,44 +758,44 @@ ruleTester.run('no-multi-comp', rule, { }, { code: ` - export const componentOne = () => <>; - export const componentTwo = () => <>; + export const ComponentOne = () => <>; + export const ComponentTwo = () => <>; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { code: ` - const componentOne = () => <>; - const componentTwo = () => <>; - module.exports = { componentOne, componentTwo }; + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + module.exports = { ComponentOne, ComponentTwo }; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { code: ` - const componentOne = () => <>; - export const componentTwo = () => <>; - export default componentOne; + const ComponentOne = () => <>; + export const ComponentTwo = () => <>; + export default ComponentOne; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { code: ` - export function componentOne() { return <> }; - export const componentTwo = () => <>; - export default componentTwo; + export function ComponentOne() { return <> }; + export const ComponentTwo = () => <>; + export default ComponentTwo; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { code: ` - function componentOne() { return <> }; - export function componentTwo() { return <> }; - export default componentOne; + function ComponentOne() { return <> }; + export function ComponentTwo() { return <> }; + export default ComponentOne; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], @@ -722,8 +803,8 @@ ruleTester.run('no-multi-comp', rule, { { code: ` import React, {Component} from "react"; - export class componentOne extends Component() { render() { return <>; }}; - export function componentTwo() { return <> }; + export class ComponentOne extends Component() { render() { return <>; }}; + export function ComponentTwo() { return <> }; `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], @@ -731,9 +812,118 @@ ruleTester.run('no-multi-comp', rule, { { code: ` import React, {Component} from "react"; - class componentOne extends Component() { render() { return <>; }}; - export function componentTwo() { return <> }; - export default componentOne; + class ComponentOne extends Component() { render() { return <>; }}; + export function ComponentTwo() { return <> }; + export default ComponentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React, {Component} from "react"; + class ComponentOne extends Component() { render() { return <>; }}; + function ComponentTwo() { return <> }; + export default ComponentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + export { ComponentOne }; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + export function ComponentOne() { return <>; } + function ComponentTwo() { return <>; } + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + module.exports = ComponentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + const ComponentOne = () => <>; + const ComponentTwo = () => <>; + export default function() { return ; } + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + function ComponentOne() { return <>; } + const ComponentTwo = () => <>; + export { ComponentOne as default }; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React from 'react'; + export default class ComponentOne extends React.Component { + render() { return <>; } + } + class ComponentTwo extends React.Component { + render() { return <>; } + } + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React from 'react'; + class ComponentOne extends React.Component { + render() { return <>; } + } + class ComponentTwo extends React.Component { + render() { return <>; } + } + export { ComponentOne }; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React, { memo } from 'react'; + const ComponentOne = memo(() => <>); + const ComponentTwo = () => <>; + export default ComponentOne; + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React from "react"; + export default function Component(props) { return
{props.children}
; } + export function ComponentTwo(props) { return
{props.children}
; } + `, + options: [{ exportOnly: true }], + errors: [{ messageId: 'onlyOneExportedComponent' }], + }, + { + code: ` + import React from "react"; + export function componentOne(props) { return
{props.children}
; } + export function ComponentOne(props) { return
{props.children}
; } `, options: [{ exportOnly: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], From da04e1711ee50d2d6e2bcf33986436a9e148b21b Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Fri, 18 Oct 2024 12:29:25 -0500 Subject: [PATCH 03/14] logic fixes --- lib/rules/no-multi-comp.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index c6b9164686..6f94754420 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -54,7 +54,7 @@ module.exports = { const exportOnly = configuration.exportOnly || false; const exportedComponents = new Set(); // Track exported components - const validIdentifiers = ['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']; + const validIdentifiers = new Set(['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']); /** * Given an export declaration, find the export name. @@ -67,7 +67,7 @@ module.exports = { } for (const declarator of node.declaration.declarations || []) { const type = declarator.init.type; - if (validIdentifiers.find(type)) { + if (validIdentifiers.has(type)) { return declarator.id.name; } } @@ -80,7 +80,7 @@ module.exports = { */ function findComponentIdentifierFromComponent(component) { let name; - if (component.node.parent) { + if (component.node.parent.id) { name = component.node.parent.id.name; } if (!name) { From 23c5e346bcb6c200b25cb096fffd6602090cf67c Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Fri, 18 Oct 2024 12:33:11 -0500 Subject: [PATCH 04/14] silly logic oversight --- lib/rules/no-multi-comp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index 6f94754420..70e9001106 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -109,7 +109,7 @@ module.exports = { * @returns {boolean} True if the component is exported or exportOnly is false */ function isExported(component) { - return !exportOnly && exportedComponents.has(findComponentIdentifierFromComponent(component)); + return !exportOnly || exportedComponents.has(findComponentIdentifierFromComponent(component)); } const rule = { From 27d8075413e7ebd9f369ff97e3d7714676dec8d5 Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Fri, 18 Oct 2024 12:46:31 -0500 Subject: [PATCH 05/14] fix docs, change name to ignorePrivate --- docs/rules/no-multi-comp.md | 6 +-- lib/rules/no-multi-comp.js | 16 ++++---- tests/lib/rules/no-multi-comp.js | 70 ++++++++++++++++---------------- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index 3f4e5c756e..43a9354d7d 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -69,9 +69,10 @@ class HelloJohn extends React.Component { module.exports = HelloJohn; ``` -### `exportOnly` +### `ignorePrivate` -When `true` the rule will ignore components which are not exported, which allows you to define components as long as they are only used within a private scope. +When `true` the rule will ignore components which are not exported, which allows you to define components that are consumed only within the same file. +This ensures there is only one entry point for a React component without limiting the structural content of the file. Examples of **correct** code for this rule: @@ -117,7 +118,6 @@ function HelloAgain(props) { module.exports = {Hello, HelloAgain} ``` - ## When Not To Use It If you prefer to declare multiple components per file you can disable this rule. diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index 70e9001106..c580a57640 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -39,7 +39,7 @@ module.exports = { default: false, type: 'boolean', }, - exportOnly: { + ignorePrivate: { default: false, type: 'boolean', }, @@ -51,7 +51,7 @@ module.exports = { create: Components.detect((context, components, utils) => { const configuration = context.options[0] || {}; const ignoreStateless = configuration.ignoreStateless || false; - const exportOnly = configuration.exportOnly || false; + const ignorePrivate = configuration.ignorePrivate || false; const exportedComponents = new Set(); // Track exported components const validIdentifiers = new Set(['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']); @@ -108,8 +108,8 @@ module.exports = { * @param {Object} component The component being checked. * @returns {boolean} True if the component is exported or exportOnly is false */ - function isExported(component) { - return !exportOnly || exportedComponents.has(findComponentIdentifierFromComponent(component)); + function isPrivate(component) { + return ignorePrivate && exportedComponents.has(findComponentIdentifierFromComponent(component)); } const rule = { @@ -120,12 +120,12 @@ module.exports = { values(components.list()) .filter((component) => !isIgnored(component)) - .filter((component) => isExported(component)) + .filter((component) => !isPrivate(component)) .slice(1) .forEach((component) => { report(context, - exportOnly ? messages.onlyOneExportedComponent : messages.onlyOneComponent, - exportOnly ? 'onlyOneExportedComponent' : 'onlyOneComponent', + ignorePrivate ? messages.onlyOneExportedComponent : messages.onlyOneComponent, + ignorePrivate ? 'onlyOneExportedComponent' : 'onlyOneComponent', { node: component.node, }); @@ -133,7 +133,7 @@ module.exports = { }, }; - if (exportOnly) { + if (ignorePrivate) { rule.ExportNamedDeclaration = (node) => { exportedComponents.add(getExportedComponentName(node)); }; diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 09d2142fc2..2a8b2611d7 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -270,14 +270,14 @@ ruleTester.run('no-multi-comp', rule, { const ComponentOne = () => <>; const ComponentTwo = () => <>; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` export const ComponentOne = () => <>; const ComponentTwo = () => <>; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -285,7 +285,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = { ComponentOne }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -293,7 +293,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -301,7 +301,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -309,7 +309,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -317,7 +317,7 @@ ruleTester.run('no-multi-comp', rule, { export class ComponentOne extends Component() { render() { return <>; }}; function ComponentTwo() { return <> }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -326,7 +326,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -334,14 +334,14 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` export function ComponentOne() { return <>; } function ComponentTwo() { return <>; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -349,7 +349,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -357,7 +357,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default function() { return ; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -365,7 +365,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne as default }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -377,7 +377,7 @@ ruleTester.run('no-multi-comp', rule, { render() { return <>; } } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -390,7 +390,7 @@ ruleTester.run('no-multi-comp', rule, { } export { ComponentOne }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -399,7 +399,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, { code: ` @@ -407,7 +407,7 @@ ruleTester.run('no-multi-comp', rule, { export default function Component(props) { return
{props.children}
; } function ComponentTwo(props) { return
{props.children}
; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], }, ]), @@ -761,7 +761,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentOne = () => <>; export const ComponentTwo = () => <>; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -770,7 +770,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = { ComponentOne, ComponentTwo }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -779,7 +779,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -788,7 +788,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentTwo = () => <>; export default ComponentTwo; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -797,7 +797,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -806,7 +806,7 @@ ruleTester.run('no-multi-comp', rule, { export class ComponentOne extends Component() { render() { return <>; }}; export function ComponentTwo() { return <> }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -816,7 +816,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -826,7 +826,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -835,7 +835,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -843,7 +843,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentOne() { return <>; } function ComponentTwo() { return <>; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -852,7 +852,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -861,7 +861,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default function() { return ; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -870,7 +870,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne as default }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -883,7 +883,7 @@ ruleTester.run('no-multi-comp', rule, { render() { return <>; } } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -897,7 +897,7 @@ ruleTester.run('no-multi-comp', rule, { } export { ComponentOne }; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -907,7 +907,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -916,7 +916,7 @@ ruleTester.run('no-multi-comp', rule, { export default function Component(props) { return
{props.children}
; } export function ComponentTwo(props) { return
{props.children}
; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -925,7 +925,7 @@ ruleTester.run('no-multi-comp', rule, { export function componentOne(props) { return
{props.children}
; } export function ComponentOne(props) { return
{props.children}
; } `, - options: [{ exportOnly: true }], + options: [{ ignorePrivate: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, ]), From 9c03d568fd39ecf0c17a204e091f29e9500c18bd Mon Sep 17 00:00:00 2001 From: Victor Willyams Date: Mon, 21 Oct 2024 11:57:31 -0500 Subject: [PATCH 06/14] Update lib/rules/no-multi-comp.js Co-authored-by: Jordan Harband --- lib/rules/no-multi-comp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index c580a57640..fe742dc23f 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -120,7 +120,7 @@ module.exports = { values(components.list()) .filter((component) => !isIgnored(component)) - .filter((component) => !isPrivate(component)) + .filter((component) => !isIgnored(component) && !isPrivate(component)) .slice(1) .forEach((component) => { report(context, From 34d2e33276f306f040d5c35630a79c4bb26956e6 Mon Sep 17 00:00:00 2001 From: Victor Willyams Date: Mon, 21 Oct 2024 11:57:40 -0500 Subject: [PATCH 07/14] Update lib/rules/no-multi-comp.js Co-authored-by: Jordan Harband --- lib/rules/no-multi-comp.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index fe742dc23f..e0ede2ead3 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -65,9 +65,9 @@ module.exports = { if (node.declaration.type === 'ClassDeclaration') { return node.declaration.id.name; } - for (const declarator of node.declaration.declarations || []) { - const type = declarator.init.type; - if (validIdentifiers.has(type)) { + if (node.declaration.declarations) { + const declarator = node.declaration.declarations.find((declarator) => validIdentifiers.has(declarator.init.type)); + if (declarator) { return declarator.id.name; } } From 82b4da78f8927655c02d93c09f37af2bb4fe012b Mon Sep 17 00:00:00 2001 From: Victor Willyams Date: Mon, 21 Oct 2024 11:57:46 -0500 Subject: [PATCH 08/14] Update docs/rules/no-multi-comp.md Co-authored-by: Jordan Harband --- docs/rules/no-multi-comp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index 43a9354d7d..fb61677f87 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -115,7 +115,7 @@ function Hello(props) { function HelloAgain(props) { return
Hello again {props.name}
; } -module.exports = {Hello, HelloAgain} +module.exports = { Hello, HelloAgain }; ``` ## When Not To Use It From 66559508ddb3040417f7ceccf4c8e8beb862435b Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 11:44:04 -0500 Subject: [PATCH 09/14] clean up github commit suggestions --- lib/rules/no-multi-comp.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index e0ede2ead3..8da20e02b5 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -66,7 +66,7 @@ module.exports = { return node.declaration.id.name; } if (node.declaration.declarations) { - const declarator = node.declaration.declarations.find((declarator) => validIdentifiers.has(declarator.init.type)); + const declarator = node.declaration.declarations.find((declaration) => validIdentifiers.has(declaration.init.type)); if (declarator) { return declarator.id.name; } @@ -119,7 +119,6 @@ module.exports = { } values(components.list()) - .filter((component) => !isIgnored(component)) .filter((component) => !isIgnored(component) && !isPrivate(component)) .slice(1) .forEach((component) => { From b420cb525d350594db32a28068e8ca179aff7d34 Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 11:47:15 -0500 Subject: [PATCH 10/14] PR suggested changes --- docs/rules/no-multi-comp.md | 2 +- lib/rules/no-multi-comp.js | 27 ++++++------ tests/lib/rules/no-multi-comp.js | 70 ++++++++++++++++---------------- 3 files changed, 50 insertions(+), 49 deletions(-) diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index fb61677f87..1e38c10a10 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -69,7 +69,7 @@ class HelloJohn extends React.Component { module.exports = HelloJohn; ``` -### `ignorePrivate` +### `ignoreInternal` When `true` the rule will ignore components which are not exported, which allows you to define components that are consumed only within the same file. This ensures there is only one entry point for a React component without limiting the structural content of the file. diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index 8da20e02b5..b07251d465 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -39,7 +39,7 @@ module.exports = { default: false, type: 'boolean', }, - ignorePrivate: { + ignoreInternal: { default: false, type: 'boolean', }, @@ -51,7 +51,7 @@ module.exports = { create: Components.detect((context, components, utils) => { const configuration = context.options[0] || {}; const ignoreStateless = configuration.ignoreStateless || false; - const ignorePrivate = configuration.ignorePrivate || false; + const ignoreInternal = configuration.ignoreInternal || false; const exportedComponents = new Set(); // Track exported components const validIdentifiers = new Set(['ArrowFunctionExpression', 'Identifier', 'FunctionExpression']); @@ -109,7 +109,7 @@ module.exports = { * @returns {boolean} True if the component is exported or exportOnly is false */ function isPrivate(component) { - return ignorePrivate && exportedComponents.has(findComponentIdentifierFromComponent(component)); + return ignoreInternal && exportedComponents.has(findComponentIdentifierFromComponent(component)); } const rule = { @@ -123,8 +123,8 @@ module.exports = { .slice(1) .forEach((component) => { report(context, - ignorePrivate ? messages.onlyOneExportedComponent : messages.onlyOneComponent, - ignorePrivate ? 'onlyOneExportedComponent' : 'onlyOneComponent', + ignoreInternal ? messages.onlyOneExportedComponent : messages.onlyOneComponent, + ignoreInternal ? 'onlyOneExportedComponent' : 'onlyOneComponent', { node: component.node, }); @@ -132,14 +132,15 @@ module.exports = { }, }; - if (ignorePrivate) { - rule.ExportNamedDeclaration = (node) => { - exportedComponents.add(getExportedComponentName(node)); - }; - - rule.ExportDefaultDeclaration = (node) => { - exportedComponents.add(getExportedComponentName(node)); - }; + if (ignoreInternal) { + Object.assign(rule, { + ExportNamedDeclaration(node) { + exportedComponents.add(getExportedComponentName(node)); + }, + ExportDefaultDeclaration(node) { + exportedComponents.add(getExportedComponentName(node)); + }, + }); } return rule; diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 2a8b2611d7..b39e2a07ff 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -270,14 +270,14 @@ ruleTester.run('no-multi-comp', rule, { const ComponentOne = () => <>; const ComponentTwo = () => <>; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` export const ComponentOne = () => <>; const ComponentTwo = () => <>; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -285,7 +285,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = { ComponentOne }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -293,7 +293,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -301,7 +301,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -309,7 +309,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -317,7 +317,7 @@ ruleTester.run('no-multi-comp', rule, { export class ComponentOne extends Component() { render() { return <>; }}; function ComponentTwo() { return <> }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -326,7 +326,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -334,14 +334,14 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` export function ComponentOne() { return <>; } function ComponentTwo() { return <>; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -349,7 +349,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -357,7 +357,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default function() { return ; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -365,7 +365,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne as default }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -377,7 +377,7 @@ ruleTester.run('no-multi-comp', rule, { render() { return <>; } } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -390,7 +390,7 @@ ruleTester.run('no-multi-comp', rule, { } export { ComponentOne }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -399,7 +399,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, { code: ` @@ -407,7 +407,7 @@ ruleTester.run('no-multi-comp', rule, { export default function Component(props) { return
{props.children}
; } function ComponentTwo(props) { return
{props.children}
; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], }, ]), @@ -761,7 +761,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentOne = () => <>; export const ComponentTwo = () => <>; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -770,7 +770,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = { ComponentOne, ComponentTwo }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -779,7 +779,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -788,7 +788,7 @@ ruleTester.run('no-multi-comp', rule, { export const ComponentTwo = () => <>; export default ComponentTwo; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -797,7 +797,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -806,7 +806,7 @@ ruleTester.run('no-multi-comp', rule, { export class ComponentOne extends Component() { render() { return <>; }}; export function ComponentTwo() { return <> }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -816,7 +816,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -826,7 +826,7 @@ ruleTester.run('no-multi-comp', rule, { function ComponentTwo() { return <> }; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -835,7 +835,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -843,7 +843,7 @@ ruleTester.run('no-multi-comp', rule, { export function ComponentOne() { return <>; } function ComponentTwo() { return <>; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -852,7 +852,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; module.exports = ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -861,7 +861,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default function() { return ; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -870,7 +870,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export { ComponentOne as default }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -883,7 +883,7 @@ ruleTester.run('no-multi-comp', rule, { render() { return <>; } } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -897,7 +897,7 @@ ruleTester.run('no-multi-comp', rule, { } export { ComponentOne }; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -907,7 +907,7 @@ ruleTester.run('no-multi-comp', rule, { const ComponentTwo = () => <>; export default ComponentOne; `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -916,7 +916,7 @@ ruleTester.run('no-multi-comp', rule, { export default function Component(props) { return
{props.children}
; } export function ComponentTwo(props) { return
{props.children}
; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, { @@ -925,7 +925,7 @@ ruleTester.run('no-multi-comp', rule, { export function componentOne(props) { return
{props.children}
; } export function ComponentOne(props) { return
{props.children}
; } `, - options: [{ ignorePrivate: true }], + options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], }, ]), From cf6a63f478bb4312836a9d2007e7acdada1bb538 Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 12:57:31 -0500 Subject: [PATCH 11/14] major rewrite of test code, fixed a couple of missing cases. awaiting more testing. --- lib/rules/no-multi-comp.js | 5 +- tests/lib/rules/no-multi-comp.js | 454 ++++++++++--------------------- 2 files changed, 152 insertions(+), 307 deletions(-) diff --git a/lib/rules/no-multi-comp.js b/lib/rules/no-multi-comp.js index b07251d465..2124128875 100644 --- a/lib/rules/no-multi-comp.js +++ b/lib/rules/no-multi-comp.js @@ -65,6 +65,9 @@ module.exports = { if (node.declaration.type === 'ClassDeclaration') { return node.declaration.id.name; } + if (node.declaration.type === 'Identifier') { + return node.declaration.name; + } if (node.declaration.declarations) { const declarator = node.declaration.declarations.find((declaration) => validIdentifiers.has(declaration.init.type)); if (declarator) { @@ -109,7 +112,7 @@ module.exports = { * @returns {boolean} True if the component is exported or exportOnly is false */ function isPrivate(component) { - return ignoreInternal && exportedComponents.has(findComponentIdentifierFromComponent(component)); + return ignoreInternal && !exportedComponents.has(findComponentIdentifierFromComponent(component)); } const rule = { diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index b39e2a07ff..491c595730 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -22,6 +22,143 @@ const parserOptions = { }, }; +// ------------------------------------------------------------------------------ +// Combinatorial test generation for ignoreInvalid +// ------------------------------------------------------------------------------ +// eslint-disable-next-line valid-jsdoc +/** + * @typedef {Function} ComponentGenerator + * @param {string} name - The name of the component to be generated. + * @returns {string} - The component declaration. + */ + +// eslint-disable-next-line valid-jsdoc +/** + * @type {ComponentGenerator[]} Array of different ways to output valid code for instantiating a React component with the given name. + */ +const COMPONENT_TYPES = [ + (name) => `const ${name} = () => <>;`, // arrow function component + (name) => `let ${name} = () => <>;`, // arrow function component (with let) + (name) => `var ${name} = () => <>;`, // arrow function component (with var) + (name) => `function ${name}() { return <>; }`, // standard function component + (name) => `class ${name} extends React.Component { + render() { return <>; } + }`, // class component + (name) => `const ${name} = memo(() => <>);`, // memoized anonymous component + (name) => `const ${name} = async () => <>;`, // async component (react server components) +]; + +/** + * Helper function for combinatorial testing of no-multi-comp ignoreInternal rule. + * + * @typedef {Function} ComponentExportGenerator + * @param {ComponentGenerator} compOne - Generator function for the first component to export. + * @param {string} compOneName - The name of the first component, which will typically be exported. + * @param {ComponentGenerator} compTwo - Generator function for the second component to export. + * @param {string} compTwoName - The name of the second component. This will be exported in invalid scenarios. + * @param {string} exportRename - A potential rename of the export for the first component, used by some scenarios. + * @returns {string} - A (nearly) complete test case - although we also prepend a generic import string. + */ + +// eslint-disable-next-line valid-jsdoc +/** + * @type {ComponentExportGenerator[]} + */ +const EXPORT_TYPES_VALID = [ + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)}`, // no export at all + // DECLARATION TIME EXPORTS + (compOne, compOneName, compTwo, compTwoName) => ` + export ${compOne(compOneName)} + ${compTwo(compTwoName)}`, // standard export + (compOne, compOneName, compTwo, compTwoName) => ` + export default ${compOne(compOneName)} + ${compTwo(compTwoName)}`, // default export + (compOne, compOneName, compTwo, compTwoName, exportRename) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + module.exports = { ${compOneName} as ${exportRename} }`, // module export with rename, post declaration + // nb: module export at declaration time will be handled separately + // POST DECLARATION EXPORTS + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export default ${compOneName}`, // default export, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export ${compOneName}`, // export, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + module.exports = { ${compOneName} }`, // module export, post declaration + (compOne, compOneName, compTwo, compTwoName, exportRename) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + module.exports = { ${compOneName} as ${exportRename} }`, // module export with rename, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export default function() { return <${compOneName} />; }`, // exporting component with indirection +]; + +// eslint-disable-next-line valid-jsdoc +/** + * @type {ComponentExportGenerator[]} + */ +const EXPORT_TYPES_INVALID = [ + // DECLARATION TIME EXPORTS + (compOne, compOneName, compTwo, compTwoName) => ` + export ${compOne(compOneName)} + export ${compTwo(compTwoName)}`, // standard export + (compOne, compOneName, compTwo, compTwoName) => ` + export default ${compOne(compOneName)} + export ${compTwo(compTwoName)}`, // default export + // nb: module export at declaration time will be handled separately + // POST DECLARATION EXPORTS + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export default ${compOneName} + export ${compTwoName}`, // default export, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export ${compOneName} + export ${compTwoName}`, // export, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + module.exports = { ${compOneName} ${compTwoName} }`, // module export, post declaration +]; + +/** + * @param {ComponentExportGenerator[]} scenarioArray array of scenario generator functions which we will now convert into strings + * @param {boolean} [invalid] whether generated scenarios should expect to fail + * @returns {{code: string, options: object[], errors: object[]}[]} + */ +const generateScenarios = (scenarioArray, invalid = false) => { + const result = []; + for (const scenario of scenarioArray) { + for (const first of COMPONENT_TYPES) { + for (const second of COMPONENT_TYPES) { + result.push({ + code: ` + import React, { memo, Component } from 'react'; + ${scenario(first, 'ComponentOne', second, 'ComponentTwo', 'RenamedComponent')}`, + options: [{ ignoreInternal: true }], + errors: invalid ? [{ messageId: 'onlyOneExportedComponent' }] : undefined, + }); + } + } + } + return result; +}; + +const ignoreInternalValidScenarios = generateScenarios(EXPORT_TYPES_VALID); +const ignoreInternalInvalidScenarios = generateScenarios(EXPORT_TYPES_INVALID); + // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ @@ -265,147 +402,13 @@ ruleTester.run('no-multi-comp', rule, { export default MenuList `, }, - { + ...ignoreInternalValidScenarios, + { // special case: components declared inside of module export block code: ` const ComponentOne = () => <>; - const ComponentTwo = () => <>; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - export const ComponentOne = () => <>; - const ComponentTwo = () => <>; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - module.exports = { ComponentOne }; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - function ComponentOne() { return <> }; - const ComponentTwo = () => <>; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - function ComponentOne() { return <> }; - function ComponentTwo() { return <> }; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React, {Component} from "react"; - export class ComponentOne extends Component() { render() { return <>; }}; - function ComponentTwo() { return <> }; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React, {Component} from "react"; - class ComponentOne extends Component() { render() { return <>; }}; - function ComponentTwo() { return <> }; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - export { ComponentOne }; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - export function ComponentOne() { return <>; } - function ComponentTwo() { return <>; } - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - module.exports = ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - export default function() { return ; } - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - function ComponentOne() { return <>; } - const ComponentTwo = () => <>; - export { ComponentOne as default }; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React from 'react'; - export default class ComponentOne extends React.Component { - render() { return <>; } - } - class ComponentTwo extends React.Component { - render() { return <>; } - } - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React from 'react'; - class ComponentOne extends React.Component { - render() { return <>; } - } - class ComponentTwo extends React.Component { - render() { return <>; } - } - export { ComponentOne }; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React, { memo } from 'react'; - const ComponentOne = memo(() => <>); - const ComponentTwo = () => <>; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - }, - { - code: ` - import React from "react"; - export default function Component(props) { return
{props.children}
; } - function ComponentTwo(props) { return
{props.children}
; } + module.exports = { + ComponentTwo() { return <>; } + }; `, options: [{ ignoreInternal: true }], }, @@ -756,174 +759,13 @@ ruleTester.run('no-multi-comp', rule, { }, errors: [{ messageId: 'onlyOneComponent' }], }, - { - code: ` - export const ComponentOne = () => <>; - export const ComponentTwo = () => <>; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - module.exports = { ComponentOne, ComponentTwo }; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - const ComponentOne = () => <>; - export const ComponentTwo = () => <>; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - export function ComponentOne() { return <> }; - export const ComponentTwo = () => <>; - export default ComponentTwo; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - function ComponentOne() { return <> }; - export function ComponentTwo() { return <> }; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React, {Component} from "react"; - export class ComponentOne extends Component() { render() { return <>; }}; - export function ComponentTwo() { return <> }; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React, {Component} from "react"; - class ComponentOne extends Component() { render() { return <>; }}; - export function ComponentTwo() { return <> }; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React, {Component} from "react"; - class ComponentOne extends Component() { render() { return <>; }}; - function ComponentTwo() { return <> }; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - export { ComponentOne }; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { + ...ignoreInternalInvalidScenarios, + { // special case: components declared inside of module export block code: ` - export function ComponentOne() { return <>; } - function ComponentTwo() { return <>; } - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - module.exports = ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - const ComponentOne = () => <>; - const ComponentTwo = () => <>; - export default function() { return ; } - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - function ComponentOne() { return <>; } - const ComponentTwo = () => <>; - export { ComponentOne as default }; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React from 'react'; - export default class ComponentOne extends React.Component { - render() { return <>; } - } - class ComponentTwo extends React.Component { - render() { return <>; } - } - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React from 'react'; - class ComponentOne extends React.Component { - render() { return <>; } - } - class ComponentTwo extends React.Component { - render() { return <>; } - } - export { ComponentOne }; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React, { memo } from 'react'; - const ComponentOne = memo(() => <>); - const ComponentTwo = () => <>; - export default ComponentOne; - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React from "react"; - export default function Component(props) { return
{props.children}
; } - export function ComponentTwo(props) { return
{props.children}
; } - `, - options: [{ ignoreInternal: true }], - errors: [{ messageId: 'onlyOneExportedComponent' }], - }, - { - code: ` - import React from "react"; - export function componentOne(props) { return
{props.children}
; } - export function ComponentOne(props) { return
{props.children}
; } + module.exports = { + ComponentOne() { return <>; } + ComponentTwo() { return <>; } + }; `, options: [{ ignoreInternal: true }], errors: [{ messageId: 'onlyOneExportedComponent' }], From cb7e496e359f2b388feeeeb919437af4e0a2bf0c Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 13:10:36 -0500 Subject: [PATCH 12/14] hopefully resolve syntax issues with combinatorial testing --- tests/lib/rules/no-multi-comp.js | 79 ++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 491c595730..5b7e87e412 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -36,16 +36,23 @@ const parserOptions = { /** * @type {ComponentGenerator[]} Array of different ways to output valid code for instantiating a React component with the given name. */ -const COMPONENT_TYPES = [ +const SIMPLE_COMPONENT_TYPES = [ (name) => `const ${name} = () => <>;`, // arrow function component (name) => `let ${name} = () => <>;`, // arrow function component (with let) (name) => `var ${name} = () => <>;`, // arrow function component (with var) + (name) => `const ${name} = memo(() => <>);`, // memoized anonymous component + (name) => `const ${name} = async () => <>;`, // async component (react server components) +]; + +// eslint-disable-next-line valid-jsdoc +/** + * @type {ComponentGenerator[]} Array of different ways to output valid code for instantiating a React component with the given name. + */ +const COMPLEX_COMPONENT_TYPES = [ (name) => `function ${name}() { return <>; }`, // standard function component (name) => `class ${name} extends React.Component { render() { return <>; } }`, // class component - (name) => `const ${name} = memo(() => <>);`, // memoized anonymous component - (name) => `const ${name} = async () => <>;`, // async component (react server components) ]; /** @@ -72,9 +79,6 @@ const EXPORT_TYPES_VALID = [ (compOne, compOneName, compTwo, compTwoName) => ` export ${compOne(compOneName)} ${compTwo(compTwoName)}`, // standard export - (compOne, compOneName, compTwo, compTwoName) => ` - export default ${compOne(compOneName)} - ${compTwo(compTwoName)}`, // default export (compOne, compOneName, compTwo, compTwoName, exportRename) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} @@ -103,6 +107,18 @@ const EXPORT_TYPES_VALID = [ export default function() { return <${compOneName} />; }`, // exporting component with indirection ]; +// eslint-disable-next-line valid-jsdoc +/** + * Special case: inline `export default` syntax cannot be followed by `const, let, var` + * + * @type {ComponentExportGenerator[]} + */ +const EXPORT_TYPES_VALID_COMPLEX = [ + (compOne, compOneName, compTwo, compTwoName) => ` + export default ${compOne(compOneName)} + ${compTwo(compTwoName)}`, // default export +]; + // eslint-disable-next-line valid-jsdoc /** * @type {ComponentExportGenerator[]} @@ -112,9 +128,6 @@ const EXPORT_TYPES_INVALID = [ (compOne, compOneName, compTwo, compTwoName) => ` export ${compOne(compOneName)} export ${compTwo(compTwoName)}`, // standard export - (compOne, compOneName, compTwo, compTwoName) => ` - export default ${compOne(compOneName)} - export ${compTwo(compTwoName)}`, // default export // nb: module export at declaration time will be handled separately // POST DECLARATION EXPORTS (compOne, compOneName, compTwo, compTwoName) => ` @@ -133,16 +146,28 @@ const EXPORT_TYPES_INVALID = [ module.exports = { ${compOneName} ${compTwoName} }`, // module export, post declaration ]; +// eslint-disable-next-line valid-jsdoc +/** + * @type {ComponentExportGenerator[]} + */ +const EXPORT_TYPES_INVALID_COMPLEX = [ + // DECLARATION TIME EXPORTS + (compOne, compOneName, compTwo, compTwoName) => ` + export default ${compOne(compOneName)} + export ${compTwo(compTwoName)}`, // default export +]; + /** * @param {ComponentExportGenerator[]} scenarioArray array of scenario generator functions which we will now convert into strings + * @param {ComponentGenerator[]} componentTypes array of components to generate on * @param {boolean} [invalid] whether generated scenarios should expect to fail * @returns {{code: string, options: object[], errors: object[]}[]} */ -const generateScenarios = (scenarioArray, invalid = false) => { +const generateScenarios = (scenarioArray, componentTypes, invalid = false) => { const result = []; for (const scenario of scenarioArray) { - for (const first of COMPONENT_TYPES) { - for (const second of COMPONENT_TYPES) { + for (const first of componentTypes) { + for (const second of SIMPLE_COMPONENT_TYPES) { result.push({ code: ` import React, { memo, Component } from 'react'; @@ -156,8 +181,14 @@ const generateScenarios = (scenarioArray, invalid = false) => { return result; }; -const ignoreInternalValidScenarios = generateScenarios(EXPORT_TYPES_VALID); -const ignoreInternalInvalidScenarios = generateScenarios(EXPORT_TYPES_INVALID); +const ignoreInternalValidScenarios = [ + ...generateScenarios(EXPORT_TYPES_VALID, [...SIMPLE_COMPONENT_TYPES, ...COMPLEX_COMPONENT_TYPES]), + ...generateScenarios(EXPORT_TYPES_VALID_COMPLEX, COMPLEX_COMPONENT_TYPES), +]; +const ignoreInternalInvalidScenarios = [ + ...generateScenarios(EXPORT_TYPES_INVALID, [...SIMPLE_COMPONENT_TYPES, ...COMPLEX_COMPONENT_TYPES]), + ...generateScenarios(EXPORT_TYPES_INVALID_COMPLEX, COMPLEX_COMPONENT_TYPES), +]; // ------------------------------------------------------------------------------ // Tests @@ -412,6 +443,26 @@ ruleTester.run('no-multi-comp', rule, { `, options: [{ ignoreInternal: true }], }, + { // basic testing for intersection of ignoreStateless and ignoreInternal + code: ` + export function Hello(props) { + return
Hello {props.name}
; + } + export function HelloAgain(props) { + return
Hello again {props.name}
; + } + `, + options: [{ ignoreStateless: true, ignoreInternal: true }], + }, + { + code: ` + export const HelloComponent = (props) => { + return
; + } + export default React.memo((props, ref) => ); + `, + options: [{ ignoreStateless: true, ignoreInternal: true }], + }, ]), invalid: parsers.all([ From e852af080d3356458497932cfde34fc6b41d9613 Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 13:35:16 -0500 Subject: [PATCH 13/14] fix syntax --- tests/lib/rules/no-multi-comp.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 5b7e87e412..391fc0ee93 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -82,7 +82,7 @@ const EXPORT_TYPES_VALID = [ (compOne, compOneName, compTwo, compTwoName, exportRename) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} - module.exports = { ${compOneName} as ${exportRename} }`, // module export with rename, post declaration + module.exports = { ${exportRename} : ${compOneName} }`, // module export with rename, post declaration // nb: module export at declaration time will be handled separately // POST DECLARATION EXPORTS (compOne, compOneName, compTwo, compTwoName) => ` @@ -100,7 +100,7 @@ const EXPORT_TYPES_VALID = [ (compOne, compOneName, compTwo, compTwoName, exportRename) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} - module.exports = { ${compOneName} as ${exportRename} }`, // module export with rename, post declaration + module.exports = { ${exportRename} : ${compOneName} }`, // module export with rename, post declaration (compOne, compOneName, compTwo, compTwoName) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} From 82ea0aa761e9e719b0850c1d85c251d9393e403e Mon Sep 17 00:00:00 2001 From: Vic Willyams Date: Wed, 23 Oct 2024 16:24:10 -0500 Subject: [PATCH 14/14] more syntax goofing, should really figure out local test runs --- tests/lib/rules/no-multi-comp.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/lib/rules/no-multi-comp.js b/tests/lib/rules/no-multi-comp.js index 391fc0ee93..bdc5512746 100644 --- a/tests/lib/rules/no-multi-comp.js +++ b/tests/lib/rules/no-multi-comp.js @@ -92,7 +92,7 @@ const EXPORT_TYPES_VALID = [ (compOne, compOneName, compTwo, compTwoName) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} - export ${compOneName}`, // export, post declaration + export { ${compOneName} }`, // export, post declaration (compOne, compOneName, compTwo, compTwoName) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} @@ -134,12 +134,16 @@ const EXPORT_TYPES_INVALID = [ ${compOne(compOneName)} ${compTwo(compTwoName)} export default ${compOneName} - export ${compTwoName}`, // default export, post declaration + export { ${compTwoName} }`, // default export, post declaration (compOne, compOneName, compTwo, compTwoName) => ` ${compOne(compOneName)} ${compTwo(compTwoName)} - export ${compOneName} - export ${compTwoName}`, // export, post declaration + export { ${compOneName} } + export { ${compTwoName} }`, // export, post declaration + (compOne, compOneName, compTwo, compTwoName) => ` + ${compOne(compOneName)} + ${compTwo(compTwoName)} + export { ${compOneName}, ${compTwoName} }`, // export, post declaration (compOne, compOneName, compTwo, compTwoName) => ` ${compOne(compOneName)} ${compTwo(compTwoName)}