Skip to content

fix: guard against non-function d3-scale exports in getD3ScaleFromType#7123

Merged
PavelVanecek merged 6 commits into
recharts:mainfrom
tdebarochez:fix/d3Scales-name-not-a-function
Mar 22, 2026
Merged

fix: guard against non-function d3-scale exports in getD3ScaleFromType#7123
PavelVanecek merged 6 commits into
recharts:mainfrom
tdebarochez:fix/d3Scales-name-not-a-function

Conversation

@tdebarochez

@tdebarochez tdebarochez commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Description

Add typeof === 'function' guards in getD3ScaleFromType (and isSupportedScaleName in v3.7+) before calling d3-scale exports. The in operator returns true for scaleImplicit, which is a Symbol (not a function), causing a TypeError: d3Scales[name] is not a function at runtime.

Related Issue

N/A — discovered in production usage with victory-vendor/d3-scale re-exports.

Motivation and Context

d3-scale exports scaleImplicit as Symbol("implicit") (via d3-scale/src/ordinal.js). When recharts imports * as d3Scales from victory-vendor/d3-scale (which re-exports everything from d3-scale), the namespace object includes scaleImplicit.

In getD3ScaleFromType, the check name in d3Scales passes for "scaleImplicit", but d3Scales["scaleImplicit"]() throws because a Symbol is not callable.

This surfaces depending on how the bundler resolves the victory-vendor ESM re-exports — some bundlers tree-shake scaleImplicit out of the namespace, others preserve it.

How Has This Been Tested?

  • Verified the fix resolves the runtime error in a project using recharts with Bun as bundler
  • Confirmed scaleImplicit is correctly skipped while all valid scale* functions continue to work

Types of changes

  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
    • No documentation change needed — this is an internal guard.
  • I have added tests to cover my changes.
    • Existing tests should continue to pass; the fix only adds a type guard to skip non-callable exports.

Summary by CodeRabbit

  • Refactor

    • Improved runtime guards and type-safety around scale resolution to reduce risk of runtime errors when applying scales.
  • Tests

    • Added comprehensive tests validating scale configuration, unknown or non-function scale cases, and direct function usage (verifying domain/range behavior).

No user-facing changes.

@tdebarochez tdebarochez changed the title Fix/d3 scales name not a function fix: guard against non-function d3-scale exports in getD3ScaleFromType Mar 10, 2026
@tdebarochez
tdebarochez marked this pull request as ready for review March 10, 2026 13:59
@coderabbitai

coderabbitai Bot commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

Replaces direct property access on d3Scales with a local scales view and adds typeof ... === 'function' guards when resolving scale factories; adds unit tests covering named, full-name, function, unknown, and non-function scale resolution cases.

Changes

Cohort / File(s) Summary
Scale combiner
src/state/selectors/combiners/combineConfiguredScale.ts
Use a local scales alias and add typeof ... === 'function' guards for both direct and derived name lookups before invoking scale factories; behavior unchanged when guards pass.
Unit tests
test/state/selectors/combiners/combineConfiguredScale.spec.ts
Adds tests for combineConfiguredScaleInternal: undefined scale, short names (linear, log), full d3 names (scaleLinear), non-function export resolution, unknown name, and passing a function directly; verifies domain/range behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

typescript

Suggested reviewers

  • ckifer
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically summarizes the main change: adding guards against non-function d3-scale exports in getD3ScaleFromType.
Description check ✅ Passed The description provides a comprehensive explanation of the bug, its root cause, motivation, testing approach, and is well-structured following the repository template with all key sections addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can validate your CodeRabbit configuration file in your editor.

If your editor has YAML language server, you can enable auto-completion and validation by adding # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json at the top of your CodeRabbit configuration file.

@PavelVanecek

Copy link
Copy Markdown
Collaborator

We're going to need a test for this. How to reproduce?

@tdebarochez

tdebarochez commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

@PavelVanecek I found the issue while switching from one branch to another on a big project and regenerating my bun.lock file.
TBH, I'm not sure to know how to reproduce it.

@PavelVanecek

Copy link
Copy Markdown
Collaborator

If we won't test then it's likely to come back later.

@tdebarochez

Copy link
Copy Markdown
Contributor Author

Of course, I completely understand.
Does the tests I've written are enough? Do you have any clue on how I can help? The last thing I want is making yourself losing your time.

Comment thread test/state/selectors/combiners/combineConfiguredScale.spec.ts Outdated
@PavelVanecek

Copy link
Copy Markdown
Collaborator

Better like this. If you prefer you can also add a full app with bun and all and put it in https://github.com/recharts/recharts-integ/tree/main/integrations. We don't have anything for bun yet.

@codecov

