Skip to content

JS-1391 Fix S6767 false positive for components exported via HOC#6704

Open
sonar-nigel[bot] wants to merge 35 commits intomasterfrom
fix/JS-1391-fix-fp-on-s6767-props-reported-unused-when-component-is-wrapped-and-exported-via-an-hoc-sonnet
Open

JS-1391 Fix S6767 false positive for components exported via HOC#6704
sonar-nigel[bot] wants to merge 35 commits intomasterfrom
fix/JS-1391-fix-fp-on-s6767-props-reported-unused-when-component-is-wrapped-and-exported-via-an-hoc-sonnet

Conversation

@sonar-nigel
Copy link
Copy Markdown
Contributor

@sonar-nigel sonar-nigel bot commented Mar 27, 2026

Relates to JS-1391

S6767 (no-unused-prop-types) can incorrectly flag props as unused when a React component is exported via an HOC and the wrapper injects a small, stable set of known props that the upstream eslint-plugin-react rule cannot trace. This PR narrows the decorator to suppress only those fixed, known injected props for directly exported components.

  • Suppression now applies only to directly exported HOC forms where the wrapper's injected props are stable and known up front: export default HOC(Comp), export const X = HOC(Comp), and module.exports = HOC(Comp).
  • The current fixed-prop wrappers are withStyles (classes), withTheme (theme), withTranslation (t, i18n, tReady), and withLocalization (getString).
  • Unknown or data-dependent HOCs such as connect(...) remain intentionally unsupported: when the wrapper may inject props but the implementation cannot determine which ones, the rule keeps reporting rather than suppressing potentially real issues.
  • Suppression is prop-specific for the fixed-prop wrappers above, so genuinely unused component-defined props are still reported.
  • helpers/react.ts gains a syntax-based fallback for class components when React types are unresolved, and an annotation-based path for React.FC<Props> / FC<Props> arrow functions when contextual typing is unavailable.

Vibe Bot added 4 commits March 27, 2026 15:28
Tests cover the scenario where props are reported unused when a React
component is wrapped in an HOC and exported. Four export patterns are
tested: export default HOC()(Comp) (curried), export const X = HOC(Comp)
(named), module.exports = HOC(Comp) (CommonJS), and the two-statement
form (const X = HOC(Comp); export { X }). Both PropTypes-based and
TypeScript interface-based components are covered. True positive cases
confirm the rule still reports genuinely unused props when no HOC is
involved. An upstream sentinel verifies the upstream eslint-plugin-react
rule still raises on this pattern.

Relates to JS-1391
The upstream eslint-plugin-react/no-unused-prop-types rule incorrectly
reports props as unused when a React component is wrapped in a Higher-Order
Component (HOC) and exported, because HOCs can inject props that are not
directly accessed in the component body.

Fix: extend the S6767 decorator with composable HOC export suppression.
When the reported component is wrapped in an HOC and exported anywhere in
the file, the issue is suppressed. Four export forms are detected via the
extensible hocExportPatterns array (Array.some): export default HOC(Comp),
export const X = HOC(Comp), module.exports = HOC(Comp), and the
two-statement form const X = HOC(Comp); export { X } / export default X.
Also adds a fallback in matchesClassProps (helpers/react.ts) to resolve
TypeScript class components via heritage clause type arguments when the
base class is unresolved (e.g. declare const React: any).

Also fixes TypeScript compile error: ExportSpecifier.local is typed as
Identifier | Literal, so a type guard is required before accessing .name.

Implementation follows the approved proposal guidelines. Relates to JS-1391.
Fix matchesFunctionProps in helpers/react.ts to use a two-phase strategy
for matching arrow-function components typed as `React.FC<Props>` with
destructured parameters. The previous implementation only used the function
signature's first parameter type, which TypeScript infers from the
destructuring pattern (e.g. `{ tag: any }`) rather than from the contextual
`React.FC<Props>` type annotation when module imports are unresolvable (no
`node_modules`). This incomplete inferred type was NOT mutually assignable
with the full `Props` interface (which includes un-destructured props like
`relay`), causing Strategy C in `findComponentNode` to miss the component
and leaving the HOC-export suppression in the S6767 decorator unreachable.

