fix(vscode-extension): stop Export to React emitting an unused React import - #7978
Merged
Merged
Conversation
…import `generateReactComponent()` opened every generated file with `import React from 'react'` while the file's only JSX is one SchemaRenderer element. Under the automatic runtime (`"jsx": "react-jsx"`) the identifier is never read, so a consumer with `noUnusedLocals: true` could not compile the file the command had just handed them (objectui#7862). Measured on this branch against the built `dist/index.d.ts` of `@object-ui/react` and `@object-ui/components`, TypeScript 6.0.3: the emitted file was clean under `react-jsx` + `strict` (exit 0) and failed with `TS6133: 'React' is declared but its value is never read` once `noUnusedLocals: true` was added. Nothing in the extension emits or promises a `jsx` setting: the string does not occur anywhere in the package, the command emits only the untitled `.tsx` document, and the published docs page for the command already showed the output without the import. Adds `src/__tests__/export-to-react-compiles.test.ts`, which extracts the template's PRODUCT and compiles it under `noUnusedLocals` instead of matching substrings in the generator. Its positive control runs on every invocation, so the harness cannot go quietly, permanently green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
… React preamble DESIGN.md section 4 keeps a hand-maintained second copy of the text `generateReactComponent()` emits, and the previous commit removed `import React from 'react'` from the original. The copy is updated from the template verbatim, so the design record does not name a line the command no longer emits — the spelling objectui#7837 landed for this same file and the same defect class. Nothing binds the two copies together; that question is objectui#7976, which stays open for it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This was referenced Sep 6, 2026
os-sam
marked this pull request as ready for review
September 6, 2026 06:43
os-sam
deleted the
claude/issue-7862-vscode-export-react-unused-import
branch
September 6, 2026 06:59
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.
Fixes #7862
generateReactComponent()— the body of the Export to React command — opened everyfile it generated with
import React from 'react', while the only JSX in that file is asingle SchemaRenderer element. Under the automatic JSX runtime the identifier is never
read, so a consumer with
noUnusedLocals: truecould not compile the file the commandhad just handed them.
Clause-②:
no. This changes the scaffolding text one command emits: it moves noschema's accept/reject behaviour, widens no published package's public surface, and
moves no gate's scan population.
The card's reading, reproduced on this branch
Extracted the template's product from the source on disk, then compiled it against the
built
dist/index.d.tsof@object-ui/reactand@object-ui/components, TypeScript6.0.3. Exit codes captured by redirecting first, never through a pipe:
"jsx": "react-jsx",strictnoUnusedLocals: trueGenerated.final.tsx(1,1): error TS6133: 'React' is declared but its value is never read.tsc --listFilesconfirms the program was not vacuous: it holds the generated file, bothpackages/{react,components}/dist/index.d.ts, and@types/react/jsx-runtime.d.ts— sothe automatic runtime's types are the real ones, not a stub.
Two configs were added to price the correction rather than assume it, same tree, same tsc:
"jsx": "react"+noUnusedLocals, import present"jsx": "react", import removederror TS2686: 'React' refers to a UMD global, but the current file is a module.So the trade is exact: one transform's error for the other's. What decides it is below.
What this extension actually promises about the transform
Measured before choosing, because "react-jsx is the modern default" is a recollection,
not a reading. Every search below was run with the exit code captured after a redirect.
jsx? No.exportToReact()calls
vscode.workspace.openTextDocumentwith the generated content andlanguage: 'typescriptreact'— one untitled, in-memory document. The package's onlyvscode.workspace.fs.writeFileis increateNewSchema(), and it writes a.objectui.jsonschema.tsconfig.json/ bundler config setjsxto? Neithersets it.
tsconfig.jsondeclares"lib": ["ES2020"]and"types": ["node", "vscode"]with no
jsxkey;tsup.config.tshas none either. Case-insensitivejsxacross thewhole package — sources, manifest, tsconfigs, snippets, schemas, README, DESIGN,
CHANGELOG — returns zero lines (exit 1).
transform? No. Searched
packages/vscode-extension/README.md,DESIGN.md,CHANGELOG.md,content/docs/utilities/vscode-extension.mdxandcontent/docs/utilities/index.mdforjsx,createElement,classic,transform,react-jsx,React 16,React 17,automatic runtimeandtsconfig: one hittotal,
DESIGN.md:185 tsconfig.json, a build-config heading listing target, module,strict mode and source maps. The other generators in the package emit webview HTML and
JSON schemas — no TypeScript.
"jsx": "react"occurs zero times; all 52"jsx":lines intracked tsconfigs read
react-jsx.@object-ui/reactand@object-ui/componentsboth peeron
react: ^18.0.0 || ^19.0.0, so the automatic runtime is available to every versionthis scaffolding is allowed to target.
And the published docs page already documents the corrected output. The Output Example
under
ObjectUI: Export to Reactincontent/docs/utilities/vscode-extension.mdxopenswith
import { SchemaRenderer } from '@object-ui/react'and carries no React import. Thecommand was drifting from its own documentation; this brings it back.
Nothing found promises or emits the classic transform, so removing the line breaks no
promise the extension has made. The preamble now says which runtime it assumes, so the
one configuration this costs gets told what to do about it instead of just failing.
The pin reads the product, not a substring of the generator
src/__tests__/export-to-react-compiles.test.tsextracts the template literal's body,interpolates a schema, and compiles the result under
"jsx": "react-jsx",strict,noUnusedLocalsandnoUnusedParameters, asserting zero diagnostics.This is deliberately not a second source-text pin. The objectui#7837 pin asserts that
named strings are present or absent; it was green for the entire life of
import React from 'react', a line it never mentions, and it would be green for the nextpreamble line too. Compiling the product closes the class instead of one member of it.
Three things make it hard to fool:
extracted product and requires exactly one
TS6133. If the stubs, options or host everstop reaching the compiler, that test goes red rather than the main assertion going
quietly, permanently green.
.github/workflows/ci.ymlruns the test job as
pnpm install --frozen-lockfilethenpnpm test, with no buildstep, so both sibling
dist/trees are absent there. Resolving to them would make thisfile emit a TS2307 storm in CI — a red test reporting a missing build rather than a
defect. The property under test is a property of the emitted text, and any declaration
for those specifiers decides it. Import shape against the real packages stays the
objectui#7837 pin's job.
needs rewriting, not deleting", and it rejects a template containing a backslash escape
or an unexpected interpolation, since either means the sliced source and the emitted text
have diverged.
A third test records what the correction costs the classic transform. It asserts the
substance — one diagnostic, about
React— not a code, because the code depends on whatdeclares
react:TS2686against the real@types/reactand its UMD global,TS2874against the hermetic stubs. Pinning either would pin the stub, not the fact.
DESIGN.md carries a hand-copy of the same preamble, corrected here
packages/vscode-extension/DESIGN.mdsection 4 keeps a second, hand-maintained copy ofthe text this command emits, and it named the removed line too. The file surface was
widened by exactly this one file to take it, on the precedent for the same file and the
same defect class: the objectui#7837 changeset states that the mirror is corrected in the
same commit so the design record does not freeze the defect. Leaving it would have
manufactured, knowingly, the state that ruling exists to prevent.
The correction is taken from the template verbatim — the commit reads the emitted
preamble out of
generateReactComponent()and writes those bytes into the fence, ratherthan retyping them — and it touches nothing else in the file:
git difffor that commitis one hunk, four lines added and one removed, entirely inside that fenced block.
What still binds the two copies together is nothing, and that is the durable half of
the problem: no gate reaches this file. Measured rather than assumed — the scan surfaces
of
check-doc-snippet-types.mjsandcheck-doc-fence-languages.mjsarecontent/docs,the per-app
apps/*/docstrees,packages/NAME/README.mdand the rootREADME.md, so apackage's
DESIGN.mdis in neither population.check:doc-fencesran here and judged 227documents;
DESIGNappears in zero of them. objectui#7976 stays open for that classquestion — should the pin bind the fence to the template's product — with the instance
corrected here.
Verification, all at
a4d7f6433(the final commit) unless notedpnpm exec vitest run packages/vscode-extension/from the repository root —RUN v4.1.10 /home/user/objectui-issue-7862, Test Files 2 passed, Tests 6 passed.Run from the root deliberately: a package-directory cwd silently runs
apps/consoleinstead (objectui#3378). Re-run after the final commit, together with type-check and
lint, on a freshly built dependency closure (
pnpm --filter 'object-ui^...' build).template — landed-on-disk confirmed by anchored counts (injected text 1, displaced text
0) and a blob change
3f85710d1tof3980085c. The pin went red and named itself:Generated.tsx(1,1): error TS6133: 'React' is declared but its value is never read.Allthree tests went red, in the predicted directions. Restored with
git checkout HEAD -- (absolute path)and proven by state, not by exit code:git diff HEADzero bytes, working blob back to3f85710d1— identical toHEAD:packages/vscode-extension/src/extension.ts—git status --porcelainempty. Thesuite is green again on the restored tree.
pnpm --filter object-ui type-check— exit 0, script name echoed(
tsc --noEmit && tsc -p tsconfig.test.json), zeroerror TS. Proven non-vacuous with--listFiles: the build program holds 180 files includingsrc/extension.tsandexcluding the new test, so it cannot ship inside the
.vsix; the test program holds227 files including both test files.
pnpm --filter object-ui lint— exit 0.eslint . --format jsonin the package: 8files linted, 0 errors, 13 warnings, all pre-existing in
extension.tsandproviders/; both test files score 0 and 0.node scripts/check-lint-coverage.mjs—46/46 packages linted, 0 outstanding errors. The repo-wide
pnpm lintwas narrowed tothis package, and the narrowing is a measurement rather than a skip: the population came
from eslint's own config resolution, the count from
--format json, and the rooteslint.config.jsdeclares noprojectService,projectortsconfigRootDir, sotype-aware linting is off and this diff cannot move any untouched file's verdict.
node scripts/check-changeset-presence.mjsfirst said1 source file(s) of 1 released package(s) changed, and this change adds no changeset,naming
object-ui. After adding.changeset/7862-vscode-export-react-unused-import.mdas a
patch, it reads1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s). Re-run from scratch onceDESIGN.mdjoined the diff, not carriedover:
4 file(s) changed, 2 of them published source of a package the release coversand2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)— exit 0, the one changeset covers both files, and its textnow names the
DESIGN.mdcorrection the way the objectui#7837 changeset named itsown.
check-changeset-no-major.mjsandcheck-changeset-fixed.mjsboth green on thefinal tree.
captured after a redirect, all 0:
check:control-bytes(6419 files scanned),check:phantom-deps,check:self-import,check:unreferenced-sources,check:doc-example-readers,check:vi-mock-specifiers,check:vi-mock-inherit,check:shell-escape-residue,check:side-effects-array,check:published-tsconfig-exclude,scripts/check-type-check-coverage.mjs,scripts/check-lint-coverage.mjs. Adding a package.mdpulled in the documentationfamily, so it was re-derived rather than reused:
check:doc-fences0 (227documents),
check:doc-types0,scripts/check-doc-links.mjs0.git diff HEADis zero bytes ata4d7f6433.check:doc-snippetsexited 2printing
PRECONDITION NOT MET (exit 2) — The snippet program was NOT run, which thegate itself distinguishes from exit 1: it needs 26 packages built. It is also not in
this diff's family — its scan surface excludes a package's
DESIGN.md, as above. And aguessed
scripts/check-links.mjsthrewMODULE_NOT_FOUND; the real gate isscripts/check-doc-links.mjs(run, 0), whilecheck-links.ymlis a lychee actionrather than a local script.
check:readme-exportsis reported as not measured locally, not as green. It printedthe population COLLAPSED -- this run proves nothing, because it reads export sets frombuilt
dist/index.d.tsand its workflow builds every package first. This change edits noREADME and moves no export, so its population is untouched; CI runs it with the build.
Two findings filed
packages/vscode-extension/DESIGN.mdhand-mirroring thispreamble with nothing binding the copy to the original. The instance it was filed for
is corrected in this PR; the card stays open, and is being re-scoped to the class
question it exposes — whether the pin should assert that the fenced block EQUALS the
template's product. That is deliberately not done here: binding a documentation file
into the generator's pin adds a scan surface, and it is a real design decision rather
than a mechanical edit. No closing keyword names it above, on purpose.
content/docs/utilities/vscode-extension.mdxsays the output is copiedto the clipboard, which the command never does (
clipboardoccurs nowhere insrc/),and its example omits the
import '@object-ui/components'side-effect import thatobjectui#7837 established as load-bearing. Untouched here.
Authored by Claude Code in session
01KbJQ1y1J12nZxYzFWhP8Q3(https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3), which is recorded in both commit trailers as well. The footer below is the bare form this platform writes on an edited body; the session-URL form survives creation only, so it is stated here instead of re-posted.Generated by Claude Code