Skip to content

Commit 8845a31

Browse files
ntuckercursoragent
andauthored
fix(website): Strip multiline imports in playground transformCode (#4031)
* fix(website): Strip multiline imports in playground transformCode Prettier-wrapped import { … } from blocks were only partially removed, leaving orphan } from '…' syntax that broke GraphQL live previews. Also include Playground tests in the ReactDOM Jest project. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(website): Match side-effect imports in transformCode Require whitespace or a from-clause before the module string so import './setup' is stripped like other static imports. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): Persist Playground tests without breaking CircleCI workspace CircleCI attach_workspace omits website/, so a hard-coded Jest root failed validation. Only add the Playground root when present, and persist that path so ReactDOM CI still runs transformCode/codeModel. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 93555f8 commit 8845a31

4 files changed

Lines changed: 144 additions & 5 deletions

File tree

.circleci/config.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ jobs:
130130
- project/node_modules
131131
- project/packages
132132
- project/scripts
133+
# Playground unit tests (transformCode, codeModel); rest of website omitted
134+
- project/website/src/components/Playground
133135
- project/.yarnrc.yml
134136
- project/babel.config.js
135137
- project/eslint.config.mjs

jest.config.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
process.env.ANANSI_JEST_BABELCONFIG = 'babel.config.js';
22
process.env.ANANSI_JEST_TSCONFIG = 'tsconfig.test.json';
33

4+
const fs = require('fs');
45
const path = require('path');
56

67
const baseConfig = {
@@ -56,11 +57,24 @@ const packages = [
5657
'test',
5758
];
5859

60+
// CircleCI persist_to_workspace omits most of website/; only include this root
61+
// when the tree is present (full checkout / when CI persists Playground).
62+
const playgroundRoot = path.join(
63+
__dirname,
64+
'website/src/components/Playground',
65+
);
66+
const reactDomRoots = [
67+
...packages.map(pkgName => `<rootDir>/packages/${pkgName}/src`),
68+
...(fs.existsSync(playgroundRoot) ?
69+
['<rootDir>/website/src/components/Playground']
70+
: []),
71+
];
72+
5973
const projects = [
6074
{
6175
...baseConfig,
6276
rootDir: __dirname,
63-
roots: packages.map(pkgName => `<rootDir>/packages/${pkgName}/src`),
77+
roots: reactDomRoots,
6478
displayName: 'ReactDOM',
6579
setupFiles: ['<rootDir>/scripts/testSetup.js'],
6680
testEnvironment: 'jsdom',
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/// <reference types="jest" />
2+
3+
import transformCode from '../transformCode';
4+
5+
describe('transformCode', () => {
6+
test('removes Prettier multiline imports', () => {
7+
expect(
8+
transformCode(
9+
`import {
10+
GQLEntity,
11+
GQLEndpoint,
12+
} from '@data-client/graphql';
13+
const x = 1;`,
14+
),
15+
).toBe('const x = 1;');
16+
});
17+
18+
test('removes type-bearing and relative imports', () => {
19+
expect(
20+
transformCode(
21+
`import { type Post, PostResource } from './resources';
22+
render(PostResource);`,
23+
),
24+
).toBe('render(PostResource);');
25+
});
26+
27+
test('removes side-effect and import-type statements', () => {
28+
expect(
29+
transformCode(
30+
`import './setup'
31+
import type { User } from './types'
32+
const user: User = value;`,
33+
),
34+
).toBe('const user: User = value;');
35+
});
36+
37+
test('strips export prefixes from declarations', () => {
38+
expect(
39+
transformCode(
40+
`export type User = { id: string };
41+
export const user = {} as User;
42+
export default function App() {}`,
43+
),
44+
).toBe(
45+
`type User = { id: string };
46+
const user = {} as User;
47+
function App() {}`,
48+
);
49+
});
50+
51+
test('removes export lists and re-exports', () => {
52+
expect(
53+
transformCode(
54+
`const X = 1;
55+
export { X };
56+
export type { User };
57+
export { Y } from './y';
58+
export * from './z';`,
59+
),
60+
).toBe('const X = 1;\n');
61+
});
62+
63+
test('keeps GraphQL resource bodies after multiline import strip', () => {
64+
const input = `import {
65+
GQLEndpoint,
66+
GQLEntity,
67+
Collection,
68+
} from '@data-client/graphql';
69+
70+
const gql = new GQLEndpoint('/');
71+
72+
export class User extends GQLEntity {
73+
name = '';
74+
}
75+
76+
export const UserResource = {
77+
get: gql.query(\`query GetUser($id: ID!) { user(id: $id) { id } }\`, { user: User }),
78+
};`;
79+
80+
const output = transformCode(input);
81+
expect(output).not.toMatch(/\bfrom\b/);
82+
expect(output).toContain("const gql = new GQLEndpoint('/');");
83+
expect(output).toContain('class User extends GQLEntity');
84+
expect(output).toContain('const UserResource =');
85+
expect(output.trimStart().startsWith('GQLEndpoint')).toBe(false);
86+
});
87+
88+
test('does not strip import.meta usage', () => {
89+
expect(
90+
transformCode(
91+
`const url = import.meta.url;
92+
const x = 'value';`,
93+
),
94+
).toBe(
95+
`const url = import.meta.url;
96+
const x = 'value';`,
97+
);
98+
});
99+
100+
test('removes namespace re-exports', () => {
101+
expect(transformCode(`export * as ns from './x';\nconst y = 1;`)).toBe(
102+
'const y = 1;',
103+
);
104+
});
105+
});
Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
const transformCode = (code: string) => {
2-
return code.replaceAll(/^(import.+$|export (default )?)/gm, '');
3-
};
4-
export default transformCode;
1+
/**
2+
* Strip ESM import/export wrappers so concatenated playground documents can run
3+
* under react-live (scope provides package imports; earlier files provide locals).
4+
*
5+
* Order matters: remove complete import / export-list declarations first, then
6+
* strip `export` prefixes from remaining declarations. That keeps Prettier
7+
* multiline imports from leaving orphan `} from '…'` syntax.
8+
*/
9+
const STATIC_IMPORT =
10+
/^[ \t]*import(?!\s*\(|\.)(?:(?:[\s\S]*?\bfrom\s*)|\s+)["'][^"'\r\n]+["']\s*;?[ \t]*(?:\/\/[^\r\n]*)?(?:\r?\n|$)/gm;
11+
12+
const EXPORT_LIST =
13+
/^[ \t]*export\s+(?:type\s+)?(?:\{[\s\S]*?\}|\*(?:\s+as\s+\w+)?)\s*(?:from\s*["'][^"'\r\n]+["'])?\s*;?[ \t]*(?:\/\/[^\r\n]*)?(?:\r?\n|$)/gm;
14+
15+
const EXPORT_PREFIX = /^[ \t]*export[ \t]+(?:default[ \t]+)?/gm;
16+
17+
export default function transformCode(code: string): string {
18+
return code
19+
.replaceAll(STATIC_IMPORT, '')
20+
.replaceAll(EXPORT_LIST, '')
21+
.replaceAll(EXPORT_PREFIX, '');
22+
}

0 commit comments

Comments
 (0)