Repository navigation
fix(js,ts): a function value imported from the project links only to what its import binds - #2673
Merged
Merged
Conversation
…what its import binds A function passed as a value whose name the file imports from its own project still fell back to the project's one function of that name whenever the import resolver bound the name to something that is not a function: outline's `isCloudHosted` constant landed on the server's `isCloudHosted()` check, its `Star` model class on the `<Star>` icon, `import * as Slack` on the Slack settings component, and topcoder's `import TCO21 from './themes/TCO21.scss'` on a route component. matchFunctionRef's unique cross-file fallback now also declines when a project import binds the name to something else (importBindsOther): a namespace import, an asset import (stylesheet, image, GraphQL, JSON, font, media; judged by the specifier's extension), or an import the import resolver binds to a node that is not a function or method. A wrapper declared beside the namesake (`export default styled(Disclosure)`) and a lazy loader (`lazy(() => import(...))`) still lead to it, and an import the resolver can't follow at all keeps the fallback. Follow-up to #2671. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
Follow-up to #2671. A function value whose name the file imports from its own project still took
matchFunctionRef's unique cross-file fallback whenever the import resolver bound the name to something that is not a function. It then linked to the project's one function of that name, wherever that was:import { isCloudHosted } from '~/utils/isCloudHosted'(a boolean) linked to the server'sisCloudHosted()policy helper, and itsStar/Import/AuthenticationProvidermodel classes to the UI components of those names. signoz'simport store from 'store'(the Redux store) linked to a test's localstorefactory, and bitwarden's eslint rule specs'errorMessagestring constants to a web-app step'serrorMessage().import * as Slack from '../slack'(a module object) linked to the Slack plugin's settings component.import TCO21 from './themes/TCO21.scss'andimport QA from 'assets/images/img-QA.png'linked to those communities' route components, cord-field's.graphqldocument to aCreateStorycomponent, and mastodon'svisibility.svg?reacticon to itsVisibilityIconcomponent.The fix
src/resolution/name-matcher.ts: the unique cross-file fallback also declines when a project import binds the name to something else (importBindsOther, JS family only, bare names):import * as X): the module object;?query/#hashstripped (stylesheets, images, GraphQL, JSON, fonts, media);context.resolveImport) binds to a node that is not a function or method.Two bindings still lead to the namesake, because there it usually is what the value is:
function Disclosure…; export default styled(Disclosure));import((signoz'sLoadable(() => import('pages/Home'))), which loads the page it is named after.An import the import resolver can't follow at all (a barrel it doesn't read, a
.jsxfile importing a.tsxmodule) still uses the fallback as before.Tests
__tests__/function-ref-project-imports.test.ts:.scss,.png,.svg?reactand.graphqlimports → no edge;styled()wrapper beside its function, alazy(() => import(...))page, anexport { default as … } frombarrel and a plain function import keep their edges.Red on main b8f81e2 (the first two tests fail with all seven edges), green with the fix. Mutation check: dropping the namespace, asset, same-file or lazy-loader rule each fails exactly the test that pins it.
Full suite on this branch at main b8f81e2 (Windows 11, kernel built from the tree,
CODEGRAPH_KERNEL_EXPECT=1,--maxWorkers=4 --minWorkers=1): 7,485 passed, 80 skipped, 1 failed. The failure wasawaited-receiver.test.ts's #1840 case hitting the 5 s default timeout under parallel load (theEBUSYis its teardown after the timeout). That file passes 11/11 alone, twice, and it exercises sync andawaitreceivers, which function values never reach. Rebased afterwards onto main a50104c (#2672, React Router route tables:react.tsonly);tsc --noEmit, both function-ref import tests,function-ref.test.tsand #2672'sreact-router-returned-tables.test.tsre-run green (45/45).A/B on real repos
Same kit as #2671: 23 repos with stubbed
node_modules. Both arms are this branch on main b8f81e2 with the same kernel, identical exceptdist/resolution/name-matcher.js. Every edge compared by source, target, kind, position and metadata; node counts are identical everywhere. For each removed edge,_main5's own resolver was asked (read-only) what the import binds.Heading,Table,Video,Notice,ListItem,Link,History,Highlight,Embed; ×18) → React components of those names; model classesAuthenticationProvider×7,Import×4,Star×2,Reaction→ UI components; client error classes ×5 →server/errors.ts's factories; theHeadingstyled component ×6 → the email template'sHeading; theisCloudHostedconstant ×4 →server/policies/utils.ts:isCloudHosted;Empty×3 →FilterOptions.tsx's localEmpty;const Avatar = AvatarWithHoverCard×3 → the baseAvatar.tsx:Avatar;ListItemand the editor'sInput→ other components; theEditorUpdateErrorclose code → a server factory; the Koaauthroutes app → theauthmiddleware;import * as Slack→ the Slack settings componentstorefrom'store'×28 → a test's localstorefactory; each panel kind'ssections×20 → a test'ssectionshelper; paneldefinitionobjects ({ definition as Histogram }, …) ×6 → the visualization chart components; a test helperreplaceVariables→ the generated querier's; mockaccountsResponse→ a stories mock; theSummary/Hoststab-route objects → the pages those tabs mount (see below)RadioButtonField,TextInputField,ToggleField,CharacterCounter(×2 each) → namesakes inredesign.tsx/ another feature folder; theMediaGalleryclass ×2 → its async loader inasync-components.js; theAutosuggestAccountclass → another component; acreateActionconstantloadPending→timelines.js'sloadPending();visibility.svg?react×2 → theVisibilityIconcomponentimport * as InteractiveCanvas from '../renderer/interactiveScene'×8 and* as distancehanded to spies → theInteractiveCanvascomponent and adistance()utilButtoncomponent ×7 → a stories file'sButton; a reducer constant →scripts/utils.ts:error()errorMessagestrings ×6 → a web-app step'serrorMessage(); spec-datafolders/loginconstants → other specs' functions.scss×2 and.png×4 imports → community route components; an example class → a dashboard componentmixins: [truncate](a mixin object) →utils/truncate.js:truncate()login().graphqldocument → aCreateStorycomponentTriage: 167 of 168 removed edges were wrong. Each took a value that is a constant, class, module object or asset and linked it to an unrelated function that shares its name. The exception is SigNoz's
pages/MetricsExplorer/constants.tsxSummary, aTabRoutesobject{ Component: SummaryPage, … }. The removed edge named the very component the tab mounts, but only by coincidence of names. The real path,SummaryPage→container/MetricsExplorer/Summary/index.ts(import Summary from './Summary'; export default Summary) →Summary.tsx, is a default-export forwarding gap in the import resolver, which #2451 (open) fills.Hostsis the same shape, but its flow survives through the constant's ownHostsContaineredge.Residual: after the fix, the only imports that bind a non-function and still lead to a namesake in another file are SigNoz's 49
pageComponentslazy loaders (Loadable(() => import('pages/…'))), kept by design. The same-file wrappers (outline ×4) are kept, and imports the import resolver can't follow (barrels,.jsx→.tsx) are untouched.Cost: none measurable (outline 21 s / 22 s, signoz 30 s / 26 s, bitwarden 57 s / 51 s). The check runs only on the unique cross-file fallback for a name the file imports from the project.
Coordination
Builds on #2671, which gated the same fallback for package imports. No other open PR touches
matchFunctionRef.🤖 Generated with Claude Code