Phase 1 now reads the first type argument directly from the VariableDeclarator
type annotation (e.g. `Props` in `React.FC<Props>`) via the TypeScript AST,
bypassing inference entirely. Phase 2 falls back to the signature-based
approach for components typed via their parameter list (e.g.
`function Foo(props: FooProps)`).

The expected ruling file for eigen is updated to remove the 8 FP locations
that are now suppressed: `TagHeader.tsx` (relay), `ConversationDetails.tsx`
(relay), `SaleArtworkList.tsx` (contextScreenOwnerId/Slug),
`SaleArtworkListItem.tsx` (contextScreenOwnerId/Slug), and
`SaleArtworkGridItem.tsx` (contextScreenOwnerId/Slug) — all
`createFragmentContainer` HOC-export patterns in the eigen project.

Two test cases are added to `unit.test.ts` covering the React.FC<Props>
arrow function pattern for both Pattern 1 (`export default HOC(Comp)`) and
Pattern 2 (`export const X = HOC(Comp)`).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 27, 2026

Ruling Report

Code no longer flagged (21 issues)

S6767

ant-design/components/input/ClearableLabeledInput.tsx:32

    30 |   disabled?: boolean;
    31 |   direction?: DirectionType;
>   32 |   focused?: boolean;
    33 |   readOnly?: boolean;
    34 |   bordered: boolean;

ant-design/components/input/ClearableLabeledInput.tsx:40

    38 | /** This props only for input. */
    39 | export interface ClearableInputProps extends BasicProps {
>   40 |   size?: SizeType;
    41 |   suffix?: React.ReactNode;
    42 |   prefix?: React.ReactNode;

ant-design/components/input/ClearableLabeledInput.tsx:42

    40 |   size?: SizeType;
    41 |   suffix?: React.ReactNode;
>   42 |   prefix?: React.ReactNode;
    43 |   addonBefore?: React.ReactNode;
    44 |   addonAfter?: React.ReactNode;

ant-design/components/input/ClearableLabeledInput.tsx:43

    41 |   suffix?: React.ReactNode;
    42 |   prefix?: React.ReactNode;
>   43 |   addonBefore?: React.ReactNode;
    44 |   addonAfter?: React.ReactNode;
    45 |   triggerFocus?: () => void;

ant-design/components/input/ClearableLabeledInput.tsx:44

    42 |   prefix?: React.ReactNode;
    43 |   addonBefore?: React.ReactNode;
>   44 |   addonAfter?: React.ReactNode;
    45 |   triggerFocus?: () => void;
    46 |   status?: InputStatus;

ant-design/components/input/ClearableLabeledInput.tsx:45

    43 |   addonBefore?: React.ReactNode;
    44 |   addonAfter?: React.ReactNode;
>   45 |   triggerFocus?: () => void;
    46 |   status?: InputStatus;
    47 | }

desktop/app/src/ui/app-menu/app-menu.tsx:41

    39 |    * menu will receive focus.
    40 |    */
>   41 |   readonly openedWithAccessKey: boolean
    42 | 
    43 |   /**

desktop/app/src/ui/branches/branch-list.tsx:30

    28 |    * See IBranchesState.defaultBranch
    29 |    */
>   30 |   readonly defaultBranch: Branch | null
    31 | 
    32 |   /**

desktop/app/src/ui/branches/branch-list.tsx:35

    33 |    * The currently checked out branch or null if HEAD is detached
    34 |    */
>   35 |   readonly currentBranch: Branch | null
    36 | 
    37 |   /**

desktop/app/src/ui/branches/branch-list.tsx:45

    43 |    * See IBranchesState.recentBranches
    44 |    */
