Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
```ts
type ReturnType = {
ctx: {
getAllComponents: (node: TSESTree.Program) => Map<string, FunctionComponent>;
getAllComponents: (node: TSESTree.Program) => FunctionComponent[];
getCurrentEntries: () => FunctionEntry[];
getCurrentEntry: () => FunctionEntry | unit;
};
Expand All @@ -17,8 +17,8 @@ type ReturnType = {

| Property | Type |
| ------ | ------ |
| <a id="ctx"></a> `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)\>; `getCurrentEntries`: () => `FunctionEntry`[]; `getCurrentEntry`: () => `FunctionEntry` \| `unit`; \} |
| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)\> |
| <a id="ctx"></a> `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)[]; `getCurrentEntries`: () => `FunctionEntry`[]; `getCurrentEntry`: () => `FunctionEntry` \| `unit`; \} |
| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)[] |
| `ctx.getCurrentEntries` | () => `FunctionEntry`[] |
| `ctx.getCurrentEntry` | () => `FunctionEntry` \| `unit` |
| <a id="listeners"></a> `listeners` | `ESLintUtils.RuleListener` |
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
```ts
type ReturnType = {
ctx: {
getAllComponents: (node: TSESTree.Program) => Map<string, ClassComponent>;
getAllComponents: (node: TSESTree.Program) => ClassComponent[];
};
listeners: ESLintUtils.RuleListener;
};
Expand All @@ -15,6 +15,6 @@ type ReturnType = {

| Property | Type |
| ------ | ------ |
| <a id="ctx"></a> `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`ClassComponent`](../../../../interfaces/ClassComponent.md)\>; \} |
| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`ClassComponent`](../../../../interfaces/ClassComponent.md)\> |
| <a id="ctx"></a> `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`ClassComponent`](../../../../interfaces/ClassComponent.md)[]; \} |
| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`ClassComponent`](../../../../interfaces/ClassComponent.md)[] |
| <a id="listeners"></a> `listeners` | `ESLintUtils.RuleListener` |
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
```ts
type ReturnType = {
ctx: {
getAllHooks: Map<string, Hook>;
getAllHooks: Hook[];
};
listeners: ESLintUtils.RuleListener;
};
Expand All @@ -15,6 +15,6 @@ type ReturnType = {

| Property | Type |
| ------ | ------ |
| <a id="ctx"></a> `ctx` | \{ `getAllHooks`: [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`Hook`](../../../../interfaces/Hook.md)\>; \} |
| `ctx.getAllHooks` | [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`Hook`](../../../../interfaces/Hook.md)\> |
| <a id="ctx"></a> `ctx` | \{ `getAllHooks`: [`Hook`](../../../../interfaces/Hook.md)[]; \} |
| `ctx.getAllHooks` | [`Hook`](../../../../interfaces/Hook.md)[] |
| <a id="listeners"></a> `listeners` | `ESLintUtils.RuleListener` |
6 changes: 3 additions & 3 deletions packages/core/src/component/component-collector-legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const idGen = new IdGenerator("class_component_");
export declare namespace useComponentCollectorLegacy {
type ReturnType = {
ctx: {
getAllComponents: (node: TSESTree.Program) => Map<string, ClassComponent>;
getAllComponents: (node: TSESTree.Program) => ClassComponent[];
};
listeners: ESLintUtils.RuleListener;
};
Expand All @@ -27,8 +27,8 @@ export function useComponentCollectorLegacy(): useComponentCollectorLegacy.Retur

const ctx = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getAllComponents(node: TSESTree.Program): typeof components {
return components;
getAllComponents(node: TSESTree.Program) {
return [...components.values()];
},
} as const;

Expand Down
31 changes: 8 additions & 23 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export declare namespace useComponentCollector {
};
type ReturnType = {
ctx: {
getAllComponents: (node: TSESTree.Program) => Map<string, FunctionComponent>;
getAllComponents: (node: TSESTree.Program) => FunctionComponent[];
getCurrentEntries: () => FunctionEntry[];
getCurrentEntry: () => FunctionEntry | unit;
};
Expand All @@ -57,37 +57,22 @@ export function useComponentCollector(
hint = DEFAULT_COMPONENT_DETECTION_HINT,
} = options;

const components = new Map<string, FunctionComponent>();
const functionEntries: FunctionEntry[] = [];
const components = new Map<string, FunctionComponent>();

const getCurrentEntry = () => functionEntries.at(-1);
const onFunctionEnter = (node: AST.TSESTreeFunction) => {
const key = idGen.next();
functionEntries.push({ key, node, hookCalls: [], isComponent: false });
};
const onFunctionExit = () => {
const entry = functionEntries.at(-1);
if (entry == null) return;
if (!entry.isComponent) return functionEntries.pop();
const rets = AST.getNestedReturnStatements(entry.node.body);
for (let i = rets.length - 1; i >= 0; i--) {
const ret = rets[i];
if (ret == null) continue;
const shouldDrop = context.sourceCode.getScope(ret).block === entry.node
&& ret.argument != null
&& !isJsxLike(context.sourceCode, ret.argument, hint);
if (shouldDrop) {
components.delete(entry.key);
break;
}
}
return functionEntries.pop();
};

const ctx = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getAllComponents(node: TSESTree.Program): typeof components {
return components;
getAllComponents(node: TSESTree.Program) {
return [...components.values()];
},
getCurrentEntries() {
return [...functionEntries];
Expand Down Expand Up @@ -131,8 +116,7 @@ export function useComponentCollector(
const componentName = left.object.type === T.Identifier
? left.object.name
: unit;
const component = [...components.values()]
.findLast(({ name }) => name != null && name === componentName);
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
if (component == null) return;
component.displayName = right;
},
Expand All @@ -158,10 +142,11 @@ export function useComponentCollector(
entry.isComponent = true;
const initPath = AST.getFunctionInitPath(entry.node);
const id = getFunctionComponentId(context, entry.node);
const key = entry.key;
const name = getComponentNameFromId(id);
components.set(entry.key, {
components.set(key, {
id,
key: entry.key,
key,
kind: "function",
name,
node: entry.node,
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/hook/hook-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type FunctionEntry = {
export declare namespace useHookCollector {
type ReturnType = {
ctx: {
getAllHooks(node: TSESTree.Program): Map<string, Hook>;
getAllHooks(node: TSESTree.Program): Hook[];
};
listeners: ESLintUtils.RuleListener;
};
Expand Down Expand Up @@ -51,8 +51,8 @@ export function useHookCollector(): useHookCollector.ReturnType {
};
const ctx = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getAllHooks(node: TSESTree.Program): typeof hooks {
return hooks;
getAllHooks(node: TSESTree.Program) {
return [...hooks.values()];
},
} as const;
const listeners = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { name = "anonymous", node: component } of components.values()) {
for (const { name = "anonymous", node: component } of ctx.getAllComponents(program)) {
context.report({
messageId: "classComponent",
node: component,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { name = "anonymous", node, displayName, flag, hookCalls } of components.values()) {
for (const { name = "anonymous", node, displayName, flag, hookCalls } of ctx.getAllComponents(program)) {
context.report({
messageId: "functionComponent",
node,
Expand Down
4 changes: 1 addition & 3 deletions packages/plugins/eslint-plugin-react-debug/src/rules/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const allHooks = ctx.getAllHooks(program);

for (const { name, node, hookCalls } of allHooks.values()) {
for (const { name, node, hookCalls } of ctx.getAllHooks(program)) {
context.report({
messageId: "hook",
node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,14 @@ export default createRule<Options, MessageID>({
export function create(context: RuleContext<MessageID, Options>): RuleListener {
const options = normalizeOptions(context.options);
const { rule } = options;
const collector = useComponentCollector(context);
const collectorLegacy = useComponentCollectorLegacy();
const fCollector = useComponentCollector(context);
const cCollector = useComponentCollectorLegacy();

return {
...collector.listeners,
...collectorLegacy.listeners,
...fCollector.listeners,
...cCollector.listeners,
"Program:exit"(program) {
const functionComponents = collector.ctx.getAllComponents(program);
const classComponents = collectorLegacy.ctx.getAllComponents(program);
for (const { node: component } of functionComponents.values()) {
for (const { node: component } of fCollector.ctx.getAllComponents(program)) {
const id = AST.getFunctionId(component);
if (id?.name == null) continue;
const name = id.name;
Expand All @@ -102,7 +100,7 @@ export function create(context: RuleContext<MessageID, Options>): RuleListener {
data: { name, rule },
});
}
for (const { node: component } of classComponents.values()) {
for (const { node: component } of cCollector.ctx.getAllComponents(program)) {
const id = AST.getClassId(component);
if (id?.name == null) continue;
const name = id.name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { name = "anonymous", node: component } of components.values()) {
for (const { name = "anonymous", node: component } of ctx.getAllComponents(program)) {
if (component.body.body.some((m) => isComponentDidCatch(m) || isGetDerivedStateFromError(m))) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { node: component } of components.values()) {
for (const { node: component } of ctx.getAllComponents(program)) {
const { body } = component.body;
for (const member of body) {
if (isComponentWillMount(member)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { node: component } of components.values()) {
for (const { node: component } of ctx.getAllComponents(program)) {
const { body } = component.body;
for (const member of body) {
if (isComponentWillReceiveProps(member)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { node: component } of components.values()) {
for (const { node: component } of ctx.getAllComponents(program)) {
const { body } = component.body;
for (const member of body) {
if (isComponentWillUpdate(member)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import rule, { RULE_NAME } from "./no-missing-component-display-name";
ruleTester.run(RULE_NAME, rule, {
invalid: [
{
code: tsx`const App = React.memo(() => <div>foo</div>)`,
code: tsx`
const App = React.memo(() => <div>foo</div>)
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
Expand All @@ -22,31 +24,41 @@ ruleTester.run(RULE_NAME, rule, {
}],
},
{
code: tsx`const App = React.forwardRef(() => <div>foo</div>)`,
code: tsx`
const App = React.forwardRef(() => <div>foo</div>)
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
},
{
code: tsx`const MemoComponent = React.memo(() => <div></div>)`,
code: tsx`
const MemoComponent = React.memo(() => <div></div>)
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
},
{
code: tsx`const ForwardRefComponent = React.forwardRef(() => <div></div>)`,
code: tsx`
const ForwardRefComponent = React.forwardRef(() => <div></div>)
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
},
{
code: tsx`const MemoForwardRefComponent = React.memo(forwardRef(() => <div></div>))`,
code: tsx`
const MemoForwardRefComponent = React.memo(forwardRef(() => <div></div>))
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
},
{
code: tsx`const MemoForwardRefComponent = React.memo(React.forwardRef(() => <div></div>))`,
code: tsx`
const MemoForwardRefComponent = React.memo(React.forwardRef(() => <div></div>))
`,
errors: [{
messageId: "noMissingComponentDisplayName",
}],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,22 @@
// Fast path: skip if `memo` or `forwardRef` is not present in the file
if (!context.sourceCode.text.includes("memo") && !context.sourceCode.text.includes("forwardRef")) return {};

const {
ctx,
listeners,
} = useComponentCollector(
context,
{
collectDisplayName: true, // We need to collect the displayName of components
collectHookCalls: false,
hint: DEFAULT_COMPONENT_DETECTION_HINT,
},
);
const { ctx, listeners } = useComponentCollector(context, {
collectDisplayName: true,
collectHookCalls: false,
hint: DEFAULT_COMPONENT_DETECTION_HINT,
});

return {
...listeners,
"Program:exit"(program) {
const components = ctx.getAllComponents(program);
for (const { node, displayName, flag } of components.values()) {
for (const { node, displayName, flag } of ctx.getAllComponents(program)) {
console.log(context.sourceCode.getText(node));

Check failure on line 46 in packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement

Check failure on line 46 in packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts

View workflow job for this annotation

GitHub Actions / Publish

Unexpected console statement

Check failure on line 46 in packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement
const id = AST.getFunctionId(node);
// Check if the component is wrapped with `forwardRef` or `memo`
const isMemoOrForwardRef = (flag & (ComponentFlag.ForwardRef | ComponentFlag.Memo)) > 0n;
// If the component is a named function, it has an implicit displayName
if (AST.getFunctionId(node) != null) {
if (id != null) {
continue;
}
// This rule only targets components wrapped with `forwardRef` or `memo`
Expand All @@ -62,10 +57,9 @@
}
// If the component has no displayName, report an error
if (displayName == null) {
const id = AST.getFunctionId(node);
context.report({
messageId: "noMissingComponentDisplayName",
node: id ?? node,
node,
});
}
}
Expand Down
Loading
Loading