Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into ab/evaluate-with-modu…
Browse files Browse the repository at this point in the history
…le-globals
  • Loading branch information
cspotcode committed Jun 8, 2021
2 parents 4a2caf0 + 4f16d1b commit 5f30514
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 76 deletions.
116 changes: 80 additions & 36 deletions src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { resolve, dirname } from 'path';
import type * as _ts from 'typescript';
import { CreateOptions, DEFAULTS, TSCommon, TsConfigOptions } from './index';
import type { TSInternal } from './ts-compiler-types';
import { createTsInternals } from './ts-internals';
import { getDefaultTsconfigJsonForNodeVersion } from './tsconfigs';
import { createRequire } from './util';
import { assign, createRequire, trace } from './util';

/**
* TypeScript compiler option values required by `ts-node` which cannot be overridden.
Expand Down Expand Up @@ -69,6 +71,12 @@ export function readConfig(
*/
tsNodeOptionsFromTsconfig: TsConfigOptions;
} {
// Ordered [a, b, c] where config a extends b extends c
const configChain: Array<{
config: any;
basePath: string;
configPath: string;
}> = [];
let config: any = { compilerOptions: {} };
let basePath = cwd;
let configFilePath: string | undefined = undefined;
Expand All @@ -88,27 +96,81 @@ export function readConfig(
: ts.findConfigFile(projectSearchDir, fileExists);

if (configFilePath) {
const result = ts.readConfigFile(configFilePath, readFile);

// Return diagnostics.
if (result.error) {
return {
configFilePath,
config: { errors: [result.error], fileNames: [], options: {} },
tsNodeOptionsFromTsconfig: {},
};
let pathToNextConfigInChain = configFilePath;
const tsInternals = createTsInternals(ts);
const errors: Array<_ts.Diagnostic> = [];

// Follow chain of "extends"
while (true) {
const result = ts.readConfigFile(pathToNextConfigInChain, readFile);

// Return diagnostics.
if (result.error) {
return {
configFilePath,
config: { errors: [result.error], fileNames: [], options: {} },
tsNodeOptionsFromTsconfig: {},
};
}

const c = result.config;
const bp = dirname(pathToNextConfigInChain);
configChain.push({
config: c,
basePath: bp,
configPath: pathToNextConfigInChain,
});

if (c.extends == null) break;
const resolvedExtendedConfigPath = tsInternals.getExtendsConfigPath(
c.extends,
{
fileExists,
readDirectory: ts.sys.readDirectory,
readFile,
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
trace,
},
bp,
errors,
((ts as unknown) as TSInternal).createCompilerDiagnostic
);
if (errors.length) {
return {
configFilePath,
config: { errors, fileNames: [], options: {} },
tsNodeOptionsFromTsconfig: {},
};
}
if (resolvedExtendedConfigPath == null) break;
pathToNextConfigInChain = resolvedExtendedConfigPath;
}

config = result.config;
basePath = dirname(configFilePath);
({ config, basePath } = configChain[0]);
}
}

// Fix ts-node options that come from tsconfig.json
const tsNodeOptionsFromTsconfig: TsConfigOptions = Object.assign(
{},
filterRecognizedTsConfigTsNodeOptions(config['ts-node']).recognized
);
// Merge and fix ts-node options that come from tsconfig.json(s)
const tsNodeOptionsFromTsconfig: TsConfigOptions = {};
for (let i = configChain.length - 1; i >= 0; i--) {
const { config, basePath, configPath } = configChain[i];
const options = filterRecognizedTsConfigTsNodeOptions(config['ts-node'])
.recognized;

// Some options are relative to the config file, so must be converted to absolute paths here
if (options.require) {
// Modules are found relative to the tsconfig file, not the `dir` option
const tsconfigRelativeRequire = createRequire(configPath);
options.require = options.require.map((path: string) =>
tsconfigRelativeRequire.resolve(path)
);
}
if (options.scopeDir) {
options.scopeDir = resolve(basePath, options.scopeDir!);
}

assign(tsNodeOptionsFromTsconfig, options);
}

// Remove resolution of "files".
const files =
Expand Down Expand Up @@ -160,24 +222,6 @@ export function readConfig(
)
);

// Some options are relative to the config file, so must be converted to absolute paths here

if (tsNodeOptionsFromTsconfig.require) {
// Modules are found relative to the tsconfig file, not the `dir` option
const tsconfigRelativeRequire = createRequire(configFilePath!);
tsNodeOptionsFromTsconfig.require = tsNodeOptionsFromTsconfig.require.map(
(path: string) => {
return tsconfigRelativeRequire.resolve(path);
}
);
}
if (tsNodeOptionsFromTsconfig.scopeDir) {
tsNodeOptionsFromTsconfig.scopeDir = resolve(
basePath,
tsNodeOptionsFromTsconfig.scopeDir
);
}

return { configFilePath, config: fixedConfig, tsNodeOptionsFromTsconfig };
}

