Skip to content

Improve babel-helper-define-polyfill-provider resolveSource logic#249

Merged
nicolo-ribaudo merged 3 commits intobabel:mainfrom
zloirock:improve-resolve-source
Mar 6, 2026
Merged

Improve babel-helper-define-polyfill-provider resolveSource logic#249
nicolo-ribaudo merged 3 commits intobabel:mainfrom
zloirock:improve-resolve-source

Conversation

@zloirock
Copy link
Member

@zloirock zloirock commented Mar 6, 2026

Some years ago, I found that babel-helper-define-polyfill-provider couldn't properly determine Number instance properties (in Babel 7, number literals produce NumericLiteral nodes), and templates weren't recognized as strings. I didn't have time to explore the problem further.

Now I'm working on a Babel plugin for core-js@4, and I need better type inference logic for instance properties -)

@zloirock zloirock force-pushed the improve-resolve-source branch from c1d157f to 6f65df9 Compare March 6, 2026 13:44
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Thank you! Can you add also a test case for the ambiguous (a + b).foo?

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Code changes look good to me

@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

Can you add also a test case for the ambiguous (a + b).foo?

It's already here. Sure, the case that could be determined, but I think it's future work.

@zloirock zloirock force-pushed the improve-resolve-source branch from 1110399 to 889abad Compare March 6, 2026 14:42
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Awesome, thanks again :)

@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

It seems some more moments. For example, a - b could be bigint. I'll review it again.

@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

Yes, bigint significantly complicates the inference...

@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

Done.

@nicolo-ribaudo
Copy link
Member

Thank you! I'm going to merge this and release.

We are doing some clean up and dropping old Node.js versions (#250), so if we will then need more releases also for the Babel 7 release line those will take a bit more effort.

@nicolo-ribaudo nicolo-ribaudo merged commit 441eb0f into babel:main Mar 6, 2026
6 checks passed
@zloirock zloirock deleted the improve-resolve-source branch March 6, 2026 16:07
@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

@nicolo-ribaudo I wanted to save compatibility of the plugin with Babel@7 - will it be compatible? Sure, since Node requirement will be bumped - not in the scope of preset-env@7.

@zloirock
Copy link
Member Author

zloirock commented Mar 6, 2026

It seems because Removed the CJS build - not completely.

@jackjsj
Copy link

jackjsj commented Mar 11, 2026

Hi @zloirock @nicolo-ribaudo,

I noticed that the changes to resolveSource in #249 may introduce an infinite loop issue. Like this:
image

Problem

The new version of resolveSource adds recursive calls to itself in several cases:

case "SequenceExpression": {
  return resolveSource(expressions[expressions.length - 1]);
}
case "AssignmentExpression": {
  return resolveSource(...get("right"));
}
case "ConditionalExpression": {
  const consequent = resolveSource(...);
  const alternate = resolveSource(...);
}
case "ParenthesizedExpression":
  return resolveSource(...);
// TypeScript/Flow type wrappers also have recursive calls

However, unlike the resolve function which uses a visited Set to prevent infinite loops:

function resolve(
  path: NodePath,
  resolved: Set<NodePath> = new Set(),  // ✅ Has protection
): NodePath | undefined {
  if (resolved.has(path)) return;
  resolved.add(path);
  // ...
}

The resolveSource function has no such protection mechanism.

How it can cause infinite loop

Since resolveSource calls resolve(obj) internally, and resolve may return the same path or a related node, the recursive calls in the new cases can potentially loop indefinitely. For example, with complex nested expressions or edge cases in AST manipulation.

Suggested fix

Add a visited Set parameter to resolveSource:

export function resolveSource(
  obj: NodePath,
  visited: Set<NodePath> = new Set()
): { id: string | null; placement: "prototype" | "static" | null } {
  if (visited.has(obj)) {
    return { id: null, placement: null };
  }
  visited.add(obj);
  
  // ... existing logic ...
  
  // Pass visited to all recursive calls
  case "SequenceExpression": {
    return resolveSource(expressions[expressions.length - 1], visited);
  }
  // ... etc
}

I encountered this infinite loop issue in production. Would you be able to take a look?

Thanks!

@nicolo-ribaudo
Copy link
Member

It seems because Removed the CJS build - not completely.

It will be compatible with Babel 7 on Node.js 20+, which supports require(esm)

@nicolo-ribaudo
Copy link
Member

@jackjsj could you open a PR?

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.

3 participants