codecov Bot commented Mar 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.62%. Comparing base (b39edbf) to head (1108a3b).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7123      +/-   ##
==========================================
+ Coverage   89.61%   89.62%   +0.01%     
==========================================
  Files         536      536              
  Lines       40479    40480       +1     
  Branches     5519     5521       +2     
==========================================
+ Hits        36275    36282       +7     
+ Misses       4196     4190       -6     
  Partials        8        8              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codecov

codecov Bot commented Mar 11, 2026

Copy link
Copy Markdown

Bundle Report

Changes will decrease total bundle size by 166 bytes (-0.0%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
recharts/bundle-cjs 1.3MB -32 bytes (-0.0%) ⬇️
recharts/bundle-es6 1.13MB -32 bytes (-0.0%) ⬇️
recharts/bundle-umd 546.73kB 58 bytes (0.01%) ⬆️
recharts/bundle-treeshaking-sankey 345.51kB -32 bytes (-0.01%) ⬇️
recharts/bundle-treeshaking-polar 447.96kB -32 bytes (-0.01%) ⬇️
recharts/bundle-treeshaking-treemap 353.98kB -32 bytes (-0.01%) ⬇️
recharts/bundle-treeshaking-sunburst 323.19kB -32 bytes (-0.01%) ⬇️
recharts/bundle-treeshaking-cartesian 643.57kB -32 bytes (-0.0%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: recharts/bundle-treeshaking-treemap

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -32 bytes 353.98kB -0.01%
view changes for bundle: recharts/bundle-es6

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/combiners/combineConfiguredScale.js -32 bytes 1.56kB -2.01%
view changes for bundle: recharts/bundle-cjs

Assets Changed:

Asset Name Size Change Total Size Change (%)
state/selectors/combiners/combineConfiguredScale.js -32 bytes 2.44kB -1.3%
view changes for bundle: recharts/bundle-treeshaking-sankey

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -32 bytes 345.51kB -0.01%
view changes for bundle: recharts/bundle-umd

Assets Changed:

Asset Name Size Change Total Size Change (%)
Recharts.js 58 bytes 546.73kB 0.01%
view changes for bundle: recharts/bundle-treeshaking-polar

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -32 bytes 447.96kB -0.01%
view changes for bundle: recharts/bundle-treeshaking-cartesian

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -32 bytes 643.57kB -0.0%
view changes for bundle: recharts/bundle-treeshaking-sunburst

Assets Changed:

Asset Name Size Change Total Size Change (%)
bundle.js -32 bytes 323.19kB -0.01%

Ensures d3Scales entries are functions before invoking them, preventing
runtime "not a function" errors when a scale name resolves to a
non-callable export.
@tdebarochez
tdebarochez force-pushed the fix/d3Scales-name-not-a-function branch from 64aa020 to 5de819e Compare March 11, 2026 12:57
@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/recharts/recharts/issues/comments/4031595697","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- walkthrough_start -->\n\n## Walkthrough\n\nReplaces direct property access on `d3Scales` with a local `scales` alias and adds `typeof ... === 'function'` guards when resolving scale factories; adds unit tests covering short names, full d3 names, function scales, unknown names, and non-function exports.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Scale combiner** <br> `src/state/selectors/combiners/combineConfiguredScale.ts`|Use a local `scales` alias and guard lookups with `typeof ... === 'function'` before invoking scale factory functions; returns undefined when not a function.|\n|**Unit tests** <br> `test/state/selectors/combiners/combineConfiguredScale.spec.ts`|Add Vitest suite for `combineConfiguredScaleInternal`: tests for undefined input, short names (`linear`, `log`), full d3 name (`scaleLinear`), non-function export, unknown name, and passing a function directly; verifies domain/range.|\n\n## Estimated code review effort\n\n🎯 2 (Simple) | ⏱️ ~10 minutes\n\n## Possibly related PRs\n\n- recharts/recharts#6930 — touches and refactors the same combiner (`combineConfiguredScale` / `combineConfiguredScaleInternal`), likely directly related.\n\n## Suggested labels\n\n`typescript`\n\n## Suggested reviewers\n\n- ckifer\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 2 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                          | Resolution                                                                         |\n| :----------------: | :--------- | :----------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (2 passed)</summary>\n\n|     Check name    | Status   | Explanation                                                                                                                                                                                            |\n| :---------------: | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|    Title check    | ✅ Passed | The title accurately describes the main change: adding guards against non-function d3-scale exports in getD3ScaleFromType function.                                                                    |\n| Description check | ✅ Passed | The description covers all required sections: problem description, related issue status, motivation/context, testing approach, and change type checkbox. Only missing the optional screenshot section. |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=recharts/recharts&utm_content=7123)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- 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 -->\n\n<!-- internal state end -->"},"request":{"retryCount":3,"retries":3,"retryAfter":16}}}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/state/selectors/combiners/combineConfiguredScale.ts (2)

17-24: Runtime guards correctly prevent TypeError on non-function exports.

The typeof === 'function' guards effectively fix the issue where scaleImplicit (a Symbol) would cause a runtime error when invoked. The use of unknown over any is the right choice.

Note on as assertions: The coding guidelines prohibit as type assertions except for as const. Lines 17, 19, and 23 use as assertions. While TypeScript cannot automatically narrow indexed access types after a typeof check, you could consider a type guard pattern to eliminate the assertions:

♻️ Optional refactor using a type guard
+type ScaleFactory<Domain extends CategoricalDomainItem> = () => CustomScaleDefinition<Domain>;
+
+function isScaleFactory<Domain extends CategoricalDomainItem>(
+  value: unknown,
+): value is ScaleFactory<Domain> {
+  return typeof value === 'function';
+}
+
 function getD3ScaleFromType<Domain extends CategoricalDomainItem = CategoricalDomainItem>(
   realScaleType: D3ScaleType | RechartsScaleType,
 ): CustomScaleDefinition<Domain> | undefined {
-  const scales = d3Scales as Record<string, unknown>;
-  if (realScaleType in scales && typeof scales[realScaleType] === 'function') {
-    return (scales[realScaleType] as () => CustomScaleDefinition<Domain>)();
+  const scales: Record<string, unknown> = d3Scales;
+  const directScale = scales[realScaleType];
+  if (isScaleFactory<Domain>(directScale)) {
+    return directScale();
   }
   const name = `scale${upperFirst(realScaleType)}`;
-  if (name in scales && typeof scales[name] === 'function') {
-    return (scales[name] as () => CustomScaleDefinition<Domain>)();
+  const namedScale = scales[name];
+  if (isScaleFactory<Domain>(namedScale)) {
+    return namedScale();
   }
   return undefined;
 }

Given the runtime safety is ensured by the typeof checks and this is a pragmatic solution, this can be deferred if needed. As per coding guidelines: "Do not use as type assertions. The only exception is as const".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/state/selectors/combiners/combineConfiguredScale.ts` around lines 17 -
24, The code uses forbidden `as` assertions on lines that index into `scales`;
replace those casts by adding a type guard (e.g. isScaleFactory(value): value is
() => CustomScaleDefinition<Domain>) and use it for the checks around
`scales[realScaleType]` and `scales[name]` so you can safely call the returned
function without `as` assertions; reference `d3Scales`, `scales`,
`realScaleType`, `name`, `upperFirst`, and `CustomScaleDefinition` when
implementing the guard and remove the `as` usages.

14-26: The inconsistency concern is valid, but the specific example is misleading.

The code in combineConfiguredScale.ts validates that the value is both in d3Scales and is a function, while isSupportedScaleName only checks the in operator. However, isSupportedScaleName('implicit') would not actually be a problem since 'implicit' is not part of the RechartsScaleType union type—all valid RechartsScaleType values have corresponding function exports in d3-scale.

That said, updating isSupportedScaleName to also verify callability would improve consistency and defensive programming:

function isSupportedScaleName(name: string): name is RechartsScaleType {
  const scaleName = getD3ScaleName(name);
  return scaleName in d3Scales && typeof d3Scales[scaleName as keyof typeof d3Scales] === 'function';
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/state/selectors/combiners/combineConfiguredScale.ts` around lines 14 -
26, Update the isSupportedScaleName type guard to mirror the defensive check
used in getD3ScaleFromType: compute the canonical name via getD3ScaleName(name),
then verify that this key exists in d3Scales and that typeof d3Scales[scaleName]
=== 'function' before returning true; ensure the function signature remains a
type predicate (name is RechartsScaleType) so callers relying on the guard (and
getD3ScaleFromType) are safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/state/selectors/combiners/combineConfiguredScale.ts`:
- Around line 17-24: The code uses forbidden `as` assertions on lines that index
into `scales`; replace those casts by adding a type guard (e.g.
isScaleFactory(value): value is () => CustomScaleDefinition<Domain>) and use it
for the checks around `scales[realScaleType]` and `scales[name]` so you can
safely call the returned function without `as` assertions; reference `d3Scales`,
`scales`, `realScaleType`, `name`, `upperFirst`, and `CustomScaleDefinition`
when implementing the guard and remove the `as` usages.
- Around line 14-26: Update the isSupportedScaleName type guard to mirror the
defensive check used in getD3ScaleFromType: compute the canonical name via
getD3ScaleName(name), then verify that this key exists in d3Scales and that
typeof d3Scales[scaleName] === 'function' before returning true; ensure the
function signature remains a type predicate (name is RechartsScaleType) so
callers relying on the guard (and getD3ScaleFromType) are safe.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d51aeb76-b6c0-4521-a255-fe034157c37e

📥 Commits

Reviewing files that changed from the base of the PR and between 9fde793 and 5de819e.

📒 Files selected for processing (2)
  • src/state/selectors/combiners/combineConfiguredScale.ts
  • test/state/selectors/combiners/combineConfiguredScale.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/state/selectors/combiners/combineConfiguredScale.spec.ts

// @ts-expect-error we should do better type verification here
return d3Scales[name]();
if (name in scales && typeof scales[name] === 'function') {
return (scales[name] as () => CustomScaleDefinition<Domain>)();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Btw the "as" has the exact same type safety as the comment (= none whatsoever) but it pretends that it does. I would prefer the comment. At least that way it's obvious that this has no type safety.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/state/selectors/combiners/combineConfiguredScale.ts (1)

17-23: ⚠️ Potential issue | 🟠 Major

Use own-key lookup and remove the forbidden as assertion.

Line 17 uses an as assertion (disallowed by repo rules), and Lines 18/22 use in, which can match inherited function keys. Prefer own-property resolution plus runtime narrowing.

♻️ Proposed fix
 function getD3ScaleFromType<Domain extends CategoricalDomainItem = CategoricalDomainItem>(
   realScaleType: D3ScaleType | RechartsScaleType,
 ): CustomScaleDefinition<Domain> | undefined {
-  const scales = d3Scales as Record<string, unknown>;
-  if (realScaleType in scales && typeof scales[realScaleType] === 'function') {
-    return scales[realScaleType]();
-  }
+  const resolveScaleFactory = (key: PropertyKey): (() => CustomScaleDefinition<Domain>) | undefined => {
+    if (!Object.prototype.hasOwnProperty.call(d3Scales, key)) {
+      return undefined;
+    }
+    const candidate = Reflect.get(d3Scales, key);
+    return typeof candidate === 'function' ? candidate : undefined;
+  };
+
+  const directFactory = resolveScaleFactory(realScaleType);
+  if (directFactory != null) {
+    return directFactory();
+  }
+
   const name = `scale${upperFirst(realScaleType)}`;
-  if (name in scales && typeof scales[name] === 'function') {
-    return scales[name]();
+  const prefixedFactory = resolveScaleFactory(name);
+  if (prefixedFactory != null) {
+    return prefixedFactory();
   }
 
   return undefined;
 }
#!/bin/bash
# Verify this file no longer contains the forbidden cast or `in scales` pattern.
rg -n "d3Scales\s+as\s+Record|in\s+scales" src/state/selectors/combiners/combineConfiguredScale.ts

As per coding guidelines: "src/**/*.{ts,tsx}: Do not use as type assertions. The only exception is as const."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/state/selectors/combiners/combineConfiguredScale.ts` around lines 17 -
23, Remove the forbidden "as" type assertion and stop using the `in scales`
inherited-key check; instead use the original d3Scales object directly and
perform own-key lookups with Object.prototype.hasOwnProperty.call(d3Scales, key)
(where key is realScaleType or `scale${upperFirst(realScaleType)}`), then
runtime-narrow the retrieved value with typeof === 'function' and call it (e.g.,
const candidate = d3Scales[key]; if
(Object.prototype.hasOwnProperty.call(d3Scales, key) && typeof candidate ===
'function') return candidate();). Ensure you update the lookup logic around the
symbols d3Scales, realScaleType, upperFirst and the scale factory calls to use
this pattern and remove the `as` cast.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/state/selectors/combiners/combineConfiguredScale.ts`:
- Around line 17-23: Remove the forbidden "as" type assertion and stop using the
`in scales` inherited-key check; instead use the original d3Scales object
directly and perform own-key lookups with
Object.prototype.hasOwnProperty.call(d3Scales, key) (where key is realScaleType
or `scale${upperFirst(realScaleType)}`), then runtime-narrow the retrieved value
with typeof === 'function' and call it (e.g., const candidate = d3Scales[key];
if (Object.prototype.hasOwnProperty.call(d3Scales, key) && typeof candidate ===
'function') return candidate();). Ensure you update the lookup logic around the
symbols d3Scales, realScaleType, upperFirst and the scale factory calls to use
this pattern and remove the `as` cast.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 44f4f30a-737e-4975-8451-0c9bc061b5e1

📥 Commits

Reviewing files that changed from the base of the PR and between 9fde793 and cb43c2b.

📒 Files selected for processing (2)
  • src/state/selectors/combiners/combineConfiguredScale.ts
  • test/state/selectors/combiners/combineConfiguredScale.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/state/selectors/combiners/combineConfiguredScale.spec.ts

@PavelVanecek

Copy link
Copy Markdown
Collaborator

@tdebarochez may I please ask you to rebase or merge main? I fixed the builds in another PR.

@PavelVanecek
PavelVanecek merged commit 03df01a into recharts:main Mar 22, 2026
52 checks passed
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.

2 participants