Expand All @@ -188,7 +232,7 @@ export function readConfig(
function filterRecognizedTsConfigTsNodeOptions(
jsonObject: any
): { recognized: TsConfigOptions; unrecognized: any } {
if (jsonObject == null) return { recognized: jsonObject, unrecognized: {} };
if (jsonObject == null) return { recognized: {}, unrecognized: {} };
const {
compiler,
compilerHost,
Expand Down
24 changes: 8 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import { BaseError } from 'make-error';
import type * as _ts from 'typescript';

import type { Transpiler, TranspilerFactory } from './transpilers/types';
import { assign, normalizeSlashes, parse, split, yn } from './util';
import {
assign,
cachedLookup,
normalizeSlashes,
parse,
split,
yn,
} from './util';
import { readConfig } from './configuration';
import type { TSCommon, TSInternal } from './ts-compiler-types';

Expand Down Expand Up @@ -374,21 +381,6 @@ export interface Service {
*/
export type Register = Service;

/**
* Cached fs operation wrapper.
*/
function cachedLookup<T>(fn: (arg: string) => T): (arg: string) => T {
const cache = new Map<string, T>();

return (arg: string): T => {
if (!cache.has(arg)) {
cache.set(arg, fn(arg));
}

return cache.get(arg)!;
};
}

/** @internal */
export function getExtensions(config: _ts.ParsedCommandLine) {
const tsExtensions = ['.ts'];
Expand Down
55 changes: 32 additions & 23 deletions src/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,10 +618,9 @@ test.suite('ts-node', (test) => {
moduleId: '<repl>',
modulePath: '.',
moduleFilename: null,
modulePaths: exp.objectContaining({...[
join(TEST_DIR, `repl/node_modules`),
...modulePaths,
]}),
modulePaths: exp.objectContaining({
...[join(TEST_DIR, `repl/node_modules`), ...modulePaths],
}),
// Note: vanilla node REPL does not set exports
exportsTest: true,
// Note: vanilla node uses different name. See #1360
Expand Down Expand Up @@ -720,10 +719,7 @@ test.suite('ts-node', (test) => {
` at ${join(TEST_DIR, `[eval].ts`)}:1:`
),
moduleAccessorsTest: true,
argv: [
tsNodeExe,
'./repl/script.js',
],
argv: [tsNodeExe, './repl/script.js'],
},
replReport: false,
});
Expand Down Expand Up @@ -756,10 +752,7 @@ test.suite('ts-node', (test) => {
` at ${join(TEST_DIR, `[eval].ts`)}:1:`
),
moduleAccessorsTest: true,
argv: [
tsNodeExe,
'./does-not-exist.js',
],
argv: [tsNodeExe, './does-not-exist.js'],
},
replReport: false,
});
Expand Down Expand Up @@ -798,10 +791,9 @@ test.suite('ts-node', (test) => {
moduleId: '<repl>',
modulePath: '.',
moduleFilename: null,
modulePaths: exp.objectContaining({...[
join(TEST_DIR, `repl/node_modules`),
...modulePaths
]}),
modulePaths: exp.objectContaining({
...[join(TEST_DIR, `repl/node_modules`), ...modulePaths],
}),
// Note: vanilla node REPL does not set exports, so this would be false
exportsTest: true,
// Note: vanilla node uses different name. See #1360
Expand Down Expand Up @@ -889,12 +881,11 @@ test.suite('ts-node', (test) => {
// // Note: vanilla node REPL does not set exports
// exportsTest: true,
// moduleAccessorsTest: true,

// Note: vanilla node uses different name. See #1360
stackTest: exp.stringContaining(
` at ${join(ROOT_DIR, '<repl>.ts')}:1:`
),

},
});
}
Expand All @@ -916,10 +907,9 @@ test.suite('ts-node', (test) => {
moduleId: '<repl>',
modulePath: '.',
moduleFilename: null,
modulePaths: exp.objectContaining({...[
join(ROOT_DIR, `repl/node_modules`),
...rootModulePaths,
]}),
modulePaths: exp.objectContaining({
...[join(ROOT_DIR, `repl/node_modules`), ...rootModulePaths],
}),
// Note: vanilla node REPL does not set exports
exportsTest: true,
// Note: vanilla node uses different name. See #1360
Expand All @@ -930,7 +920,9 @@ test.suite('ts-node', (test) => {
},
});
// Prior to these, nyc adds another entry on Windows; we need to ignore it
exp(globalInRepl.testReport.replReport.modulePaths.slice(-3)).toMatchObject([
exp(
globalInRepl.testReport.replReport.modulePaths.slice(-3)
).toMatchObject([
join(homedir(), `.node_modules`),
join(homedir(), `.node_libraries`),
// additional entry goes to node's install path
Expand Down Expand Up @@ -1217,6 +1209,23 @@ test.suite('ts-node', (test) => {
join(TEST_DIR, './tsconfig-options/required1.js'),
]);
});

if (semver.gte(ts.version, '3.2.0')) {
test('should pull ts-node options from extended `tsconfig.json`', async () => {
const { err, stdout } = await exec(
`${BIN_PATH} --show-config --project ./tsconfig-extends/tsconfig.json`
);
expect(err).to.equal(null);
const config = JSON.parse(stdout);
expect(config['ts-node'].require).to.deep.equal([
resolve(TEST_DIR, 'tsconfig-extends/other/require-hook.js'),
]);
expect(config['ts-node'].scopeDir).to.equal(
resolve(TEST_DIR, 'tsconfig-extends/other/scopedir')
);
expect(config['ts-node'].preferTsExts).to.equal(true);
});
}
});

test.suite(
Expand Down
19 changes: 19 additions & 0 deletions src/ts-compiler-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export interface TSCommon {
getDefaultLibFileName: typeof _ts.getDefaultLibFileName;
createIncrementalProgram: typeof _ts.createIncrementalProgram;
createEmitAndSemanticDiagnosticsBuilderProgram: typeof _ts.createEmitAndSemanticDiagnosticsBuilderProgram;

Extension: typeof _ts.Extension;
ModuleResolutionKind: typeof _ts.ModuleResolutionKind;
}

/**
Expand All @@ -50,6 +53,22 @@ export interface TSInternal {
host: TSInternal.ConvertToTSConfigHost
): any;
libs?: string[];
Diagnostics: {
File_0_not_found: _ts.DiagnosticMessage;
};
createCompilerDiagnostic(
message: _ts.DiagnosticMessage,
...args: (string | number | undefined)[]
): _ts.Diagnostic;
nodeModuleNameResolver(
moduleName: string,
containingFile: string,
compilerOptions: _ts.CompilerOptions,
host: _ts.ModuleResolutionHost,
cache?: _ts.ModuleResolutionCache,
redirectedReference?: _ts.ResolvedProjectReference,
lookupConfig?: boolean
): _ts.ResolvedModuleWithFailedLookupLocations;
}
/** @internal */
export namespace TSInternal {
Expand Down
Loading

0 comments on commit 5f30514

Please sign in to comment.