From 8035ef58898c1d0392305a3b99a278caa3afed77 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 8 May 2023 23:27:49 -0400 Subject: [PATCH] implement --- src/bin.ts | 191 ++++++++++++++++++-------------- src/test/cli-args.spec.ts | 32 ++++++ src/test/helpers/ctx-ts-node.ts | 4 +- src/test/helpers/misc.ts | 3 +- src/test/repl/repl.spec.ts | 4 +- 5 files changed, 148 insertions(+), 86 deletions(-) create mode 100644 src/test/cli-args.spec.ts diff --git a/src/bin.ts b/src/bin.ts index cbe34caf8..fa52bf7c2 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -94,90 +94,101 @@ export function bootstrap(state: BootstrapState) { return phase4(state); } -function parseArgv(argv: string[], entrypointArgs: Record) { +/** @internal */ +export type ParsedArgv = ReturnType; + +/** @internal */ +export function parseArgv(argv: string[], entrypointArgs: Record) { arg ??= require('arg'); + // Attempt parsing w/two specs to support `-p 123` or `-pe 123` + const StringArray = [String] as [StringConstructor]; + const spec1 = { + // Node.js-like options. + '--eval': String, + '--interactive': Boolean, + '--print': String, + '--require': StringArray, + + // CLI options. + '--help': Boolean, + '--cwdMode': Boolean, + '--scriptMode': Boolean, + '--version': arg.COUNT, + '--showConfig': Boolean, + '--esm': Boolean, + + // Project options. + '--cwd': String, + '--files': Boolean, + '--compiler': String, + '--compilerOptions': parse, + '--project': String, + '--ignoreDiagnostics': StringArray, + '--ignore': StringArray, + '--transpileOnly': Boolean, + '--transpiler': String, + '--swc': Boolean, + '--typeCheck': Boolean, + '--compilerHost': Boolean, + '--pretty': Boolean, + '--skipProject': Boolean, + '--skipIgnore': Boolean, + '--preferTsExts': Boolean, + '--logError': Boolean, + '--emit': Boolean, + '--scope': Boolean, + '--scopeDir': String, + '--noExperimentalReplAwait': Boolean, + '--experimentalSpecifierResolution': String, + + // Aliases. + '-e': '--eval', + '-i': '--interactive', + '-p': '--print', + '-r': '--require', + '-h': '--help', + '-s': '--script-mode', + '-v': '--version', + '-T': '--transpileOnly', + '-H': '--compilerHost', + '-I': '--ignore', + '-P': '--project', + '-C': '--compiler', + '-D': '--ignoreDiagnostics', + '-O': '--compilerOptions', + '--dir': '--cwd', + + // Support both tsc-style camelCase and node-style hypen-case for *all* flags + '--cwd-mode': '--cwdMode', + '--script-mode': '--scriptMode', + '--show-config': '--showConfig', + '--compiler-options': '--compilerOptions', + '--ignore-diagnostics': '--ignoreDiagnostics', + '--transpile-only': '--transpileOnly', + '--type-check': '--typeCheck', + '--compiler-host': '--compilerHost', + '--skip-project': '--skipProject', + '--skip-ignore': '--skipIgnore', + '--prefer-ts-exts': '--preferTsExts', + '--log-error': '--logError', + '--scope-dir': '--scopeDir', + '--no-experimental-repl-await': '--noExperimentalReplAwait', + '--experimental-specifier-resolution': '--experimentalSpecifierResolution', + }; + const spec2 = { + ...spec1, + '--print': Boolean, + }; + let argParse; + try { + argParse = arg(spec1, { argv, stopAtPositional: true }); + } catch (e) { + argParse = arg(spec2, { argv, stopAtPositional: true }); + } const args = { ...entrypointArgs, - ...arg( - { - // Node.js-like options. - '--eval': String, - '--interactive': Boolean, - '--print': Boolean, - '--require': [String], - - // CLI options. - '--help': Boolean, - '--cwdMode': Boolean, - '--scriptMode': Boolean, - '--version': arg.COUNT, - '--showConfig': Boolean, - '--esm': Boolean, - - // Project options. - '--cwd': String, - '--files': Boolean, - '--compiler': String, - '--compilerOptions': parse, - '--project': String, - '--ignoreDiagnostics': [String], - '--ignore': [String], - '--transpileOnly': Boolean, - '--transpiler': String, - '--swc': Boolean, - '--typeCheck': Boolean, - '--compilerHost': Boolean, - '--pretty': Boolean, - '--skipProject': Boolean, - '--skipIgnore': Boolean, - '--preferTsExts': Boolean, - '--logError': Boolean, - '--emit': Boolean, - '--scope': Boolean, - '--scopeDir': String, - '--noExperimentalReplAwait': Boolean, - '--experimentalSpecifierResolution': String, - - // Aliases. - '-e': '--eval', - '-i': '--interactive', - '-p': '--print', - '-r': '--require', - '-h': '--help', - '-s': '--script-mode', - '-v': '--version', - '-T': '--transpileOnly', - '-H': '--compilerHost', - '-I': '--ignore', - '-P': '--project', - '-C': '--compiler', - '-D': '--ignoreDiagnostics', - '-O': '--compilerOptions', - '--dir': '--cwd', - - // Support both tsc-style camelCase and node-style hypen-case for *all* flags - '--cwd-mode': '--cwdMode', - '--script-mode': '--scriptMode', - '--show-config': '--showConfig', - '--compiler-options': '--compilerOptions', - '--ignore-diagnostics': '--ignoreDiagnostics', - '--transpile-only': '--transpileOnly', - '--type-check': '--typeCheck', - '--compiler-host': '--compilerHost', - '--skip-project': '--skipProject', - '--skip-ignore': '--skipIgnore', - '--prefer-ts-exts': '--preferTsExts', - '--log-error': '--logError', - '--scope-dir': '--scopeDir', - '--no-experimental-repl-await': '--noExperimentalReplAwait', - '--experimental-specifier-resolution': '--experimentalSpecifierResolution', - }, - { - argv, - stopAtPositional: true, - } - ), + ...argParse, }; // Only setting defaults for CLI-specific flags @@ -191,8 +202,8 @@ function parseArgv(argv: string[], entrypointArgs: Record) { '--version': version = 0, '--showConfig': showConfig, '--require': argsRequire = [], - '--eval': code = undefined, - '--print': print = false, + '--eval': _code = undefined, + '--print': _print = false, '--interactive': interactive = false, '--files': files, '--compiler': compiler, @@ -218,6 +229,22 @@ function parseArgv(argv: string[], entrypointArgs: Record) { '--esm': esm, _: restArgs, } = args; + + let print: boolean; + let code: string | undefined; + if (typeof _print === 'string') { + // Reject `-p 123 -e 456` + if (_code != null) { + throw new Error('Conflicting options -p and -e. Hint: to specify tsconfig, use -P instead of -p'); + } + code = _print; + print = true; + } else { + // Deliberately allow -p withotu any code + print = _print; + code = _code; + } + return { // Note: argv and restArgs may be overwritten by child process argv: process.argv, diff --git a/src/test/cli-args.spec.ts b/src/test/cli-args.spec.ts new file mode 100644 index 000000000..6d2e21443 --- /dev/null +++ b/src/test/cli-args.spec.ts @@ -0,0 +1,32 @@ +import { context } from './testlib'; +import { ctxTsNode, testsDirRequire } from './helpers'; +import type { ParsedArgv } from '../bin'; +const test = context(ctxTsNode); + +const argParseMacro = test.macro( + (args: string[], entrypointArgs: Record | undefined, expectation: Partial) => [ + () => `"${args.join(' ')}"${entrypointArgs ? ` w/entrypoint args: ${JSON.stringify(entrypointArgs)}` : ``}`, + async (t) => { + const parsedArgs = t.context.tsNodeBin.parseArgv(args, entrypointArgs ?? {}); + t.like(parsedArgs, expectation); + }, + ] +); + +test(argParseMacro, ['-pe', '123'], undefined, { + print: true, + code: '123', + restArgs: [], +}); + +test(argParseMacro, ['-p', '123'], undefined, { + print: true, + code: '123', + restArgs: [], +}); + +test(argParseMacro, ['-e', '123'], undefined, { + print: false, + code: '123', + restArgs: [], +}); diff --git a/src/test/helpers/ctx-ts-node.ts b/src/test/helpers/ctx-ts-node.ts index 7097f117f..433aadec4 100644 --- a/src/test/helpers/ctx-ts-node.ts +++ b/src/test/helpers/ctx-ts-node.ts @@ -6,14 +6,16 @@ import { join } from 'path'; import type { ExecutionContext } from '../testlib'; import { sync as rimrafSync } from 'rimraf'; import { TEST_DIR } from './paths'; -import { testsDirRequire, tsNodeTypes } from './misc'; +import { testsDirRequire, tsNodeBinTypes, tsNodeTypes } from './misc'; /** Pass to `test.context()` to get access to the ts-node API under test */ export async function ctxTsNode() { await installTsNode(); const tsNodeUnderTest: typeof tsNodeTypes = testsDirRequire('ts-node'); + const tsNodeBin: typeof tsNodeBinTypes = testsDirRequire('ts-node/dist/bin'); return { tsNodeUnderTest, + tsNodeBin, }; } export namespace ctxTsNode { diff --git a/src/test/helpers/misc.ts b/src/test/helpers/misc.ts index e05eb10c6..866a5f651 100644 --- a/src/test/helpers/misc.ts +++ b/src/test/helpers/misc.ts @@ -1,10 +1,11 @@ /** types from ts-node under test */ import type * as tsNodeTypes from '../../index'; +import type * as tsNodeBinTypes from '../../bin'; import { TEST_DIR } from './paths'; import { join } from 'path'; import { promisify } from 'util'; import { createRequire } from 'module'; -export { tsNodeTypes }; +export { tsNodeTypes, tsNodeBinTypes }; export const testsDirRequire = createRequire(join(TEST_DIR, 'index.js')); diff --git a/src/test/repl/repl.spec.ts b/src/test/repl/repl.spec.ts index 99612ac40..1e4ee2c21 100644 --- a/src/test/repl/repl.spec.ts +++ b/src/test/repl/repl.spec.ts @@ -222,8 +222,8 @@ test.suite('top level await', ({ contextEach }) => { }); test('should pass upstream test cases', async (t) => { - const { tsNodeUnderTest } = t.context; - await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest }); + const { tsNodeUnderTest, tsNodeBin } = t.context; + await upstreamTopLevelAwaitTests({ TEST_DIR, tsNodeUnderTest, tsNodeBin }); }); });