From 9184bab6d5cc4a2319df2bf3648df0a7a565804c Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 12 Jul 2021 21:42:53 -0400 Subject: [PATCH 1/8] empty commit From bc7fe6ed3b2f85703bc548d66368a24abca1c425 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 12 Jul 2021 22:15:10 -0400 Subject: [PATCH 2/8] Add mechanism to ignore diagnostics in certain files; use it to ignore annoying diagnostics in the REPL --- src/index.ts | 42 ++++++++++++++++++++++++++++++------------ src/repl.ts | 10 ++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index f770eceb3..184c1726f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -410,6 +410,8 @@ export interface Service { configFilePath: string | undefined; /** @internal */ moduleTypeClassifier: ModuleTypeClassifier; + /** @internal */ + addDiagnosticFilter(filter: DiagnosticFilter): void; } /** @@ -419,6 +421,16 @@ export interface Service { */ export type Register = Service; +/** @internal */ +export interface DiagnosticFilter { + /** if true, filter applies to all files */ + appliesToAllFiles: boolean; + /** Filter applies onto to these filenames. Only used if appliesToAllFiles is false */ + filenamesAbsolute: string[]; + /** these diagnostic codes are ignored */ + diagnosticsIgnored: number[]; +} + /** @internal */ export function getExtensions(config: _ts.ParsedCommandLine) { const tsExtensions = ['.ts']; @@ -516,16 +528,20 @@ export function create(rawOptions: CreateOptions = {}): Service { const transpileOnly = options.transpileOnly === true && options.typeCheck !== true; const transformers = options.transformers || undefined; - const ignoreDiagnostics = [ - 6059, // "'rootDir' is expected to contain all source files." - 18002, // "The 'files' list in config file is empty." - 18003, // "No inputs were found in config file." - ...(options.ignoreDiagnostics || []), - ].map(Number); + const diagnosticFilters: Array = [{ + appliesToAllFiles: true, + filenamesAbsolute: [], + diagnosticsIgnored: [ + 6059, // "'rootDir' is expected to contain all source files." + 18002, // "The 'files' list in config file is empty." + 18003, // "No inputs were found in config file." + ...(options.ignoreDiagnostics || []), + ].map(Number) + }]; const configDiagnosticList = filterDiagnostics( config.errors, - ignoreDiagnostics + diagnosticFilters ); const outputCache = new Map< string, @@ -804,7 +820,7 @@ export function create(rawOptions: CreateOptions = {}): Service { const diagnosticList = filterDiagnostics( diagnostics, - ignoreDiagnostics + diagnosticFilters ); if (diagnosticList.length) reportTSError(diagnosticList); @@ -963,7 +979,7 @@ export function create(rawOptions: CreateOptions = {}): Service { const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile); const diagnosticList = filterDiagnostics( diagnostics, - ignoreDiagnostics + diagnosticFilters ); if (diagnosticList.length) reportTSError(diagnosticList); @@ -1079,7 +1095,7 @@ export function create(rawOptions: CreateOptions = {}): Service { const diagnosticList = filterDiagnostics( result.diagnostics || [], - ignoreDiagnostics + diagnosticFilters ); if (diagnosticList.length) reportTSError(diagnosticList); @@ -1306,9 +1322,11 @@ function updateSourceMap(sourceMapText: string, fileName: string) { */ function filterDiagnostics( diagnostics: readonly _ts.Diagnostic[], - ignore: number[] + filters: DiagnosticFilter[] ) { - return diagnostics.filter((x) => ignore.indexOf(x.code) === -1); + return diagnostics.filter(d => + filters.every(f => (!f.appliesToAllFiles && f.filenamesAbsolute.indexOf(d.file?.fileName!) === -1) || f.diagnosticsIgnored.indexOf(d.code) === -1) + ); } /** diff --git a/src/repl.ts b/src/repl.ts index fca8b1bc6..d03687e28 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -103,6 +103,16 @@ export function createRepl(options: CreateReplOptions = {}) { function setService(_service: Service) { service = _service; + // Ignore these diagnostics when they occur in the virtual REPL file + service.addDiagnosticFilter({ + appliesToAllFiles: false, + filenamesAbsolute: [state.path], + diagnosticsIgnored: [ + 2393, // Duplicate function implementation: https://github.com/TypeStrong/ts-node/issues/729 + 6133, // is declared but its value is never read. https://github.com/TypeStrong/ts-node/issues/850 + 7027, // Unreachable code detected. https://github.com/TypeStrong/ts-node/issues/469 + ] + }); } function evalCode(code: string) { From ab7a6885c9b8494f08a609b278d94b74ac70c9ba Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 12 Jul 2021 22:19:14 -0400 Subject: [PATCH 3/8] misc cleanup of commented-out code --- src/bin.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 4a4957d80..58fb8c6ef 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -197,11 +197,6 @@ export function main( let evalStuff: VirtualFileState | undefined; let replStuff: VirtualFileState | undefined; let stdinStuff: VirtualFileState | undefined; - // let evalService: ReplService | undefined; - // let replState: EvalState | undefined; - // let replService: ReplService | undefined; - // let stdinState: EvalState | undefined; - // let stdinService: ReplService | undefined; let evalAwarePartialHost: EvalAwarePartialHost | undefined = undefined; if (executeEval) { const state = new EvalState(join(cwd, EVAL_FILENAME)); From f331a4c0802215fcc8d98acc3ec4cc5af1594833 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 12 Jul 2021 22:20:02 -0400 Subject: [PATCH 4/8] Apply the new diagnostic filtering only to interactive REPL; not to [stdin] nor [eval] --- src/bin.ts | 2 ++ src/repl.ts | 27 +++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/bin.ts b/src/bin.ts index 58fb8c6ef..c6424647e 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -205,6 +205,7 @@ export function main( repl: createRepl({ state, composeWithEvalAwarePartialHost: evalAwarePartialHost, + ignoreExtraDiagnostics: false, }), }; ({ evalAwarePartialHost } = evalStuff.repl); @@ -220,6 +221,7 @@ export function main( repl: createRepl({ state, composeWithEvalAwarePartialHost: evalAwarePartialHost, + ignoreExtraDiagnostics: false, }), }; ({ evalAwarePartialHost } = stdinStuff.repl); diff --git a/src/repl.ts b/src/repl.ts index d03687e28..1fc280127 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -59,6 +59,11 @@ export interface CreateReplOptions { stderr?: NodeJS.WritableStream; /** @internal */ composeWithEvalAwarePartialHost?: EvalAwarePartialHost; + /** + * @internal + * Ignore diagnostics that are annoying when interactively entering input line-by-line + */ + ignoreExtraDiagnostics?: boolean; } /** @@ -86,6 +91,7 @@ export function createRepl(options: CreateReplOptions = {}) { stdout === process.stdout && stderr === process.stderr ? console : new Console(stdout, stderr); + const {ignoreExtraDiagnostics = true} = options; const replService: ReplService = { state: options.state ?? new EvalState(join(process.cwd(), EVAL_FILENAME)), @@ -103,16 +109,17 @@ export function createRepl(options: CreateReplOptions = {}) { function setService(_service: Service) { service = _service; - // Ignore these diagnostics when they occur in the virtual REPL file - service.addDiagnosticFilter({ - appliesToAllFiles: false, - filenamesAbsolute: [state.path], - diagnosticsIgnored: [ - 2393, // Duplicate function implementation: https://github.com/TypeStrong/ts-node/issues/729 - 6133, // is declared but its value is never read. https://github.com/TypeStrong/ts-node/issues/850 - 7027, // Unreachable code detected. https://github.com/TypeStrong/ts-node/issues/469 - ] - }); + if(ignoreExtraDiagnostics) { + service.addDiagnosticFilter({ + appliesToAllFiles: false, + filenamesAbsolute: [state.path], + diagnosticsIgnored: [ + 2393, // Duplicate function implementation: https://github.com/TypeStrong/ts-node/issues/729 + 6133, // is declared but its value is never read. https://github.com/TypeStrong/ts-node/issues/850 + 7027, // Unreachable code detected. https://github.com/TypeStrong/ts-node/issues/469 + ] + }); + } } function evalCode(code: string) { From 889b6c88920c29fff3157a63fcb6685a5c320602 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 12 Jul 2021 22:41:24 -0400 Subject: [PATCH 5/8] Fix missing addDiagnosticFilter implementation; lint format --- src/index.ts | 36 ++++++++++++++++++++++++------------ src/repl.ts | 6 +++--- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/index.ts b/src/index.ts index 184c1726f..3bb3c296a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -528,16 +528,18 @@ export function create(rawOptions: CreateOptions = {}): Service { const transpileOnly = options.transpileOnly === true && options.typeCheck !== true; const transformers = options.transformers || undefined; - const diagnosticFilters: Array = [{ - appliesToAllFiles: true, - filenamesAbsolute: [], - diagnosticsIgnored: [ - 6059, // "'rootDir' is expected to contain all source files." - 18002, // "The 'files' list in config file is empty." - 18003, // "No inputs were found in config file." - ...(options.ignoreDiagnostics || []), - ].map(Number) - }]; + const diagnosticFilters: Array = [ + { + appliesToAllFiles: true, + filenamesAbsolute: [], + diagnosticsIgnored: [ + 6059, // "'rootDir' is expected to contain all source files." + 18002, // "The 'files' list in config file is empty." + 18003, // "No inputs were found in config file." + ...(options.ignoreDiagnostics || []), + ].map(Number), + }, + ]; const configDiagnosticList = filterDiagnostics( config.errors, @@ -1157,6 +1159,10 @@ export function create(rawOptions: CreateOptions = {}): Service { return true; }; + function addDiagnosticFilter(filter: DiagnosticFilter) { + diagnosticFilters.push(filter); + } + return { ts, config, @@ -1167,6 +1173,7 @@ export function create(rawOptions: CreateOptions = {}): Service { options, configFilePath, moduleTypeClassifier, + addDiagnosticFilter, }; } @@ -1324,8 +1331,13 @@ function filterDiagnostics( diagnostics: readonly _ts.Diagnostic[], filters: DiagnosticFilter[] ) { - return diagnostics.filter(d => - filters.every(f => (!f.appliesToAllFiles && f.filenamesAbsolute.indexOf(d.file?.fileName!) === -1) || f.diagnosticsIgnored.indexOf(d.code) === -1) + return diagnostics.filter((d) => + filters.every( + (f) => + (!f.appliesToAllFiles && + f.filenamesAbsolute.indexOf(d.file?.fileName!) === -1) || + f.diagnosticsIgnored.indexOf(d.code) === -1 + ) ); } diff --git a/src/repl.ts b/src/repl.ts index 1fc280127..5ad994432 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -91,7 +91,7 @@ export function createRepl(options: CreateReplOptions = {}) { stdout === process.stdout && stderr === process.stderr ? console : new Console(stdout, stderr); - const {ignoreExtraDiagnostics = true} = options; + const { ignoreExtraDiagnostics = true } = options; const replService: ReplService = { state: options.state ?? new EvalState(join(process.cwd(), EVAL_FILENAME)), @@ -109,7 +109,7 @@ export function createRepl(options: CreateReplOptions = {}) { function setService(_service: Service) { service = _service; - if(ignoreExtraDiagnostics) { + if (ignoreExtraDiagnostics) { service.addDiagnosticFilter({ appliesToAllFiles: false, filenamesAbsolute: [state.path], @@ -117,7 +117,7 @@ export function createRepl(options: CreateReplOptions = {}) { 2393, // Duplicate function implementation: https://github.com/TypeStrong/ts-node/issues/729 6133, // is declared but its value is never read. https://github.com/TypeStrong/ts-node/issues/850 7027, // Unreachable code detected. https://github.com/TypeStrong/ts-node/issues/469 - ] + ], }); } } From ea1737ab1c04e2ac96677f690d4e4a8454068a2a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 19 Jul 2021 13:32:46 -0400 Subject: [PATCH 6/8] WIP --- src/test/index.spec.ts | 138 ++++++++++++++++++----------------------- src/test/macros.ts | 111 +++++++++++++++++++++++++++++++++ src/test/testlib.ts | 8 ++- 3 files changed, 178 insertions(+), 79 deletions(-) create mode 100644 src/test/macros.ts diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index c8c8a7e4b..bc9eba1ff 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -33,38 +33,10 @@ import type * as Module from 'module'; import { PassThrough } from 'stream'; import * as getStream from 'get-stream'; import { once } from 'lodash'; +import { createMacrosAndHelpers, ExecMacroAssertionCallback } from './macros'; const xfs = new NodeFS(fs); -type TestExecReturn = { - stdout: string; - stderr: string; - err: null | ExecException; -}; -function exec( - cmd: string, - opts: ExecOptions = {} -): Promise & { child: ChildProcess } { - let childProcess!: ChildProcess; - return Object.assign( - new Promise((resolve, reject) => { - childProcess = childProcessExec( - cmd, - { - cwd: TEST_DIR, - ...opts, - }, - (error, stdout, stderr) => { - resolve({ err: error, stdout, stderr }); - } - ); - }), - { - child: childProcess, - } - ); -} - const ROOT_DIR = resolve(__dirname, '../..'); const DIST_DIR = resolve(__dirname, '..'); const TEST_DIR = join(__dirname, '../../tests'); @@ -81,6 +53,11 @@ const testsDirRequire = createRequire(join(TEST_DIR, 'index.js')); // Set after ts-node is installed locally let { register, create, VERSION, createRepl }: typeof tsNodeTypes = {} as any; +const { exec, createExecMacro } = createMacrosAndHelpers({ + test, + defaultCwd: TEST_DIR, +}); + // Pack and install ts-node locally, necessary to test package "exports" test.beforeAll(async () => { const totalTries = process.platform === 'win32' ? 5 : 1; @@ -101,7 +78,9 @@ test.beforeAll(async () => { }); test.suite('ts-node', (test) => { + /** Default `ts-node --project` invocation */ const cmd = `"${BIN_PATH}" --project "${PROJECT}"`; + /** Default `ts-node` invocation without `--project` */ const cmdNoProject = `"${BIN_PATH}"`; test('should export the correct version', () => { @@ -428,50 +407,53 @@ test.suite('ts-node', (test) => { return { stdin, stdout, stderr, replService, service }; } - // Serial because it's timing-sensitive - test.serial('REPL can be created via API', async () => { - const { stdin, stdout, stderr, replService } = createReplViaApi(); - replService.start(); - stdin.write('\nconst a = 123\n.type a\n'); - stdin.end(); - await promisify(setTimeout)(1e3); - stdout.end(); - stderr.end(); - expect(await getStream(stderr)).to.equal(''); - expect(await getStream(stdout)).to.equal( - '> undefined\n' + '> undefined\n' + '> const a: 123\n' + '> ' - ); + const execMacro = createExecMacro({ + cmd, }); + type ReplApiMacroAssertions = ( + stdout: string, + stderr: string + ) => Promise; + + const replApiMacro = test.macro( + (opts: { + input: string; + }, + assertions: ReplApiMacroAssertions + ) => async (t) => { + const { input } = opts; + const { stdin, stdout, stderr, replService } = createReplViaApi(); + replService.start(); + stdin.write(input); + stdin.end(); + await promisify(setTimeout)(1e3); + stdout.end(); + stderr.end(); + const stderrString = await getStream(stderr); + const stdoutString = await getStream(stdout); + await assertions(stdoutString, stderrString); + } + ); + + // Serial because it's timing-sensitive + test.serial('REPL can be created via API', + replApiMacro, + { + input: '\nconst a = 123\n.type a\n', + }, + async (stdout, stderr) => { + expect(stderr).to.equal(''); + expect(stdout).to.equal( + '> undefined\n' + '> undefined\n' + '> const a: 123\n' + '> ' + ); + } + ); + test.suite( '[eval], , and [stdin] execute with correct globals', (test) => { - const cliTest = test.macro( - ( - { - flags, - stdin, - allowError = false, - }: { - flags: string; - stdin: string; - allowError?: boolean; - }, - assertions: ( - stdout: string, - stderr: string, - err: ExecException | null - ) => Promise | void - ) => async (t) => { - const execPromise = exec(`${cmd} ${flags}`); - // Uncomment to run against vanilla node, useful to verify that these test cases match vanilla node - // const execPromise = exec(`node ${flags}`); - execPromise.child.stdin!.end(stdin); - const { err, stdout, stderr } = await execPromise; - if (!allowError) expect(err).to.equal(null); - await assertions(stdout, stderr, err); - } - ); + cmd; interface GlobalInRepl extends NodeJS.Global { testReport: any; replReport: any; @@ -581,7 +563,7 @@ test.suite('ts-node', (test) => { test( 'stdin', - cliTest, + execMacro, { stdin: `${setReportGlobal('stdin')};${printReports}`, flags: '', @@ -612,7 +594,7 @@ test.suite('ts-node', (test) => { ); test( 'repl', - cliTest, + execMacro, { stdin: `${setReportGlobal('repl')};${printReports}`, flags: '-i', @@ -654,7 +636,7 @@ test.suite('ts-node', (test) => { // Should ignore -i and run the entrypoint test( '-i w/entrypoint ignores -i', - cliTest, + execMacro, { stdin: `${setReportGlobal('repl')};${printReports}`, flags: '-i ./repl/script.js', @@ -673,7 +655,7 @@ test.suite('ts-node', (test) => { // Should not interpret positional arg as an entrypoint script test( '-e', - cliTest, + execMacro, { stdin: `throw new Error()`, flags: `-e "${setReportGlobal('eval')};${printReports}"`, @@ -704,7 +686,7 @@ test.suite('ts-node', (test) => { ); test( '-e w/entrypoint arg does not execute entrypoint', - cliTest, + execMacro, { stdin: `throw new Error()`, flags: `-e "${setReportGlobal( @@ -737,7 +719,7 @@ test.suite('ts-node', (test) => { ); test( '-e w/non-path arg', - cliTest, + execMacro, { stdin: `throw new Error()`, flags: `-e "${setReportGlobal( @@ -770,7 +752,7 @@ test.suite('ts-node', (test) => { ); test( '-e -i', - cliTest, + execMacro, { stdin: `${setReportGlobal('repl')};${printReports}`, flags: `-e "${setReportGlobal('eval')}" -i`, @@ -826,7 +808,7 @@ test.suite('ts-node', (test) => { test( '-e -i w/entrypoint ignores -e and -i, runs entrypoint', - cliTest, + execMacro, { stdin: `throw new Error()`, flags: '-e "throw new Error()" -i ./repl/script.js', @@ -843,11 +825,11 @@ test.suite('ts-node', (test) => { test( '-e -i when -e throws error, -i does not run', - cliTest, + execMacro, { stdin: `console.log('hello')`, flags: `-e "throw new Error('error from -e')" -i`, - allowError: true, + expectError: true, }, (stdout, stderr, err) => { exp(err).toBeDefined(); diff --git a/src/test/macros.ts b/src/test/macros.ts new file mode 100644 index 000000000..837e171c4 --- /dev/null +++ b/src/test/macros.ts @@ -0,0 +1,111 @@ +import type { ChildProcess, ExecException, ExecOptions } from "child_process"; +import {exec as childProcessExec} from 'child_process'; +import type { TestInterface } from "./testlib"; +import { expect } from 'chai'; +import * as exp from 'expect'; + +export type ExecReturn = Promise & {child: ChildProcess}; +export interface ExecResult { + stdout: string; + stderr: string; + err: null | ExecException; + child: ChildProcess; +} + +export interface ExecMacroOptions { + titlePrefix?: string; + cmd: string; + flags?: string; + cwd?: string; + env?: Record; + stdin?: string; + expectError?: boolean; + delay?: number; +} +export type ExecMacroAssertionCallback = ( + stdout: string, + stderr: string, + err: ExecException | null + ) => Promise | void + +export interface createMacrosAndHelpersOptions { + test: TestInterface; + defaultCwd: string; +} +export function createMacrosAndHelpers(opts: createMacrosAndHelpersOptions) { + const {test, defaultCwd} = opts; + + /** + * Helper to exec a child process. + * Returns a Promise and a reference to the child process to suite multiple situations. + * Promise resolves with the process's stdout, stderr, and error. + */ + function exec( + cmd: string, + opts: ExecOptions = {} + ): ExecReturn { + let child!: ChildProcess; + return Object.assign( + new Promise((resolve, reject) => { + child = childProcessExec( + cmd, + { + cwd: defaultCwd, + ...opts, + }, + (err, stdout, stderr) => { + resolve({ err, stdout, stderr, child }); + } + ); + }), + { + child + } + ); + } + + /** + * Create a macro that launches a CLI command, optionally pipes stdin, optionally sets env vars, + * and allows assertions against the output. + */ + function createExecMacro>(preBoundOptions: T) { + return test.macro( + ( + options: Pick> & Partial>, + assertions: ExecMacroAssertionCallback + ) => [ + (title) => `${options.titlePrefix ?? ''}${title}`, + async (t) => { + const { + cmd, + flags = '', + stdin, + expectError = false, + cwd, + delay = 0, + env, + } = { ...preBoundOptions, ...options }; + const execPromise = exec(`${cmd} ${flags}`, { + cwd, + env: {...process.env, ...env}, + }); + if(stdin !== undefined) { + execPromise.child.stdin!.end(stdin); + } + const { err, stdout, stderr } = await execPromise; + if (expectError) { + exp(err).toBeDefined(); + } else { + exp(err).toBeNull(); + } + await assertions(stdout, stderr, err); + } + ] + ); + } + + return { + exec, + createExecMacro + }; +} diff --git a/src/test/testlib.ts b/src/test/testlib.ts index f3b3a2a15..d48af9ddb 100644 --- a/src/test/testlib.ts +++ b/src/test/testlib.ts @@ -1,3 +1,9 @@ +/* + * Extensions to ava, for declaring and running test cases and suites + * Utilities specific to testing ts-node, for example handling streams and exec-ing processes, + * should go in a separate module. + */ + import avaTest, { ExecutionContext, Implementation, @@ -60,7 +66,7 @@ export interface TestInterface< ...args: Args ) => | [ - (title: string | undefined) => string, + (title: string | undefined) => string | undefined, (t: ExecutionContext) => Promise ] | ((t: ExecutionContext) => Promise) From 0153167bd13789ce5a636479371aecd417867a18 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 19 Jul 2021 16:26:53 -0400 Subject: [PATCH 7/8] Finish; add tests --- src/bin.ts | 4 +- src/repl.ts | 8 +- src/test/index.spec.ts | 105 ++++++++++++++++++------ src/test/macros.ts | 57 ++++++------- tests/repl-ignored-diagnostics/index.ts | 6 ++ 5 files changed, 118 insertions(+), 62 deletions(-) create mode 100644 tests/repl-ignored-diagnostics/index.ts diff --git a/src/bin.ts b/src/bin.ts index c6424647e..71a9c506b 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -205,7 +205,7 @@ export function main( repl: createRepl({ state, composeWithEvalAwarePartialHost: evalAwarePartialHost, - ignoreExtraDiagnostics: false, + ignoreDiagnosticsThatAreAnnoyingInInteractiveRepl: false, }), }; ({ evalAwarePartialHost } = evalStuff.repl); @@ -221,7 +221,7 @@ export function main( repl: createRepl({ state, composeWithEvalAwarePartialHost: evalAwarePartialHost, - ignoreExtraDiagnostics: false, + ignoreDiagnosticsThatAreAnnoyingInInteractiveRepl: false, }), }; ({ evalAwarePartialHost } = stdinStuff.repl); diff --git a/src/repl.ts b/src/repl.ts index 5ad994432..8537e58dd 100644 --- a/src/repl.ts +++ b/src/repl.ts @@ -61,9 +61,9 @@ export interface CreateReplOptions { composeWithEvalAwarePartialHost?: EvalAwarePartialHost; /** * @internal - * Ignore diagnostics that are annoying when interactively entering input line-by-line + * Ignore diagnostics that are annoying when interactively entering input line-by-line. */ - ignoreExtraDiagnostics?: boolean; + ignoreDiagnosticsThatAreAnnoyingInInteractiveRepl?: boolean; } /** @@ -91,7 +91,7 @@ export function createRepl(options: CreateReplOptions = {}) { stdout === process.stdout && stderr === process.stderr ? console : new Console(stdout, stderr); - const { ignoreExtraDiagnostics = true } = options; + const { ignoreDiagnosticsThatAreAnnoyingInInteractiveRepl = true } = options; const replService: ReplService = { state: options.state ?? new EvalState(join(process.cwd(), EVAL_FILENAME)), @@ -109,7 +109,7 @@ export function createRepl(options: CreateReplOptions = {}) { function setService(_service: Service) { service = _service; - if (ignoreExtraDiagnostics) { + if (ignoreDiagnosticsThatAreAnnoyingInInteractiveRepl) { service.addDiagnosticFilter({ appliesToAllFiles: false, filenamesAbsolute: [state.path], diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index bc9eba1ff..d9463ffcc 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -409,6 +409,7 @@ test.suite('ts-node', (test) => { const execMacro = createExecMacro({ cmd, + cwd: TEST_DIR, }); type ReplApiMacroAssertions = ( @@ -417,11 +418,9 @@ test.suite('ts-node', (test) => { ) => Promise; const replApiMacro = test.macro( - (opts: { - input: string; - }, - assertions: ReplApiMacroAssertions - ) => async (t) => { + (opts: { input: string }, assertions: ReplApiMacroAssertions) => async ( + t + ) => { const { input } = opts; const { stdin, stdout, stderr, replService } = createReplViaApi(); replService.start(); @@ -437,11 +436,12 @@ test.suite('ts-node', (test) => { ); // Serial because it's timing-sensitive - test.serial('REPL can be created via API', - replApiMacro, - { - input: '\nconst a = 123\n.type a\n', - }, + test.serial( + 'REPL can be created via API', + replApiMacro, + { + input: '\nconst a = 123\n.type a\n', + }, async (stdout, stderr) => { expect(stderr).to.equal(''); expect(stdout).to.equal( @@ -925,6 +925,58 @@ test.suite('ts-node', (test) => { } ); + test.suite( + 'REPL ignores diagnostics that are annoying in interactive sessions', + (test) => { + const code = `function foo() {};\nfunction foo() {return 123};\nconsole.log(foo());\n`; + const diagnosticMessage = `Duplicate function implementation`; + test( + 'interactive repl should ignore them', + execMacro, + { + flags: '-i', + stdin: code, + }, + async (stdout, stderr) => { + exp(stdout).not.toContain(diagnosticMessage); + } + ); + test( + 'interactive repl should not ignore them if they occur in other files', + execMacro, + { + flags: '-i', + stdin: `import './repl-ignored-diagnostics/index.ts';\n`, + }, + async (stdout, stderr) => { + exp(stderr).toContain(diagnosticMessage); + } + ); + test( + '[stdin] should not ignore them', + execMacro, + { + stdin: code, + expectError: true, + }, + async (stdout, stderr) => { + exp(stderr).toContain(diagnosticMessage); + } + ); + test( + '[eval] should not ignore them', + execMacro, + { + flags: `-e "${code.replace(/\n/g, '')}"`, + expectError: true, + }, + async (stdout, stderr) => { + exp(stderr).toContain(diagnosticMessage); + } + ); + } + ); + test('should support require flags', async () => { const { err, stdout } = await exec( `${cmd} -r ./hello-world -pe "console.log('success')"` @@ -1085,7 +1137,9 @@ test.suite('ts-node', (test) => { test('should locate tsconfig relative to cwd in --cwd-mode', async () => { const { err, stdout } = await exec( `${BIN_PATH} --cwd-mode ../a/index`, - { cwd: join(TEST_DIR, 'cwd-and-script-mode/b') } + { + cwd: join(TEST_DIR, 'cwd-and-script-mode/b'), + } ); expect(err).to.equal(null); expect(stdout).to.match(/plugin-b/); @@ -1235,11 +1289,12 @@ test.suite('ts-node', (test) => { const { context: { tempDir }, } = t; - const { - err: err1, - stdout: stdout1, - stderr: stderr1, - } = await exec(`${BIN_PATH} --showConfig`, { cwd: tempDir }); + const { err: err1, stdout: stdout1, stderr: stderr1 } = await exec( + `${BIN_PATH} --showConfig`, + { + cwd: tempDir, + } + ); expect(err1).to.equal(null); t.like(JSON.parse(stdout1), { compilerOptions: { @@ -1748,23 +1803,21 @@ test.suite('ts-node', (test) => { test.suite('supports experimental-specifier-resolution=node', (test) => { test('via --experimental-specifier-resolution', async () => { - const { - err, - stdout, - } = await exec( + const { err, stdout } = await exec( `${esmCmd} --experimental-specifier-resolution=node index.ts`, - { cwd: join(TEST_DIR, './esm-node-resolver') } + { + cwd: join(TEST_DIR, './esm-node-resolver'), + } ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('via --es-module-specifier-resolution alias', async () => { - const { - err, - stdout, - } = await exec( + const { err, stdout } = await exec( `${esmCmd} --experimental-modules --es-module-specifier-resolution=node index.ts`, - { cwd: join(TEST_DIR, './esm-node-resolver') } + { + cwd: join(TEST_DIR, './esm-node-resolver'), + } ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); diff --git a/src/test/macros.ts b/src/test/macros.ts index 837e171c4..4e076ae30 100644 --- a/src/test/macros.ts +++ b/src/test/macros.ts @@ -1,10 +1,10 @@ -import type { ChildProcess, ExecException, ExecOptions } from "child_process"; -import {exec as childProcessExec} from 'child_process'; -import type { TestInterface } from "./testlib"; +import type { ChildProcess, ExecException, ExecOptions } from 'child_process'; +import { exec as childProcessExec } from 'child_process'; +import type { TestInterface } from './testlib'; import { expect } from 'chai'; import * as exp from 'expect'; -export type ExecReturn = Promise & {child: ChildProcess}; +export type ExecReturn = Promise & { child: ChildProcess }; export interface ExecResult { stdout: string; stderr: string; @@ -20,30 +20,26 @@ export interface ExecMacroOptions { env?: Record; stdin?: string; expectError?: boolean; - delay?: number; } export type ExecMacroAssertionCallback = ( - stdout: string, - stderr: string, - err: ExecException | null - ) => Promise | void + stdout: string, + stderr: string, + err: ExecException | null +) => Promise | void; export interface createMacrosAndHelpersOptions { test: TestInterface; defaultCwd: string; } export function createMacrosAndHelpers(opts: createMacrosAndHelpersOptions) { - const {test, defaultCwd} = opts; + const { test, defaultCwd } = opts; /** * Helper to exec a child process. * Returns a Promise and a reference to the child process to suite multiple situations. * Promise resolves with the process's stdout, stderr, and error. */ - function exec( - cmd: string, - opts: ExecOptions = {} - ): ExecReturn { + function exec(cmd: string, opts: ExecOptions = {}): ExecReturn { let child!: ChildProcess; return Object.assign( new Promise((resolve, reject) => { @@ -59,7 +55,7 @@ export function createMacrosAndHelpers(opts: createMacrosAndHelpersOptions) { ); }), { - child + child, } ); } @@ -68,28 +64,29 @@ export function createMacrosAndHelpers(opts: createMacrosAndHelpersOptions) { * Create a macro that launches a CLI command, optionally pipes stdin, optionally sets env vars, * and allows assertions against the output. */ - function createExecMacro>(preBoundOptions: T) { + function createExecMacro>( + preBoundOptions: T + ) { return test.macro( ( - options: Pick> & Partial>, + options: Pick< + ExecMacroOptions, + Exclude + > & + Partial>, assertions: ExecMacroAssertionCallback ) => [ (title) => `${options.titlePrefix ?? ''}${title}`, async (t) => { - const { - cmd, - flags = '', - stdin, - expectError = false, - cwd, - delay = 0, - env, - } = { ...preBoundOptions, ...options }; + const { cmd, flags = '', stdin, expectError = false, cwd, env } = { + ...preBoundOptions, + ...options, + }; const execPromise = exec(`${cmd} ${flags}`, { cwd, - env: {...process.env, ...env}, + env: { ...process.env, ...env }, }); - if(stdin !== undefined) { + if (stdin !== undefined) { execPromise.child.stdin!.end(stdin); } const { err, stdout, stderr } = await execPromise; @@ -99,13 +96,13 @@ export function createMacrosAndHelpers(opts: createMacrosAndHelpersOptions) { exp(err).toBeNull(); } await assertions(stdout, stderr, err); - } + }, ] ); } return { exec, - createExecMacro + createExecMacro, }; } diff --git a/tests/repl-ignored-diagnostics/index.ts b/tests/repl-ignored-diagnostics/index.ts new file mode 100644 index 000000000..77f8fe380 --- /dev/null +++ b/tests/repl-ignored-diagnostics/index.ts @@ -0,0 +1,6 @@ +// This script triggers a diagnostic that is ignored in the virtual file but +// *not* in files such as this one. +// When this file is required by the REPL, the diagnostic *should* be logged. +export {}; +function foo() {} +function foo() {} From 23d9c124d6e4564f6c982a8a4fc2084ce3ba4cd0 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 19 Jul 2021 16:34:06 -0400 Subject: [PATCH 8/8] revert unnecessary formatting changes --- src/test/index.spec.ts | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/test/index.spec.ts b/src/test/index.spec.ts index d9463ffcc..af0ab51a3 100644 --- a/src/test/index.spec.ts +++ b/src/test/index.spec.ts @@ -453,7 +453,6 @@ test.suite('ts-node', (test) => { test.suite( '[eval], , and [stdin] execute with correct globals', (test) => { - cmd; interface GlobalInRepl extends NodeJS.Global { testReport: any; replReport: any; @@ -1137,9 +1136,7 @@ test.suite('ts-node', (test) => { test('should locate tsconfig relative to cwd in --cwd-mode', async () => { const { err, stdout } = await exec( `${BIN_PATH} --cwd-mode ../a/index`, - { - cwd: join(TEST_DIR, 'cwd-and-script-mode/b'), - } + { cwd: join(TEST_DIR, 'cwd-and-script-mode/b') } ); expect(err).to.equal(null); expect(stdout).to.match(/plugin-b/); @@ -1289,12 +1286,11 @@ test.suite('ts-node', (test) => { const { context: { tempDir }, } = t; - const { err: err1, stdout: stdout1, stderr: stderr1 } = await exec( - `${BIN_PATH} --showConfig`, - { - cwd: tempDir, - } - ); + const { + err: err1, + stdout: stdout1, + stderr: stderr1, + } = await exec(`${BIN_PATH} --showConfig`, { cwd: tempDir }); expect(err1).to.equal(null); t.like(JSON.parse(stdout1), { compilerOptions: { @@ -1803,21 +1799,23 @@ test.suite('ts-node', (test) => { test.suite('supports experimental-specifier-resolution=node', (test) => { test('via --experimental-specifier-resolution', async () => { - const { err, stdout } = await exec( + const { + err, + stdout, + } = await exec( `${esmCmd} --experimental-specifier-resolution=node index.ts`, - { - cwd: join(TEST_DIR, './esm-node-resolver'), - } + { cwd: join(TEST_DIR, './esm-node-resolver') } ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n'); }); test('via --es-module-specifier-resolution alias', async () => { - const { err, stdout } = await exec( + const { + err, + stdout, + } = await exec( `${esmCmd} --experimental-modules --es-module-specifier-resolution=node index.ts`, - { - cwd: join(TEST_DIR, './esm-node-resolver'), - } + { cwd: join(TEST_DIR, './esm-node-resolver') } ); expect(err).to.equal(null); expect(stdout).to.equal('foo bar baz biff libfoo\n');