Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions apps/website/content/docs/rules/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"no-implicit-key",
"no-leaked-conditional-rendering",
"no-missing-component-display-name",
"no-missing-context-display-name",
"no-missing-key",
"no-nested-components",
"no-prop-types",
Expand Down
107 changes: 54 additions & 53 deletions apps/website/content/docs/rules/overview.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/core/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

## Variables

- [COMPONENT\_DISPLAY\_NAME\_ASSIGNMENT\_SELECTOR](variables/COMPONENT_DISPLAY_NAME_ASSIGNMENT_SELECTOR.md)
- [COMPONENT\_DISPLAY\_NAME\_ASSIGNMENT\_SELECTOR](variables/DISPLAY_NAME_ASSIGNMENT_SELECTOR.md)
- [DEFAULT\_COMPONENT\_HINT](variables/DEFAULT_COMPONENT_HINT.md)
- [ERComponentFlag](variables/ERComponentFlag.md)
- [ERComponentHint](variables/ERComponentHint.md)
Expand Down Expand Up @@ -70,7 +70,7 @@
- [isCloneElementCall](functions/isCloneElementCall.md)
- [isComponentDidCatch](functions/isComponentDidCatch.md)
- [isComponentDidMount](functions/isComponentDidMount.md)
- [isComponentDisplayNameAssignment](functions/isComponentDisplayNameAssignment.md)
- [isDisplayNameAssignment](functions/isDisplayNameAssignment.md)
- [isComponentName](functions/isComponentName.md)
- [isComponentWillUnmount](functions/isComponentWillUnmount.md)
- [isCreateContext](functions/isCreateContext.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

***

[@eslint-react/core](../README.md) / isComponentDisplayNameAssignment
[@eslint-react/core](../README.md) / isDisplayNameAssignment

# Function: isComponentDisplayNameAssignment()
# Function: isDisplayNameAssignment()

> **isComponentDisplayNameAssignment**(`node`): `node is AssignmentExpression`
> **isDisplayNameAssignment**(`node`): `node is AssignmentExpression`

Check if the node is a component display name assignment expression

Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import type { ESLintUtils } from "@typescript-eslint/utils";

import { isChildrenOfCreateElement } from "../element";
import { isReactHookCall } from "../hook";
import { DISPLAY_NAME_ASSIGNMENT_SELECTOR } from "../utils";
import { DEFAULT_COMPONENT_HINT, ERComponentHint } from "./component-collector-hint";
import { COMPONENT_DISPLAY_NAME_ASSIGNMENT_SELECTOR } from "./component-display-name";
import { ERComponentFlag } from "./component-flag";
import { getFunctionComponentIdentifier } from "./component-id";
import { isFunctionOfRenderMethod } from "./component-lifecycle";
Expand Down Expand Up @@ -106,7 +106,7 @@ export function useComponentCollector(
},
...collectDisplayName
? {
[COMPONENT_DISPLAY_NAME_ASSIGNMENT_SELECTOR](node: TSESTree.AssignmentExpression) {
[DISPLAY_NAME_ASSIGNMENT_SELECTOR](node: TSESTree.AssignmentExpression) {
const { left, right } = node;
if (left.type !== T.MemberExpression) return;
const componentName = left.object.type === T.Identifier
Expand Down
1 change: 0 additions & 1 deletion packages/core/src/component/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export * from "./component-collector";
export * from "./component-collector-hint";
export * from "./component-collector-legacy";
export * from "./component-display-name";
export * from "./component-flag";
export * from "./component-id";
export type * from "./component-kind";
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./is-display-name-assignment";
export * from "./is-from-react";
export * from "./is-initialized-from-react";
export * from "./is-react-api";
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
/**
* The ESQuery selector for a component display name assignment expression
*/
export const COMPONENT_DISPLAY_NAME_ASSIGNMENT_SELECTOR = [
export const DISPLAY_NAME_ASSIGNMENT_SELECTOR = [
"AssignmentExpression",
"[type]",
"[operator='=']",
Expand All @@ -17,7 +17,7 @@ export const COMPONENT_DISPLAY_NAME_ASSIGNMENT_SELECTOR = [
* @param node The AST node
* @returns `true` if the node is a component display name assignment
*/
export function isComponentDisplayNameAssignment(node: TSESTree.Node | _): node is TSESTree.AssignmentExpression {
export function isDisplayNameAssignment(node: TSESTree.Node | _): node is TSESTree.AssignmentExpression {
if (node == null) return false;
return node.type === T.AssignmentExpression
&& node.operator === "="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
import { isMatching, P } from "ts-pattern";

import type { TimerEntry } from "../models";
import { createRule, getPhaseKindOfFunction, isInstanceIDEqual } from "../utils";
import { createRule, getPhaseKindOfFunction, isInstanceIdEqual } from "../utils";

// #region Rule Metadata

Expand Down Expand Up @@ -81,7 +81,7 @@ export default createRule<[], MessageID>({
const sEntries: TimerEntry[] = [];
const cEntries: TimerEntry[] = [];
function isInverseEntry(a: TimerEntry, b: TimerEntry) {
return isInstanceIDEqual(a.timerId, b.timerId, context);
return isInstanceIdEqual(a.timerId, b.timerId, context);
}
return {
[":function"](node: AST.TSESTreeFunction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { TSESTree } from "@typescript-eslint/utils";
import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
import { isMatching, match, P } from "ts-pattern";

import { createRule, getInstanceID, getPhaseKindOfFunction, isInstanceIDEqual } from "../utils";
import { createRule, getPhaseKindOfFunction, isInstanceIdEqual } from "../utils";
import type { ObserverEntry, ObserverMethod } from "./../models";

// #region Rule Metadata
Expand Down Expand Up @@ -185,7 +185,7 @@ export default createRule<[], MessageID>({
if (!isNewResizeObserver(node)) {
return;
}
const id = getInstanceID(node);
const id = VAR.getVariableId(node);
if (id == null) {
context.report({
messageId: "unexpectedFloatingInstance",
Expand All @@ -202,11 +202,11 @@ export default createRule<[], MessageID>({
},
["Program:exit"]() {
for (const { id, node, phaseNode } of observers) {
if (dEntries.some((e) => isInstanceIDEqual(e.observer, id, context))) {
if (dEntries.some((e) => isInstanceIdEqual(e.observer, id, context))) {
continue;
}
const oentries = oEntries.filter((e) => isInstanceIDEqual(e.observer, id, context));
const uentries = uEntries.filter((e) => isInstanceIDEqual(e.observer, id, context));
const oentries = oEntries.filter((e) => isInstanceIdEqual(e.observer, id, context));
const uentries = uEntries.filter((e) => isInstanceIdEqual(e.observer, id, context));
const isDynamic = (node: TSESTree.Node | _) => node?.type === T.CallExpression || AST.isConditional(node);
const isPhaseNode = (node: TSESTree.Node | _) => node === phaseNode;
const hasDynamicallyAdded = oentries
Expand All @@ -216,7 +216,7 @@ export default createRule<[], MessageID>({
continue;
}
for (const oEntry of oentries) {
if (uentries.some((uEntry) => isInstanceIDEqual(uEntry.element, oEntry.element, context))) {
if (uentries.some((uEntry) => isInstanceIdEqual(uEntry.element, oEntry.element, context))) {
continue;
}
context.report({ messageId: "expectedDisconnectOrUnobserveInCleanup", node: oEntry.node });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
import { isMatching, P } from "ts-pattern";

import type { TimerEntry } from "../models";
import { createRule, getPhaseKindOfFunction, isInstanceIDEqual } from "../utils";
import { createRule, getPhaseKindOfFunction, isInstanceIdEqual } from "../utils";

// #region Rule Metadata

Expand Down Expand Up @@ -80,7 +80,7 @@ export default createRule<[], MessageID>({
const sEntries: TimerEntry[] = [];
const rEntries: TimerEntry[] = [];
function isInverseEntry(a: TimerEntry, b: TimerEntry) {
return isInstanceIDEqual(a.timerId, b.timerId, context);
return isInstanceIdEqual(a.timerId, b.timerId, context);
}
return {
[":function"](node: AST.TSESTreeFunction) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from "./create-rule";
export * from "./get-instance-id";
export * from "./get-phase-kind-of-function";
export * from "./is-instance-id-equal";
export * from "./is-instance-Id-equal";
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as AST from "@eslint-react/ast";
import type { RuleContext } from "@eslint-react/shared";
import * as VAR from "@eslint-react/var";
import type { TSESTree } from "@typescript-eslint/types";

export function isInstanceIDEqual(a: TSESTree.Node, b: TSESTree.Node, context: RuleContext) {
return AST.isNodeEqual(a, b) || VAR.isNodeValueEqual(a, b, [
export function isInstanceIdEqual(a: TSESTree.Node, b: TSESTree.Node, context: RuleContext) {
return VAR.isVariableIdEqual(a, b, [
context.sourceCode.getScope(a),
context.sourceCode.getScope(b),
]);
Expand Down
2 changes: 2 additions & 0 deletions packages/plugins/eslint-plugin-react-x/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import noForwardRef from "./rules/no-forward-ref";
import noImplicitKey from "./rules/no-implicit-key";
import noLeakedConditionalRendering from "./rules/no-leaked-conditional-rendering";
import noMissingComponentDisplayName from "./rules/no-missing-component-display-name";
import noMissingContextDisplayName from "./rules/no-missing-context-display-name";
import noMissingKey from "./rules/no-missing-key";
import noNestedComponents from "./rules/no-nested-components";
import noPropTypes from "./rules/no-prop-types";
Expand Down Expand Up @@ -85,6 +86,7 @@ export const plugin = {
"no-implicit-key": noImplicitKey,
"no-leaked-conditional-rendering": noLeakedConditionalRendering,
"no-missing-component-display-name": noMissingComponentDisplayName,
"no-missing-context-display-name": noMissingContextDisplayName,
"no-missing-key": noMissingKey,
"no-nested-components": noNestedComponents,
"no-prop-types": noPropTypes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ export default function Button() {

- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts)
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.spec.ts)

---

## See Also

- [`no-missing-context-display-name`](./no-missing-context-display-name)\
Enforces that all contexts have a `displayName` which React can use as its `displayName` in devtools.
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
title: no-missing-context-display-name
---

**Full Name in `eslint-plugin-react-x`**

```plain copy
react-x/no-missing-context-display-name
```

**Full Name in `@eslint-react/eslint-plugin`**

```plain copy
@eslint-react/no-missing-context-display-name
```

**Features**

`🔍`

## What it does

Enforces that all contexts have a `displayName` which React can use as its `displayName` in devtools.

## Examples

### Failing

```tsx
import React from "react";

const MyContext = React.createContext();
```

### Passing

```tsx
import React from "react";

const MyContext = React.createContext();
MyContext.displayName = "MyContext";
```

## Implementation

- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-context-display-name.ts)
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-context-display-name.spec.ts)

---

## See Also

- [`no-missing-component-display-name`](./no-missing-component-display-name)\
Enforces that all components have a `displayName` which React can use as its `displayName` in devtools.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { allFunctions, ruleTester } from "../../../../../test";
import rule, { RULE_NAME } from "./no-missing-context-display-name";

ruleTester.run(RULE_NAME, rule, {
invalid: [
{
code: /* tsx */ `createContext();`,
errors: [{ messageId: "noMissingContextDisplayName" }],
},
{
code: /* tsx */ `const ctx = createContext();`,
errors: [{ messageId: "noMissingContextDisplayName" }],
},
{
code: /* tsx */ `
const ctx1 = createContext();
const ctx2 = createContext();
ctx1.displayName = "ctx";
`,
errors: [{ messageId: "noMissingContextDisplayName" }],
},
{
code: /* tsx */ `
const ctx = createContext();
ctx.displayname = "ctx";
`,
errors: [{ messageId: "noMissingContextDisplayName" }],
},
{
code: /* tsx */ `
createContext();
ctx.displayName = "ctx";
`,
errors: [{ messageId: "noMissingContextDisplayName" }],
},
],
valid: [
...allFunctions,
/* tsx */ `const ctx = createContext(); ctx.displayName = "ctx";`,
/* tsx */ `
const ctx = createContext();
const displayName = "ctx";
ctx.displayName = displayName;
`,
/* tsx */ `
const ctx1 = createContext();
const ctx2 = createContext();
ctx1.displayName = "ctx1";
ctx2.displayName = "ctx2";
`,
/* tsx */ `
const ctx1 = createContext();
const ctx2 = createContext();
const displayName = "ctx";
ctx1.displayName = displayName;
ctx2.displayName = displayName;
`,
/* tsx */ `
const ctx1 = createContext();
const ctx2 = createContext();
{
const displayName = "ctx";
ctx1.displayName = displayName;
ctx2.displayName = displayName;
}
`,
],
});
Loading
Loading