>   45 |   readonly recentBranches: ReadonlyArray<Branch>
    46 | 
    47 |   /**

...and 11 more

New issues flagged (6 issues)

S6767

eigen/src/app/Components/Bidding/Components/Timer.tsx:28

    26 | 
    27 | interface Props {
>   28 |   liveStartsAt?: string
    29 |   isPreview?: boolean
    30 |   isClosed?: boolean

eigen/src/app/Components/Bidding/Components/Timer.tsx:29

    27 | interface Props {
    28 |   liveStartsAt?: string
>   29 |   isPreview?: boolean
    30 |   isClosed?: boolean
    31 |   startsAt?: string

eigen/src/app/Components/Bidding/Components/Timer.tsx:30

    28 |   liveStartsAt?: string
    29 |   isPreview?: boolean
>   30 |   isClosed?: boolean
    31 |   startsAt?: string
    32 |   biddingEndAt?: string | null

eigen/src/app/Components/Bidding/Components/Timer.tsx:31

    29 |   isPreview?: boolean
    30 |   isClosed?: boolean
>   31 |   startsAt?: string
    32 |   biddingEndAt?: string | null
    33 |   lotEndAt?: string | null

eigen/src/app/Components/Bidding/Components/Timer.tsx:32

    30 |   isClosed?: boolean
    31 |   startsAt?: string
>   32 |   biddingEndAt?: string | null
    33 |   lotEndAt?: string | null
    34 | }

eigen/src/app/Components/Bidding/Components/Timer.tsx:33

    31 |   startsAt?: string
    32 |   biddingEndAt?: string | null
>   33 |   lotEndAt?: string | null
    34 | }
    35 | function formatDate(date: string) {
📋 View full report

Code no longer flagged (21)

S6767

New issues flagged (6)

S6767

Vibe Bot and others added 3 commits March 30, 2026 12:43
Fixed build failure and code quality issues across two files:

- helpers/react.ts: Changed `import type ts from 'typescript'` to
  `import ts from 'typescript'` to allow using TypeScript API functions
  (ts.isVariableDeclaration, ts.isTypeReferenceNode) at runtime in the
  new getAnnotationBasedPropsType helper. This was the root cause of
  the bridge build failure (TS1361 errors).

- S6767/decorator.ts: Removed unnecessary type assertions (S4325) in
  getHocWrappedComponentName and hocExportPatterns. TypeScript's control
  flow analysis already narrows the types via prior type checks (e.g.
  `arg.type === 'CallExpression'` narrows arg, `declaration.type ===
  'CallExpression'` narrows declaration, `d.init?.type === 'CallExpression'`
  narrows d.init). Also eliminated the unnecessary `const call = root as
  estree.CallExpression` intermediate variable in hasPropsCall by using
  the TypeScript-narrowed `root` directly.
Fixed 1 external SonarQube issue (S1066) in helpers/react.ts:

- helpers/react.ts line 323: Merged nested if statements into a single
  combined condition. The outer `if (annotatedParamType != null)` and
  inner `if (checker.isTypeAssignableTo(...))` are now one statement,
  satisfying S1066 ("Merge this if statement with the nested one.").
  The @ts-ignore comment is repositioned before the merged if to
  continue suppressing the private TypeScript API access warning.
  Logic is semantically equivalent to the original.
- Merge nested if statement (S1066)
- Remove unnecessary type assertion (S4325)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <[email protected]>
@sonar-nigel sonar-nigel bot marked this pull request as ready for review March 31, 2026 09:33
@sonar-nigel sonar-nigel bot requested a review from a team March 31, 2026 09:33
@sonar-review-alpha
Copy link
Copy Markdown

sonar-review-alpha bot commented Mar 31, 2026

Summary

What changed

This PR fixes false positives in S6767 (no-unused-prop-types) for React components wrapped and directly exported via HOCs. The rule now recognizes when specific props are injected by known HOC wrappers and suppresses only those injected props, leaving genuine unused-prop findings intact.

Key additions:

  • Whitelist of stable HOC wrappers: withStyles (injects classes), withTheme (theme), withTranslation (t, i18n, tReady), withLocalization (getString)
  • Scope-limited suppression: Only applies to directly exported forms like export default withStyles(Comp) or export const X = withTheme(Comp)
  • Safety exclusions: Non-injecting wrappers (memo, observer) and data-dependent HOCs (connect) are intentionally unsupported
  • Enhanced React helpers: Added syntax-based fallback for class components and annotation-based detection for React.FC<Props> arrow functions when type info is unavailable
  • Test updates: Ruling expectations updated to reflect ~20–40 fewer false positives per codebase (ant-design, desktop, eigen, etc.)

Why: The original decorator was too conservative, suppressing all reported props for any HOC-exported component. This PR narrows it to only known, safe cases where the injected props are stable and documented up front.

What reviewers should know

Where to start

  1. Main logic: packages/analysis/src/jsts/rules/S6767/decorator.ts — This is where the HOC detection and suppression happen. The key function is isPropInjectedByDirectlyExportedHoc() and its helpers buildHocExportCache(), getDirectHocExportMetadataFromStatement(), and getHocApplicationMetadata(). Start by understanding how these collect and recognize HOC chains.

  2. React helpers: packages/analysis/src/jsts/rules/helpers/react.ts — Two important changes here:

    • findOwningComponentNode() (renamed from findComponentNode()) — unchanged logic, just renamed
    • getComponentIdentifierFromNode() — new helper that extracts the name from a component node
    • matchesFunctionProps() — now uses a two-phase strategy: first tries annotation-based typing for React.FC<Props> vars, then falls back to signature-based
    • getReactImportBindings() — new caching helper that tracks React import aliases to recognize React.Component, FC, etc.
  3. Test expectations: The JSON files in its/ruling/src/test/expected/*/typescript-S6767.json show which lines are now correctly not reported. Deleted entries mean "this was a false positive, now fixed".

