Skip to content

fix(transformers): use symbols for additionalProperties key variables#3841

Merged
mrlubos merged 4 commits into
mainfrom
pullfrog/3838-fix-duplicate-key-in-transformers
Apr 30, 2026
Merged

fix(transformers): use symbols for additionalProperties key variables#3841
mrlubos merged 4 commits into
mainfrom
pullfrog/3838-fix-duplicate-key-in-transformers

Conversation

@pullfrog

@pullfrog pullfrog Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #3838

Problem

The @hey-api/transformers plugin generates duplicate key variable names when nested additionalProperties produce multiple for-loops. This causes TypeScript compilation errors:

for (const key of Object.keys(data.property)) {
    for (const key of Object.keys(data.property[key])) {
        // TS7022: 'key' implicitly has type 'any'
        // TS2448: Block-scoped variable 'key' used before its declaration
    }
}

Fix

Replace the literal string 'key' with plugin.symbol('key') so the planner assigns unique names (key, key2, key3, ...) when there are conflicts. Each invocation of the additionalProperties handling block creates its own symbol, and the planner's name-conflict resolution ensures distinctness across nested and sibling scopes.

Pullfrog  | View workflow run | via Pullfrog𝕏

Replace literal string 'key' with plugin.symbol('key') in the
additionalProperties for-loop generation. This ensures the planner
assigns unique names (key, key2, key3, ...) when nested or sibling
additionalProperties produce multiple for-loops, preventing the
TypeScript error caused by duplicate variable names in nested scopes.

Closes #3838
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@pullfrog pullfrog Bot requested a review from mrlubos April 30, 2026 01:30
@vercel

vercel Bot commented Apr 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hey-api-docs Ready Ready Preview, Comment Apr 30, 2026 3:38am

Request Review

@changeset-bot

changeset-bot Bot commented Apr 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d880caa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@hey-api/openapi-ts Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 39.58%. Comparing base (982e431) to head (d880caa).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...api-ts/src/plugins/@hey-api/transformers/plugin.ts 0.00% 1 Missing ⚠️
packages/openapi-ts/src/ts-dsl/stmt/for.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3841      +/-   ##
==========================================
- Coverage   39.58%   39.58%   -0.01%     
==========================================
  Files         532      532              
  Lines       19580    19581       +1     
  Branches     5829     5835       +6     
==========================================
  Hits         7751     7751              
- Misses       9581     9582       +1     
  Partials     2248     2248              
Flag Coverage Δ
unittests 39.58% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Apr 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@hey-api/codegen-core

npm i https://pkg.pr.new/@hey-api/codegen-core@3841

@hey-api/json-schema-ref-parser

npm i https://pkg.pr.new/@hey-api/json-schema-ref-parser@3841

@hey-api/nuxt

npm i https://pkg.pr.new/@hey-api/nuxt@3841

@hey-api/openapi-ts

npm i https://pkg.pr.new/@hey-api/openapi-ts@3841

@hey-api/shared

npm i https://pkg.pr.new/@hey-api/shared@3841

@hey-api/spec-types

npm i https://pkg.pr.new/@hey-api/spec-types@3841

@hey-api/types

npm i https://pkg.pr.new/@hey-api/types@3841

@hey-api/vite-plugin

npm i https://pkg.pr.new/@hey-api/vite-plugin@3841

commit: d880caa

@mrlubos

mrlubos commented Apr 30, 2026

Copy link
Copy Markdown
Member

@pullfrog actually, the snapshot that changed shouldn't have changed. This is because both keys are within sibling blocks and therefore there's no conflict. This will be either an issue with the DSL incorrectly declaring scopes, or the core package incorrectly grouping these scopes so they conflict. Can you figure out what's the root cause first? After that, I'll give you the instructions how to resolve this issue.

@pullfrog

pullfrog Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

Identified root cause: ForTsDsl.analyze() registers the iteration variable in the parent scope (before pushScope()), causing sibling for-loops to incorrectly conflict. Posted findings and proposed fix in a comment. Awaiting instructions from the maintainer on how to proceed.

Task list (3/3 completed)
  • Checkout the PR branch and understand the current changes
  • Investigate the root cause: DSL scoping vs core grouping issue
  • Report findings to the user

Pullfrog  | View workflow run | via Pullfrog𝕏

@pullfrog

pullfrog Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

Root cause identified — it's the DSL incorrectly declaring scopes.

