Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
c9958de7d6 fix(parser): support Flow's import typeof and export typeof syntax
Strip Flow's `import typeof` and `export typeof` statements during
transpilation, analogous to how TypeScript's `import type` is handled.
This fixes parsing errors in React Native / Expo projects that use
Flow type annotations in `.js` files.

Closes #16945

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-19 09:39:22 +00:00
2 changed files with 199 additions and 0 deletions

View File

@@ -67,6 +67,30 @@ pub fn ParseStmt(
return p.parseStmt(opts);
},
// "export typeof {foo} from 'bar'" (Flow)
T.t_typeof => {
if (!opts.is_module_scope and !(opts.is_namespace_scope or !opts.is_typescript_declare)) {
try p.lexer.unexpected();
return error.SyntaxError;
}
try p.lexer.next();
switch (p.lexer.token) {
.t_open_brace => {
// "export typeof {foo} from 'bar';"
_ = try p.parseExportClause();
try p.lexer.expectContextualKeyword("from");
_ = try p.parsePath();
try p.lexer.expectOrInsertSemicolon();
return p.s(S.Empty{}, loc);
},
else => {},
}
try p.lexer.unexpected();
return error.SyntaxError;
},
T.t_identifier => {
if (p.lexer.isContextualKeyword("let")) {
opts.is_export = true;
@@ -987,6 +1011,52 @@ pub fn ParseStmt(
};
try p.lexer.expectContextualKeyword("from");
},
// "import typeof foo from 'path'" (Flow)
// "import typeof * as foo from 'path'" (Flow)
// "import typeof {foo} from 'path'" (Flow)
.t_typeof => {
if (!opts.is_module_scope and (!opts.is_namespace_scope)) {
try p.lexer.unexpected();
return error.SyntaxError;
}
// Skip over typeof-only imports (Flow syntax, analogous to TypeScript's "import type")
try p.lexer.next();
switch (p.lexer.token) {
.t_identifier => {
// "import typeof foo from 'bar';"
if (!strings.eqlComptime(p.lexer.identifier, "from")) {
try p.lexer.next();
try p.lexer.expectContextualKeyword("from");
_ = try p.parsePath();
try p.lexer.expectOrInsertSemicolon();
return p.s(S.Empty{}, loc);
}
},
.t_asterisk => {
// "import typeof * as foo from 'bar';"
try p.lexer.next();
try p.lexer.expectContextualKeyword("as");
try p.lexer.expect(.t_identifier);
try p.lexer.expectContextualKeyword("from");
_ = try p.parsePath();
try p.lexer.expectOrInsertSemicolon();
return p.s(S.Empty{}, loc);
},
.t_open_brace => {
// "import typeof {foo} from 'bar';"
_ = try p.parseImportClause();
try p.lexer.expectContextualKeyword("from");
_ = try p.parsePath();
try p.lexer.expectOrInsertSemicolon();
return p.s(S.Empty{}, loc);
},
else => {},
}
try p.lexer.unexpected();
return error.SyntaxError;
},
.t_identifier => {
// "import defaultItem from 'path'"
// "import foo = bar"

View File

@@ -0,0 +1,129 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/16945
// Flow's `import typeof` syntax should be stripped during transpilation,
// analogous to TypeScript's `import type`.
test("import typeof default from module", async () => {
using dir = tempDir("16945", {
"flow_module.js": `
import typeof ActionSheetIOS from './action_sheet';
export default function hello() { return "hello"; }
`,
"action_sheet.js": `export default class ActionSheetIOS {}`,
"index.js": `import hello from './flow_module'; console.log(hello());`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("hello");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
test("import typeof with named imports", async () => {
using dir = tempDir("16945", {
"flow_module.js": `
import typeof { Foo, Bar } from './types';
export default function hello() { return "named"; }
`,
"types.js": `export class Foo {} export class Bar {}`,
"index.js": `import hello from './flow_module'; console.log(hello());`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("named");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
test("import typeof with namespace import", async () => {
using dir = tempDir("16945", {
"flow_module.js": `
import typeof * as Types from './types';
export default function hello() { return "namespace"; }
`,
"types.js": `export class Foo {}`,
"index.js": `import hello from './flow_module'; console.log(hello());`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("namespace");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
test("export typeof is stripped", async () => {
using dir = tempDir("16945", {
"flow_module.js": `
export typeof { Foo } from './types';
export default function hello() { return "export-typeof"; }
`,
"types.js": `export class Foo {}`,
"index.js": `import hello from './flow_module'; console.log(hello());`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "index.js"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout.trim()).toBe("export-typeof");
expect(stderr).toBe("");
expect(exitCode).toBe(0);
});
test("bun build --no-bundle strips import typeof", async () => {
using dir = tempDir("16945", {
"flow_module.js": `
import typeof ActionSheetIOS from './action_sheet';
export default function hello() { return "hello"; }
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "--no-bundle", `${dir}/flow_module.js`],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// The import typeof should be stripped, so it shouldn't appear in output
expect(stdout).not.toContain("typeof");
expect(stdout).toContain("hello");
expect(exitCode).toBe(0);
});