Skip to content

Commit 744c25e

Browse files
claude[bot]claude
andauthored
fix(vscode-extension): Export to React wrote a phantom registerDefaultRenderers import into every generated file (objectui#7837) (#7863)
* fix(vscode-extension): Export to React emitted a phantom import into every generated file (objectui#7837) `generateReactComponent()` — the body of the `Export to React` command — wrote `import { registerDefaultRenderers } from '@object-ui/components';` plus a call to it into every file a user received. That symbol is on no export of that package: its built `dist/index.d.ts` carries exactly one `register*` name, `registerPlaceholders`, and `registerDefaultRenderers` occurs 0 times in either `dist/index.d.ts` or `dist/index.js` (measured at 3faaa7d). So the command's output failed to compile with TS2305 naming a symbol the user never typed. The package registers its renderers as an import side effect — `sideEffects: true`, `import './renderers'` in the barrel, 114 module-scope `register(` call sites in the built `dist/index.js` — so there is no registration function to call. The preamble now imports the package for the side effect and says so, the spelling the root README landed for objectui#7417. `DESIGN.md`, which documented the identical two lines, moves in the same commit so the design record does not freeze the defect. A pin on the template's own text is added, because the preamble lives inside a template literal where `tsc --noEmit` sees only a string and no doc gate's surface reaches this file. It asserts source text rather than compiling the output: compiling would require exporting `generateReactComponent()`, which this change is not allowed to do. No export added, no signature changed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 * test(vscode-extension): put the new pin inside a `tsc` program (objectui#7837) `scripts/check-type-check-coverage.mjs` exited 1 on the pin added in the previous commit: `packages/vscode-extension/tsconfig.json` is the BUILD program and correctly excludes `**/*.test.ts`, so nothing compiled the test — and an unchecked test can assert a contract the compiler never checked and then read as evidence that the contract holds, which is the same defect class this card is about. Adds `tsconfig.test.json` in the shape 35 sibling packages already use, chained off the package's `type-check` script as the gate requires. Its `module` / `moduleResolution` are ESNext/Bundler rather than the build's node16 CommonJS because that is the program vitest actually runs these files in — `import.meta.url` is legal there and TS1470 under the build's target. `include` and `exclude` are overridden rather than merged, so the project is not left green while reading nothing. Verified with `tsc -p tsconfig.test.json --listFiles`: the project reads the pin. `check-type-check-coverage` now reports 42/42 packages compiling their tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4304647 commit 744c25e

6 files changed

Lines changed: 169 additions & 8 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'object-ui': patch
3+
---
4+
5+
Fix the phantom import the VS Code extension's **Export to React** command wrote into
6+
every file it generated (objectui#7837).
7+
8+
`generateReactComponent()` emitted a preamble that imported `registerDefaultRenderers`
9+
from `@object-ui/components` and then called it. That symbol is on **no export** of
10+
that package: its built `dist/index.d.ts` carries exactly one `register*` name,
11+
`registerPlaceholders`, and `registerDefaultRenderers` appears **0 times** in either
12+
`dist/index.d.ts` or `dist/index.js`. So every file the command produced failed to
13+
compile with `TS2305` naming a symbol the user never typed.
14+
15+
`@object-ui/components` registers its renderers as an **import side effect** —
16+
`sideEffects: true` in its manifest, `import './renderers'` in its barrel under the
17+
comment `Register all ObjectUI renderers (side-effects)`, and **114 `register(` call
18+
sites** at module scope in the built `dist/index.js`. There is no registration function
19+
to call, so the generated preamble now imports the package for the side effect and says
20+
why. Same spelling the root README landed for objectui#7417.
21+
22+
`packages/vscode-extension/DESIGN.md`, which documented the identical two lines, is
23+
corrected in the same commit so the design record does not freeze the defect.
24+
25+
No public surface moved: no export added, no signature changed.

‎packages/vscode-extension/DESIGN.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,9 @@ if (currentType === 'input') {
269269
```typescript
270270
import React from 'react';
271271
import { SchemaRenderer } from '@object-ui/react';
272-
import { registerDefaultRenderers } from '@object-ui/components';
273-
274-
registerDefaultRenderers();
272+
// Importing the package registers every default renderer as a side effect —
273+
// there is no separate registration call.
274+
import '@object-ui/components';
275275

276276
const schema = { /* 用户的schema */ };
277277

‎packages/vscode-extension/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
"scripts": {
193193
"vscode:prepublish": "pnpm run build",
194194
"build": "tsup",
195-
"type-check": "tsc --noEmit",
195+
"type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
196196
"lint": "eslint .",
197197
"dev": "tsup --watch",
198198
"test": "vitest run --passWithNoTests --root ../.. packages/vscode-extension/",
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* objectui#7837 — pins on the preamble the `Export to React` command writes
11+
* into every file a user gets.
12+
*
13+
* WHY A PIN AT ALL. The preamble lives inside a TEMPLATE LITERAL, so nothing
14+
* in this package's toolchain reads it: `tsc --noEmit` sees a string, `tsup`
15+
* copies it through, and the doc gates' surfaces are `content/docs`, the
16+
* per-app docs trees, `packages/NAME/README.md` and the root `README.md` —
17+
* none of which is this file. For as long as the preamble imported
18+
* `registerDefaultRenderers` from `@object-ui/components` and called it, this
19+
* package's own type-check was green and every generated file failed to
20+
* compile with `TS2305` on a symbol the user never typed. There was no binding
21+
* between the verifier and the thing verified; these assertions are it.
22+
*
23+
* WHY SOURCE TEXT AND NOT A COMPILE. Compiling the emitted code under test
24+
* would mean exporting `generateReactComponent()`, and objectui#7837 is
25+
* explicitly not allowed to move this package's export surface. So these read
26+
* the template out of the source instead. That is weaker than compiling the
27+
* output — it cannot catch a NEW phantom, only the return of this one and the
28+
* loss of its replacement — and the stronger instrument is filed separately.
29+
*
30+
* WHY THE SIDE-EFFECT IMPORT IS THE CORRECT SPELLING (measured, not recalled).
31+
* `@object-ui/components` declares `sideEffects: true`, its barrel runs
32+
* `import './renderers'` under the comment `Register all ObjectUI renderers
33+
* (side-effects)`, and its built `dist/index.js` carries 114 module-scope
34+
* `register(` call sites. Its built `dist/index.d.ts` exports exactly one
35+
* `register*` name — `registerPlaceholders`. There is no registration function
36+
* to call. Same spelling the root README landed for objectui#7417.
37+
*/
38+
39+
import { readFileSync } from 'node:fs';
40+
import { dirname, resolve } from 'node:path';
41+
import { fileURLToPath } from 'node:url';
42+
import { describe, expect, it } from 'vitest';
43+
44+
const EXTENSION_SRC = readFileSync(
45+
resolve(dirname(fileURLToPath(import.meta.url)), '../extension.ts'),
46+
'utf8'
47+
);
48+
49+
/**
50+
* The body of the template literal `generateReactComponent()` returns — i.e.
51+
* the text of the file the user receives, with the schema still uninterpolated.
52+
*
53+
* Every failure mode throws with "this pin needs rewriting, not deleting": if
54+
* the generator is refactored, the pin has to be pointed at the new shape, and
55+
* a silently-skipped assertion is exactly the failure this file exists to stop.
56+
*/
57+
function generatedFileTemplate(): string {
58+
const fn = EXTENSION_SRC.indexOf('function generateReactComponent');
59+
if (fn < 0) {
60+
throw new Error(
61+
'generateReactComponent() is gone from extension.ts — this pin needs rewriting, not deleting.'
62+
);
63+
}
64+
const open = EXTENSION_SRC.indexOf('return `', fn);
65+
if (open < 0) {
66+
throw new Error(
67+
'generateReactComponent() no longer returns a template literal — this pin needs rewriting, not deleting.'
68+
);
69+
}
70+
const start = open + 'return `'.length;
71+
const close = EXTENSION_SRC.indexOf('`;', start);
72+
if (close < 0) {
73+
throw new Error(
74+
'unterminated template literal in generateReactComponent() — this pin needs rewriting, not deleting.'
75+
);
76+
}
77+
return EXTENSION_SRC.slice(start, close);
78+
}
79+
80+
describe('Export to React — the generated file preamble (objectui#7837)', () => {
81+
it('names no `registerDefaultRenderers`, which @object-ui/components does not export', () => {
82+
expect(generatedFileTemplate()).not.toContain('registerDefaultRenderers');
83+
// Belt and braces: the identifier is absent from the whole module, so a
84+
// second copy cannot reappear in a helper the template interpolates.
85+
expect(EXTENSION_SRC).not.toContain('registerDefaultRenderers');
86+
});
87+
88+
it('imports @object-ui/components for its side effect, with no named binding', () => {
89+
const template = generatedFileTemplate();
90+
expect(template).toContain("import '@object-ui/components';");
91+
// A named import from that package is how the defect was spelled. Assert
92+
// the SHAPE is gone, not just the one identifier, so the next phantom off
93+
// that specifier fails here too.
94+
expect(template).not.toMatch(/import\s*\{[^}]*\}\s*from\s*'@object-ui\/components'/);
95+
});
96+
97+
it('still imports SchemaRenderer from @object-ui/react — the renderer it calls', () => {
98+
const template = generatedFileTemplate();
99+
expect(template).toContain("import { SchemaRenderer } from '@object-ui/react';");
100+
expect(template).toContain('<SchemaRenderer schema={schema} />');
101+
});
102+
});

‎packages/vscode-extension/src/extension.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,9 @@ function generateReactComponent(schema: any): string {
227227

228228
return `import React from 'react';
229229
import { SchemaRenderer } from '@object-ui/react';
230-
import { registerDefaultRenderers } from '@object-ui/components';
231-
232-
// Register default components once
233-
registerDefaultRenderers();
230+
// Importing the package registers every default renderer as a side effect —
231+
// there is no separate registration call.
232+
import '@object-ui/components';
234233
235234
const schema = ${schemaJson};
236235
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
// Type-checks this package's TESTS, which `tsconfig.json` excludes.
3+
//
4+
// Why the exclusion exists and why it was a hole: `tsconfig.json` is the
5+
// package BUILD (`rootDir: ./src`, `declaration`, `outDir: ./dist`), so a
6+
// test file left in that program would emit into `dist` and ship inside the
7+
// `.vsix`. Excluding it keeps the build honest — but until this project
8+
// existed, no `tsc` invocation read the test at all, and an unchecked test
9+
// can assert a contract the compiler never checked and then read as evidence
10+
// that the contract holds. Same shape and same reason as the 35 sibling
11+
// `tsconfig.test.json` files; `scripts/check-type-check-coverage.mjs`
12+
// enforces the chaining off `type-check`, since a config nothing runs is the
13+
// objectui#3009 failure itself.
14+
"extends": "./tsconfig.json",
15+
"compilerOptions": {
16+
// A checking project, never an emitting one.
17+
"noEmit": true,
18+
"declaration": false,
19+
"declarationMap": false,
20+
"sourceMap": false,
21+
// The BUILD is CommonJS (`module: node16`, no `"type": "module"`), which is
22+
// what tsup emits for VS Code. Vitest is not that program: it loads these
23+
// files as ESM, so `import.meta.url` — the only stable way for a test to
24+
// locate its sibling source on disk — is legal there and TS1470 under the
25+
// build's CommonJS target. This project therefore describes the runtime the
26+
// tests actually run in, and the build config keeps describing the .vsix.
27+
"module": "ESNext",
28+
"moduleResolution": "Bundler"
29+
},
30+
// Both fields are overridden, not merged: `tsconfig.json`'s `exclude` lists
31+
// `**/*.test.ts`, so inheriting it would leave this project with no inputs —
32+
// green, and reading nothing.
33+
"include": ["src/**/*.test.ts"],
34+
"exclude": ["node_modules", "dist"]
35+
}

0 commit comments

Comments
 (0)