Non-obvious decisions & gotchas

  • Whitelist, not blacklist: Only withStyles, withTheme, withTranslation, and withLocalization suppress. Everything else is unsupported. This means connect(mapState)(Comp) will not suppress, even though it injects props—intentional, because the injected shape is unknown at static analysis time.
  • Direct export only: The HOC detection only works for top-level exports. If you wrap a component inside another function or store it in a variable first without exporting, no suppression.
  • Three-layer defense: Suppression happens in order. If opaque usage is detected (spread props), both HOC and forwardRef suppression are skipped. Understand this ordering if debugging edge cases.
  • Two-phase function typing: The new two-phase approach for matchesFunctionProps() is a workaround for unresolved React imports (missing node_modules). If phase 1 (annotation) doesn't match, phase 2 (signature) is tried. Both must use areMutuallyAssignable() for bidirectional assignability, not just one-way.
  • Non-injecting HOCs blacklist (memo, observer): These are explicitly excluded because they don't add props. Without this, export default memo(MyComponent) would incorrectly suppress.

Testing strategy

  • Look at ruling test diffs to verify the suppressed lines are real false positives (grep the original code to confirm those props aren't used).
  • Unit tests in S6767/unit.test.ts should cover:
    • Detection of each known HOC in various export forms (default, named, CommonJS)
    • Non-injecting wrapper exclusion
    • Curried HOC detection (withStyles(styles)(Comp))
    • Stacking (memo(withTheme(Comp)))
    • Unresolved/unknown HOCs returning empty props

  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

sonar-review-alpha[bot]

This comment was marked as resolved.

…rc/jsts/rules/helpers/react.ts

Comment: **Bug:** The fallback iterates over *all* heritage clauses — both `extends` and `implements` — without filtering. React component props come exclusively from the `extends React.Component<P>` clause. An `implements SomeInterface<T>` clause whose first type argument happens to be mutually assignable with `propsType` (e.g. `class Foo extends UnresolvedBase implements SomeInterface<FooProps>`) would trigger a false match, suppressing legitimate unused-prop violations for a non-component class.

Since `ts` is now a value import (no longer `import type`), `ts.SyntaxKind` is available at runtime and can fix this exactly:

```suggestion
  for (const clause of (cls.heritageClauses ?? []).filter(
    c => c.token === ts.SyntaxKind.ExtendsKeyword,
  )) {
```

- [ ] Mark as noise
sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as resolved.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
sonar-review-alpha[bot]

This comment was marked as outdated.

@francois-mora-sonarsource
Copy link
Copy Markdown
Contributor

The matchesClassProps fallback looks too broad. When props is unavailable, it currently accepts the first type argument of any extends X<FooProps> clause, so a non-React class could match before the real component, for example class StoreModel extends Store<FooProps> {}.

Could you keep this fix simple and make the fallback syntax-based here: before reading typeArguments[0], check that the extended base is exactly React.Component or bare Component, and ignore every other extends target? That should be enough to avoid matching unrelated generic classes without introducing heavier symbol/declaration-file resolution logic.

@francois-mora-sonarsource
Copy link
Copy Markdown
Contributor

The new TypeScript HOC coverage is all positive. Since this PR intentionally supports only direct export forms, could you add a negative boundary test for an unsupported split-export shape such as const Wrapped = hoc(Component); export { Wrapped }; or const Wrapped = hoc(Component); export default Wrapped;? That would pin the intended scope and prevent accidental expansion later.

@francois-mora-sonarsource
Copy link
Copy Markdown
Contributor

The Jira review report attached to JS-1391 looks out of sync with the current branch. It still describes Phase 2 / two-statement handling and RSPEC updates, while the current implementation and tests cover only direct export forms. Could you refresh or replace that report so the review metadata matches the actual PR state?

Vibe Bot added 2 commits April 1, 2026 15:17
Comment: The `matchesClassProps` fallback looks too broad. When `props` is unavailable, it currently accepts the first type argument of any `extends X<FooProps>` clause, so a non-React class could match before the real component, for example `class StoreModel extends Store<FooProps> {}`.

Could you keep this fix simple and make the fallback syntax-based here: before reading `typeArguments[0]`, check that the extended base is exactly `React.Component` or bare `Component`, and ignore every other `extends` target? That should be enough to avoid matching unrelated generic classes without introducing heavier symbol/declaration-file resolution logic.
Comment: The new TypeScript HOC coverage is all positive. Since this PR intentionally supports only direct export forms, could you add a negative boundary test for an unsupported split-export shape such as `const Wrapped = hoc(Component); export { Wrapped };` or `const Wrapped = hoc(Component); export default Wrapped;`? That would pin the intended scope and prevent accidental expansion later.
@sonar-nigel
Copy link
Copy Markdown
Contributor Author

sonar-nigel bot commented Apr 1, 2026

The Jira attachment is stale — here is the refreshed review report reflecting the current branch state. The key differences from the previous report:

What changed:

  • Phase 2 (two-statement form) was intentionally removed. The current implementation handles only three direct export forms: export default HOC(Comp), export const X = HOC(Comp), module.exports = HOC(Comp). Two explicit TP boundary tests document this scope decision.
  • No RSPEC changes are included in this branch (the approach document requested them — this is flagged as a gap).
  • decorator.ts grows from 183 → 402 lines (+219 lines, 119.7%), higher than the 85% previously reported because the baseline is the current master (which already had the forwardRef/opaque-props/bracket-notation layers).
  • helpers/react.ts adds: matchesClassProps fallback for extends React.Component<Props> heritage clauses (extends-only, base-class-filtered), and a two-phase matchesFunctionProps strategy for React.FC<Props> arrow functions.

JS-1391S6767 — SUSPICIOUS (updated)

Verdict: SUSPICIOUS
Note: This supersedes the previous Jira attachment. Reflects current branch after Phase 2 removal and scope refinement.

Claimed FP

Three direct export forms only: export default HOC(Comp), export const X = HOC(Comp), module.exports = HOC(Comp). Two-statement forms (const X = HOC(Comp); export { X } and const X = HOC(Comp); export default X) are intentionally out of scope, pinned by TP boundary tests.

Assessment summary

Criterion Result Notes
1 — Rule scope Main scope, production files
4 — Justification vs fix ⚠️ simple-examples.json has 4 examples; implementation handles 3
5 — Implementation vs approach ⚠️ Phase 2 omitted; RSPEC omitted; WeakMap cache + helpers changes not in approach
6 — RSPEC Approach document requires RSPEC changes; none present
10 — FP legitimacy Genuine: HOC injects props not directly accessed in body
12 — Over-inclusion Inherent trade-off; not verdict-changing
13 — Under-generalization ⚠️ Two-statement form intentionally excluded; documented via TP boundary tests
16 — Performance O(N) top-level scan with WeakMap cache; no type checker calls
17 — Code size decorator.ts +219 lines (119.7%), above 50% threshold → SUSPICIOUS

Verdict Rationale

The FP is genuine and the three direct export forms are correctly handled. Ruling improvements confirm real-world FP reduction (eigen, ant-design, console, courselit, desktop).

Open concerns:

  1. Criterion 17 (code size): 119.7% growth of decorator.ts. SUSPICIOUS.
  2. Criterion 6 (RSPEC): No RSPEC update despite approach document requirement.
  3. Criterion 13 (under-generalization): Two-statement form intentionally out of scope.

Recommended action

Accept with a required RSPEC follow-up: the Known Issues/Limitations section should document HOC export suppression and clarify that split-export forms are not suppressed.

Fixed 1 external SonarQube issue (S3776) in helpers/react.ts:

- helpers/react.ts line 201: Reduced cognitive complexity of
  matchesClassProps from 19 to ~4 by extracting two helper functions:
  isReactComponentExpression() (checks if an expression names the
  React.Component base class) and matchesClassPropsViaSyntax() (handles
  the heritage-clause fallback when TypeScript cannot resolve inherited
  props). Logic is semantically equivalent to the original; the
  !typeArgs?.length guard replaces the equivalent !typeArgs ||
  typeArgs.length === 0 check.
sonar-review-alpha[bot]

This comment was marked as resolved.

Add comprehensive test coverage for the new HOC suppression logic:
- Edge cases in triple-nested curried HOCs
- Split exports that don't qualify as direct HOC exports
- PropTypes.checkPropTypes() exclusion from delegation suppression
- Multiple named exports with HOC wrapping
- CommonJS module.exports patterns

New test count: 16 (up from 13)
All 1548 tests pass across 631 test suites

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as outdated.

sonar-review-alpha[bot]

This comment was marked as outdated.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
sonar-review-alpha[bot]

This comment was marked as outdated.

@francois-mora-sonarsource
Copy link
Copy Markdown
Contributor

One more Peach case seems worth supporting in the same fixed-injected-props bucket: from .

This follows the same pattern as the wrappers already handled here: the HOC has a well-known injected prop, so we can suppress the FP when that prop is only used through the wrapper.

In Peach I found these examples:

  • :
    • the wrapped component extends
    • it destructures and uses
  • :

So I think we should extend the fixed map with something like:

  • ->

That would cover another established HOC with a documented injected prop, without broadening the suppression to wrappers whose injected prop names are user-defined.

@francois-mora-sonarsource
Copy link
Copy Markdown
Contributor

One more Peach case seems worth supporting in the same fixed-injected-props bucket: withLocalization from @fluent/react.

This follows the same pattern as the wrappers already handled here: the HOC has a well-known injected prop, so we can suppress the FP when that prop is only used through the wrapper.

In Peach I found these examples:

  • fxa-auth-server/packages/fxa-payments-server/src/components/fields/index.tsx: export const Input = withLocalization(UnwrappedInput);
    • the wrapped component extends WithLocalizationProps
    • it destructures and uses getString
  • fxa-auth-server/packages/fxa-payments-server/src/components/PaymentForm/index.tsx: export default withLocalization(WrappedPaymentForm);

So I think we should extend the fixed map with something like:

  • withLocalization -> getString

That would cover another established HOC with a documented injected prop, without broadening the suppression to wrappers whose injected prop names are user-defined.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
sonar-review-alpha[bot]

This comment was marked as resolved.

sonar-review-alpha[bot]

This comment was marked as outdated.

@sonarqube-next
Copy link
Copy Markdown

Copy link
Copy Markdown

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean implementation. One small test coverage gap: observer is listed in NON_INJECTING_HOC_WRAPPERS but has no unit test verifying that export default observer(MyComponent) does not suppress unused-prop reports the way memo does at line 473.

🗣️ Give feedback

* directly exported `memo(...)` or `observer(...)` component would hide genuine
* unused-prop findings.
*/
const NON_INJECTING_HOC_WRAPPERS = new Set(['memo', 'observer']);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

observer is present in NON_INJECTING_HOC_WRAPPERS but has no corresponding unit test. The existing memo test at unit.test.ts:473 covers exactly this kind of case — add a parallel test for export default observer(MemoizedButton) to ensure the behaviour is verified and protected against regressions.

  • Mark as noise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant