From d44c983f2c385df82df88d0d4534a66a5a581e65 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 10 Oct 2021 16:50:54 -0400 Subject: [PATCH 1/3] Declare types for node builtin modules in REPL so you do not need to import them --- src/repl.ts | 20 ++++++++++++++++++++ src/test/repl/repl.spec.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/repl.ts b/src/repl.ts index 03d4d97d5..31429e4aa 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -14,6 +14,7 @@ import { Console } from 'console'; import * as assert from 'assert'; import type * as tty from 'tty'; import type * as Module from 'module'; +import { builtinModules } from 'module'; // Lazy-loaded. let _processTopLevelAwait: (src: string) => string | null; @@ -356,6 +357,25 @@ export function createRepl(options: CreateReplOptions = {}) { if (forceToBeModule) { state.input += 'export {};void 0;\n'; } + + // Declare node builtins. + // Skip the same builtins as `addBuiltinLibsToObject`: + // 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 const ${name}: typeof import('${name}');declare import ${name} = require('${name}')` + ) + .join(';')}\n`; } reset(); diff --git a/src/test/repl/repl.spec.ts b/src/test/repl/repl.spec.ts index b30f1b790..2ea11cddb 100644 --- a/src/test/repl/repl.spec.ts +++ b/src/test/repl/repl.spec.ts @@ -212,7 +212,7 @@ test.suite('top level await', (_test) => { expect(stdout).toBe('> > '); expect(stderr.replace(/\r\n/g, '\n')).toBe( - '.ts(2,7): error TS2322: ' + + '.ts(4,7): error TS2322: ' + (semver.gte(ts.version, '4.0.0') ? `Type 'number' is not assignable to type 'string'.\n` : `Type '1' is not assignable to type 'string'.\n`) + @@ -411,3 +411,30 @@ test.suite( ); } ); + +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") + 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`); +}); From e79e8e7fb42f970e11ba41afb303fedae77aa96a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 10 Oct 2021 17:19:27 -0400 Subject: [PATCH 2/3] fix --- src/test/repl/repl-environment.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/repl/repl-environment.spec.ts b/src/test/repl/repl-environment.spec.ts index 4becc6f10..9071688c6 100644 --- a/src/test/repl/repl-environment.spec.ts +++ b/src/test/repl/repl-environment.spec.ts @@ -94,7 +94,7 @@ test.suite( } ); - const declareGlobals = `declare var replReport: any, stdinReport: any, evalReport: any, restReport: any, global: any, __filename: any, __dirname: any, module: any, exports: any, fs: any;`; + const declareGlobals = `declare var replReport: any, stdinReport: any, evalReport: any, restReport: any, global: any, __filename: any, __dirname: any, module: any, exports: any;`; function setReportGlobal(type: 'repl' | 'stdin' | 'eval') { return ` ${declareGlobals} @@ -107,7 +107,7 @@ test.suite( modulePaths: typeof module !== 'undefined' && [...module.paths], exportsTest: typeof exports !== 'undefined' ? module.exports === exports : null, stackTest: new Error().stack!.split('\\n')[1], - moduleAccessorsTest: typeof fs === 'undefined' ? null : fs === require('fs'), + moduleAccessorsTest: eval('typeof fs') === 'undefined' ? null : eval('fs') === require('fs'), argv: [...process.argv] }; `.replace(/\n/g, ''); @@ -203,7 +203,7 @@ test.suite( exportsTest: true, // Note: vanilla node uses different name. See #1360 stackTest: expect.stringContaining( - ` at ${join(TEST_DIR, '.ts')}:2:` + ` at ${join(TEST_DIR, '.ts')}:4:` ), moduleAccessorsTest: true, argv: [tsNodeExe], @@ -356,7 +356,7 @@ test.suite( exportsTest: true, // Note: vanilla node uses different name. See #1360 stackTest: expect.stringContaining( - ` at ${join(TEST_DIR, '.ts')}:2:` + ` at ${join(TEST_DIR, '.ts')}:4:` ), moduleAccessorsTest: true, argv: [tsNodeExe], From c34588eca2ea1ea736de2aac2f828c16ed986324 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sun, 10 Oct 2021 18:27:24 -0400 Subject: [PATCH 3/3] fix --- src/index.ts | 18 +++++++++--------- src/repl.ts | 5 +---- src/test/helpers.ts | 2 ++ src/test/index.spec.ts | 2 +- src/test/repl/node-repl-tla.ts | 2 +- src/test/repl/repl.spec.ts | 4 ++-- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/index.ts b/src/index.ts index 5f9c5c9aa..7c3f6af07 100644 --- a/src/index.ts +++ b/src/index.ts @@ -672,6 +672,15 @@ export function create(rawOptions: CreateOptions = {}): Service { }); } + /** + * True if require() hooks should interop with experimental ESM loader. + * Enabled explicitly via a flag since it is a breaking change. + */ + let experimentalEsmLoader = false; + function enableExperimentalEsmLoaderInterop() { + experimentalEsmLoader = true; + } + // Install source map support and read from memory cache. installSourceMapSupport(); function installSourceMapSupport() { @@ -1254,15 +1263,6 @@ export function create(rawOptions: CreateOptions = {}): Service { }); } - /** - * True if require() hooks should interop with experimental ESM loader. - * Enabled explicitly via a flag since it is a breaking change. - */ - let experimentalEsmLoader = false; - function enableExperimentalEsmLoaderInterop() { - experimentalEsmLoader = true; - } - return { [TS_NODE_SERVICE_BRAND]: true, ts, diff --git a/src/repl.ts b/src/repl.ts index 31429e4aa..0ef51017d 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -371,10 +371,7 @@ export function createRepl(options: CreateReplOptions = {}) { !name.includes('/') && !['console', 'module', 'process'].includes(name) ) - .map( - (name) => - `declare const ${name}: typeof import('${name}');declare import ${name} = require('${name}')` - ) + .map((name) => `declare import ${name} = require('${name}')`) .join(';')}\n`; } diff --git a/src/test/helpers.ts b/src/test/helpers.ts index 00b59d12f..245e0a924 100644 --- a/src/test/helpers.ts +++ b/src/test/helpers.ts @@ -40,6 +40,8 @@ export const CMD_ESM_LOADER_WITHOUT_PROJECT = `node ${EXPERIMENTAL_MODULES_FLAG} // `createRequire` does not exist on older node versions export const testsDirRequire = createRequire(join(TEST_DIR, 'index.js')); +export const ts = testsDirRequire('typescript'); + export const xfs = new NodeFS(fs); /** Pass to `test.context()` to get access to the ts-node API under test */ diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index 3307ae73d..730d1a3be 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -3,7 +3,7 @@ import * as expect from 'expect'; import { join, resolve, sep as pathSep } from 'path'; import { tmpdir } from 'os'; import semver = require('semver'); -import ts = require('typescript'); +import { ts } from './helpers'; import { lstatSync, mkdtempSync } from 'fs'; import { npath } from '@yarnpkg/fslib'; import type _createRequire from 'create-require'; diff --git a/src/test/repl/node-repl-tla.ts b/src/test/repl/node-repl-tla.ts index 210d22ec0..616237926 100644 --- a/src/test/repl/node-repl-tla.ts +++ b/src/test/repl/node-repl-tla.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import type { Key } from 'readline'; import { Stream } from 'stream'; import semver = require('semver'); -import ts = require('typescript'); +import { ts } from '../helpers'; import type { ContextWithTsNodeUnderTest } from './helpers'; interface SharedObjects extends ContextWithTsNodeUnderTest { diff --git a/src/test/repl/repl.spec.ts b/src/test/repl/repl.spec.ts index 2ea11cddb..2ef41d15d 100644 --- a/src/test/repl/repl.spec.ts +++ b/src/test/repl/repl.spec.ts @@ -1,4 +1,4 @@ -import ts = require('typescript'); +import { ts } from '../helpers'; import semver = require('semver'); import * as expect from 'expect'; import { @@ -414,7 +414,7 @@ test.suite( 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") + `util.promisify(setTimeout)("should not be a string" as string) type Duplex = stream.Duplex const s = stream 'done'`,