The issue is in ForTsDsl.analyze() (packages/openapi-ts/src/ts-dsl/stmt/for.ts:42-51):

override analyze(ctx: AnalysisContext): void {
  ctx.analyze(this._condition);
  ctx.analyze(this._iterableOrUpdate);
  ctx.analyze(this._variableOrInit);  // ← registers 'key' in the PARENT scope
  ctx.pushScope();                     // ← for-loop scope starts here
  try {
    super.analyze(ctx);               // DoMixin pushes body scope
  } finally {
    ctx.popScope();
  }
}

The iteration variable (this._variableOrInit, i.e. $.const(key)) is analyzed before ctx.pushScope(), so it's registered in the enclosing scope rather than the for-loop's own block scope. This doesn't match JavaScript semantics where for (const key of ...) scopes key to the loop block.

The consequence: when the planner calls nameIsAvailable() for the second key (inside the if (data.qux) child scope), it walks up the scope chain, finds key already declared in the function body scope (from the first for-loop), and incorrectly considers it a conflict — even though in JS these two key declarations are in sibling blocks and don't shadow each other.

The fix should be moving ctx.analyze(this._variableOrInit) to after ctx.pushScope():

override analyze(ctx: AnalysisContext): void {
  ctx.analyze(this._condition);
  ctx.analyze(this._iterableOrUpdate);
  ctx.pushScope();
  ctx.analyze(this._variableOrInit);  // now scoped to the for-loop block
  try {
    super.analyze(ctx);
  } finally {
    ctx.popScope();
  }
}

Ready for your instructions on how to proceed.

Pullfrog  | View workflow run | via Pullfrog𝕏

@mrlubos

mrlubos commented Apr 30, 2026

Copy link
Copy Markdown
Member

@pullfrog that's the correct fix, apply it and then run tests to confirm everything works

@pullfrog

pullfrog Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

Applied the root cause fix: moved ctx.analyze(this._variableOrInit) after ctx.pushScope() in ForTsDsl.analyze() so the iteration variable is correctly scoped to the for-loop block. Reverted the plugin.symbol('key') workaround and snapshot — sibling for-loops no longer falsely conflict. All tests pass (923 total across openapi-ts, openapi-ts-tests, and codegen-core).

Task list (5/5 completed)
  • Apply the fix to ForTsDsl.analyze() - move ctx.analyze(this._variableOrInit) after ctx.pushScope()
  • Revert the snapshot change (sibling keys should not conflict)
  • Revert the plugin.ts symbol change (no longer needed with correct scoping)
  • Run tests to confirm everything works
  • Commit and push

Pullfrog  | View workflow run | via Pullfrog𝕏

Move ctx.analyze(this._variableOrInit) after ctx.pushScope() so the
iteration variable is registered in the for-loop's own scope rather
than the parent scope. This matches JS semantics where `for (const x of ...)`
scopes `x` to the loop block, preventing false conflicts between sibling loops.

Reverts the plugin.symbol('key') workaround since the correct scoping
makes it unnecessary — sibling for-loops no longer conflict.
@mrlubos

mrlubos commented Apr 30, 2026

Copy link
Copy Markdown
Member

@pullfrog this commit is correct but we also want to keep that symbol change in the transformers plugin. Add that back

@pullfrog

pullfrog Bot commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

Re-added the plugin.symbol('key') change in the transformers plugin. The literal 'key' is now replaced with a symbol so the planner can assign unique names when nested additionalProperties produce multiple for-loops. All tests pass — the snapshot remains unchanged since the scoping fix correctly prevents false conflicts between sibling loops.

Task list (3/3 completed)
  • Find and re-add the symbol change in the transformers plugin
  • Run tests to confirm everything works
  • Commit and push

Pullfrog  | View workflow run | via Pullfrog𝕏

@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. and removed size:XS This PR changes 0-9 lines, ignoring generated files. labels Apr 30, 2026
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Apr 30, 2026
@mrlubos mrlubos merged commit b3c2755 into main Apr 30, 2026
11 of 12 checks passed
@mrlubos mrlubos deleted the pullfrog/3838-fix-duplicate-key-in-transformers branch April 30, 2026 04:02
@hey-api hey-api Bot mentioned this pull request Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug 🔥 Broken or incorrect behavior. lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@hey-api/transformers plugin creates duplicate parameter names referncing the keys within nested objects

1 participant