Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REPL: Omit declare import prefix in transpile-only mode to avoid breaking SWC transpiler #1541

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,8 @@ export interface Service {
installSourceMapSupport(): void;
/** @internal */
enableExperimentalEsmLoaderInterop(): void;
/** @internal */
transpileOnly: boolean;
}

/**
Expand Down Expand Up @@ -1330,6 +1332,7 @@ export function create(rawOptions: CreateOptions = {}): Service {
addDiagnosticFilter,
installSourceMapSupport,
enableExperimentalEsmLoaderInterop,
transpileOnly,
};
}

Expand Down
24 changes: 14 additions & 10 deletions src/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,16 +368,20 @@ export function createRepl(options: CreateReplOptions = {}) {
// those starting with _
// those containing /
// those that already exist as globals
// Intentionally suppress type errors in case @types/node does not declare any of them.
state.input += `// @ts-ignore\n${builtinModules
.filter(
(name) =>
!name.startsWith('_') &&
!name.includes('/') &&
!['console', 'module', 'process'].includes(name)
)
.map((name) => `declare import ${name} = require('${name}')`)
.join(';')}\n`;
// Intentionally suppress type errors in case @types/node does not declare any of them, and because
// `declare import` is technically invalid syntax.
// Avoid this when in transpileOnly, because third-party transpilers may not handle `declare import`.
if (!service?.transpileOnly) {
state.input += `// @ts-ignore\n${builtinModules
.filter(
(name) =>
!name.startsWith('_') &&
!name.includes('/') &&
!['console', 'module', 'process'].includes(name)
)
.map((name) => `declare import ${name} = require('${name}')`)
.join(';')}\n`;
}
}

reset();
Expand Down
73 changes: 49 additions & 24 deletions src/test/repl/repl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,29 +455,54 @@ test.suite('REPL works with traceResolution', (test) => {
);
});

test.serial('REPL declares types for node built-ins within REPL', async (t) => {
const { stdout, stderr } = await t.context.executeInRepl(
`util.promisify(setTimeout)("should not be a string" as string)
type Duplex = stream.Duplex
const s = stream
'done'`,
{
registerHooks: true,
waitPattern: `done`,
startInternalOptions: {
useGlobal: false,
},
}
);
test.suite('REPL declares types for node built-ins within REPL', (test) => {
test.runSerially();
test('enabled when typechecking', async (t) => {
const { stdout, stderr } = await t.context.executeInRepl(
`util.promisify(setTimeout)("should not be a string" as string)
type Duplex = stream.Duplex
const s = stream
'done'`,
{
registerHooks: true,
waitPattern: `done`,
startInternalOptions: {
useGlobal: false,
},
}
);

// Assert that we receive a typechecking error about improperly using
// `util.promisify` but *not* an error about the absence of `util`
expect(stderr).not.toMatch("Cannot find name 'util'");
expect(stderr).toMatch(
"Argument of type 'string' is not assignable to parameter of type 'number'"
);
// Assert that both types and values can be used without error
expect(stderr).not.toMatch("Cannot find namespace 'stream'");
expect(stderr).not.toMatch("Cannot find name 'stream'");
expect(stdout).toMatch(`done`);
// Assert that we receive a typechecking error about improperly using
// `util.promisify` but *not* an error about the absence of `util`
expect(stderr).not.toMatch("Cannot find name 'util'");
expect(stderr).toMatch(
"Argument of type 'string' is not assignable to parameter of type 'number'"
);
// Assert that both types and values can be used without error
expect(stderr).not.toMatch("Cannot find namespace 'stream'");
expect(stderr).not.toMatch("Cannot find name 'stream'");
expect(stdout).toMatch(`done`);
});

test('disabled in transpile-only mode, to avoid breaking third-party SWC transpiler which rejects `declare import` syntax', async (t) => {
const { stdout, stderr } = await t.context.executeInRepl(
`type Duplex = stream.Duplex
const s = stream
'done'`,
{
createServiceOpts: {
swc: true,
},
registerHooks: true,
waitPattern: `done`,
startInternalOptions: {
useGlobal: false,
},
}
);

// Assert that we do not get errors about `declare import` syntax from swc
expect(stdout).toBe("> undefined\n> undefined\n> 'done'\n");
expect(stderr).toBe('');
});
});