From 6c0cbc2334d80a9ce0cf4a576f5d6b2303177af5 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 31 May 2022 23:18:07 -0400 Subject: [PATCH 1/7] Add failing test --- src/test/module-node/1778.spec.ts | 33 +++++++++++++++++++ .../{ => module-node}/module-node.spec.ts | 10 +++--- tests/1778/index.ts | 6 ++++ tests/1778/node_modules/foo/cjs/index.d.ts | 2 ++ tests/1778/node_modules/foo/cjs/index.js | 1 + tests/1778/node_modules/foo/cjs/package.json | 3 ++ tests/1778/node_modules/foo/esm/index.d.ts | 2 ++ tests/1778/node_modules/foo/esm/index.js | 1 + tests/1778/node_modules/foo/package.json | 9 +++++ tests/1778/package.json | 3 ++ tests/1778/tsconfig.json | 9 +++++ 11 files changed, 74 insertions(+), 5 deletions(-) create mode 100644 src/test/module-node/1778.spec.ts rename src/test/{ => module-node}/module-node.spec.ts (98%) create mode 100644 tests/1778/index.ts create mode 100644 tests/1778/node_modules/foo/cjs/index.d.ts create mode 100644 tests/1778/node_modules/foo/cjs/index.js create mode 100644 tests/1778/node_modules/foo/cjs/package.json create mode 100644 tests/1778/node_modules/foo/esm/index.d.ts create mode 100644 tests/1778/node_modules/foo/esm/index.js create mode 100644 tests/1778/node_modules/foo/package.json create mode 100644 tests/1778/package.json create mode 100644 tests/1778/tsconfig.json diff --git a/src/test/module-node/1778.spec.ts b/src/test/module-node/1778.spec.ts new file mode 100644 index 000000000..82f4a08cf --- /dev/null +++ b/src/test/module-node/1778.spec.ts @@ -0,0 +1,33 @@ +import { createExec } from '../exec-helpers'; +import { + ctxTsNode, + nodeSupportsEsmHooks, + TEST_DIR, + tsSupportsStableNodeNextNode16, + CMD_TS_NODE_WITHOUT_PROJECT_FLAG, +} from '../helpers'; +import { context, expect } from '../testlib'; +import { join } from 'path'; + +const exec = createExec({ + cwd: TEST_DIR, +}); + +const test = context(ctxTsNode); + +test.suite( + 'Issue #1778: typechecker resolver should take importer\'s module type -- cjs or esm -- into account when resolving package.json "exports"', + (test) => { + test.runIf(nodeSupportsEsmHooks && tsSupportsStableNodeNextNode16); + test('test', async () => { + const { err, stdout } = await exec( + `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`, + { + cwd: join(TEST_DIR, '1778'), + } + ); + expect(err).toBe(null); + expect(stdout).toBe('0\n'); + }); + } +); diff --git a/src/test/module-node.spec.ts b/src/test/module-node/module-node.spec.ts similarity index 98% rename from src/test/module-node.spec.ts rename to src/test/module-node/module-node.spec.ts index 043dc36a4..7ebb3fd4c 100644 --- a/src/test/module-node.spec.ts +++ b/src/test/module-node/module-node.spec.ts @@ -1,15 +1,15 @@ -import { expect, context } from './testlib'; +import { expect, context } from '../testlib'; import { CMD_TS_NODE_WITHOUT_PROJECT_FLAG, isOneOf, nodeSupportsImportingTransformedCjsFromEsm, resetNodeEnvironment, tsSupportsStableNodeNextNode16, -} from './helpers'; +} from '../helpers'; import * as Path from 'path'; -import { ctxTsNode } from './helpers'; -import { exec } from './exec-helpers'; -import { file, project, ProjectAPI as ProjectAPI } from './fs-helpers'; +import { ctxTsNode } from '../helpers'; +import { exec } from '../exec-helpers'; +import { file, project, ProjectAPI as ProjectAPI } from '../fs-helpers'; const test = context(ctxTsNode); test.beforeEach(async () => { diff --git a/tests/1778/index.ts b/tests/1778/index.ts new file mode 100644 index 000000000..ace4327b7 --- /dev/null +++ b/tests/1778/index.ts @@ -0,0 +1,6 @@ +import foo from 'foo'; + +// This file is ESM, so if typechecker's resolver is working correctly, will +// resolve to the foo's package.json "exports" mapping for "default", not "require" +const bar: { esm: true } = foo; +console.log(bar); diff --git a/tests/1778/node_modules/foo/cjs/index.d.ts b/tests/1778/node_modules/foo/cjs/index.d.ts new file mode 100644 index 000000000..a84bc1bc3 --- /dev/null +++ b/tests/1778/node_modules/foo/cjs/index.d.ts @@ -0,0 +1,2 @@ +declare const foo: {cjs: true} +export default foo diff --git a/tests/1778/node_modules/foo/cjs/index.js b/tests/1778/node_modules/foo/cjs/index.js new file mode 100644 index 000000000..9a4be3c24 --- /dev/null +++ b/tests/1778/node_modules/foo/cjs/index.js @@ -0,0 +1 @@ +module.exports = {cjs: true} diff --git a/tests/1778/node_modules/foo/cjs/package.json b/tests/1778/node_modules/foo/cjs/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/1778/node_modules/foo/cjs/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/1778/node_modules/foo/esm/index.d.ts b/tests/1778/node_modules/foo/esm/index.d.ts new file mode 100644 index 000000000..1da8fb3af --- /dev/null +++ b/tests/1778/node_modules/foo/esm/index.d.ts @@ -0,0 +1,2 @@ +declare const foo: {esm: true} +export default foo diff --git a/tests/1778/node_modules/foo/esm/index.js b/tests/1778/node_modules/foo/esm/index.js new file mode 100644 index 000000000..2b573c6c7 --- /dev/null +++ b/tests/1778/node_modules/foo/esm/index.js @@ -0,0 +1 @@ +export default {esm: true} diff --git a/tests/1778/node_modules/foo/package.json b/tests/1778/node_modules/foo/package.json new file mode 100644 index 000000000..e12814a53 --- /dev/null +++ b/tests/1778/node_modules/foo/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "exports": { + ".": { + "require": "./cjs/index.js", + "default": "./esm/index.js" + } + } +} diff --git a/tests/1778/package.json b/tests/1778/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/tests/1778/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/1778/tsconfig.json b/tests/1778/tsconfig.json new file mode 100644 index 000000000..1ec416b41 --- /dev/null +++ b/tests/1778/tsconfig.json @@ -0,0 +1,9 @@ +{ + "ts-node": { + "esm": true + }, + "compilerOptions": { + "module": "NodeNext", + "noEmit": true + } +} From 74ad613b89461f6c499526be9367ecfcab3cdcbd Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 31 May 2022 23:54:47 -0400 Subject: [PATCH 2/7] fix typo in failing test --- src/test/module-node/1778.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/module-node/1778.spec.ts b/src/test/module-node/1778.spec.ts index 82f4a08cf..79fa60662 100644 --- a/src/test/module-node/1778.spec.ts +++ b/src/test/module-node/1778.spec.ts @@ -27,7 +27,7 @@ test.suite( } ); expect(err).toBe(null); - expect(stdout).toBe('0\n'); + expect(stdout).toBe('{ esm: true }\n'); }); } ); From 4419800c699a8f06bee15730abffb66274e61329 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Tue, 31 May 2022 23:55:41 -0400 Subject: [PATCH 3/7] fix bug --- src/resolver-functions.ts | 14 ++++++++++---- src/ts-compiler-types.ts | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/resolver-functions.ts b/src/resolver-functions.ts index 9884c29c8..ac9dbe9e0 100644 --- a/src/resolver-functions.ts +++ b/src/resolver-functions.ts @@ -95,7 +95,8 @@ export function createResolverFunctions(kwargs: { containingFile: string, reusedNames: string[] | undefined, redirectedReference: TSCommon.ResolvedProjectReference | undefined, - optionsOnlyWithNewerTsVersions: TSCommon.CompilerOptions + optionsOnlyWithNewerTsVersions: TSCommon.CompilerOptions, + containingSourceFile?: TSCommon.SourceFile ): (TSCommon.ResolvedModule | undefined)[] => { return moduleNames.map((moduleName) => { const { resolvedModule } = ts.resolveModuleName( @@ -104,7 +105,10 @@ export function createResolverFunctions(kwargs: { config.options, host, moduleResolutionCache, - redirectedReference + redirectedReference, + containingSourceFile?.impliedNodeFormat + + // (ts as any as typeof import('typescript')).getModeForResolutionAtIndex(containingSourceFile) ); if (resolvedModule) { fixupResolvedModule(resolvedModule); @@ -117,12 +121,14 @@ export function createResolverFunctions(kwargs: { const getResolvedModuleWithFailedLookupLocationsFromCache: TSCommon.LanguageServiceHost['getResolvedModuleWithFailedLookupLocationsFromCache'] = ( moduleName, - containingFile + containingFile, + resolutionMode?: TSCommon.ModuleKind.CommonJS | TSCommon.ModuleKind.ESNext ): TSCommon.ResolvedModuleWithFailedLookupLocations | undefined => { const ret = ts.resolveModuleNameFromCache( moduleName, containingFile, - moduleResolutionCache + moduleResolutionCache, + resolutionMode ); if (ret && ret.resolvedModule) { fixupResolvedModule(ret.resolvedModule); diff --git a/src/ts-compiler-types.ts b/src/ts-compiler-types.ts index 2f961b853..77d00c4cd 100644 --- a/src/ts-compiler-types.ts +++ b/src/ts-compiler-types.ts @@ -79,6 +79,12 @@ export namespace TSCommon { ? typeof _ts.ModuleKind['Node16'] : 100; }; + // Can't figure out how to re-export an enum + // `export import ... =` complains that _ts is type-only import + export namespace ModuleKind { + export type CommonJS = _ts.ModuleKind.CommonJS; + export type ESNext = _ts.ModuleKind.ESNext; + } } /** From 2be191e703fe47d1a274ebae5db4260ffe216574 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 1 Jun 2022 00:05:54 -0400 Subject: [PATCH 4/7] fix test runIf filter --- src/test/module-node/1778.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/module-node/1778.spec.ts b/src/test/module-node/1778.spec.ts index 79fa60662..6ffe74fed 100644 --- a/src/test/module-node/1778.spec.ts +++ b/src/test/module-node/1778.spec.ts @@ -5,6 +5,7 @@ import { TEST_DIR, tsSupportsStableNodeNextNode16, CMD_TS_NODE_WITHOUT_PROJECT_FLAG, + nodeSupportsSpawningChildProcess, } from '../helpers'; import { context, expect } from '../testlib'; import { join } from 'path'; @@ -18,7 +19,7 @@ const test = context(ctxTsNode); test.suite( 'Issue #1778: typechecker resolver should take importer\'s module type -- cjs or esm -- into account when resolving package.json "exports"', (test) => { - test.runIf(nodeSupportsEsmHooks && tsSupportsStableNodeNextNode16); + test.runIf(nodeSupportsEsmHooks && nodeSupportsSpawningChildProcess && tsSupportsStableNodeNextNode16); test('test', async () => { const { err, stdout } = await exec( `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`, From c60ce010e39e46632e06e1873870afb40390fd77 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 1 Jun 2022 00:28:16 -0400 Subject: [PATCH 5/7] fix --- src/test/module-node/1778.spec.ts | 6 +++++- src/ts-compiler-types.ts | 22 ++-------------------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/test/module-node/1778.spec.ts b/src/test/module-node/1778.spec.ts index 6ffe74fed..43d65e0f7 100644 --- a/src/test/module-node/1778.spec.ts +++ b/src/test/module-node/1778.spec.ts @@ -19,7 +19,11 @@ const test = context(ctxTsNode); test.suite( 'Issue #1778: typechecker resolver should take importer\'s module type -- cjs or esm -- into account when resolving package.json "exports"', (test) => { - test.runIf(nodeSupportsEsmHooks && nodeSupportsSpawningChildProcess && tsSupportsStableNodeNextNode16); + test.runIf( + nodeSupportsEsmHooks && + nodeSupportsSpawningChildProcess && + tsSupportsStableNodeNextNode16 + ); test('test', async () => { const { err, stdout } = await exec( `${CMD_TS_NODE_WITHOUT_PROJECT_FLAG} ./index.ts`, diff --git a/src/ts-compiler-types.ts b/src/ts-compiler-types.ts index 77d00c4cd..517c9ee8d 100644 --- a/src/ts-compiler-types.ts +++ b/src/ts-compiler-types.ts @@ -32,16 +32,7 @@ export interface TSCommon { createModuleResolutionCache: typeof _ts.createModuleResolutionCache; resolveModuleName: typeof _ts.resolveModuleName; resolveModuleNameFromCache: typeof _ts.resolveModuleNameFromCache; - // Changed in TS 4.7 - resolveTypeReferenceDirective( - typeReferenceDirectiveName: string, - containingFile: string | undefined, - options: _ts.CompilerOptions, - host: _ts.ModuleResolutionHost, - redirectedReference?: _ts.ResolvedProjectReference, - cache?: _ts.TypeReferenceDirectiveResolutionCache, - resolutionMode?: _ts.SourceFile['impliedNodeFormat'] - ): _ts.ResolvedTypeReferenceDirectiveWithFailedLookupLocations; + resolveTypeReferenceDirective: typeof _ts.resolveTypeReferenceDirective; createIncrementalCompilerHost: typeof _ts.createIncrementalCompilerHost; createSourceFile: typeof _ts.createSourceFile; getDefaultLibFileName: typeof _ts.getDefaultLibFileName; @@ -52,16 +43,7 @@ export interface TSCommon { ModuleResolutionKind: typeof _ts.ModuleResolutionKind; } export namespace TSCommon { - export interface LanguageServiceHost extends _ts.LanguageServiceHost { - // Modified in 4.7 - resolveTypeReferenceDirectives?( - typeDirectiveNames: string[] | _ts.FileReference[], - containingFile: string, - redirectedReference: _ts.ResolvedProjectReference | undefined, - options: _ts.CompilerOptions, - containingFileMode?: _ts.SourceFile['impliedNodeFormat'] | undefined - ): (_ts.ResolvedTypeReferenceDirective | undefined)[]; - } + export interface LanguageServiceHost extends _ts.LanguageServiceHost {} export type ModuleResolutionHost = _ts.ModuleResolutionHost; export type ParsedCommandLine = _ts.ParsedCommandLine; export type ResolvedModule = _ts.ResolvedModule; From 2e940322821993aac1c1f20ab5c4eae894e6fd3a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 1 Jun 2022 09:53:06 -0400 Subject: [PATCH 6/7] fix --- src/resolver-functions.ts | 7 +++---- src/ts-compiler-types.ts | 6 ++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/resolver-functions.ts b/src/resolver-functions.ts index ac9dbe9e0..8154bf7f2 100644 --- a/src/resolver-functions.ts +++ b/src/resolver-functions.ts @@ -98,7 +98,8 @@ export function createResolverFunctions(kwargs: { optionsOnlyWithNewerTsVersions: TSCommon.CompilerOptions, containingSourceFile?: TSCommon.SourceFile ): (TSCommon.ResolvedModule | undefined)[] => { - return moduleNames.map((moduleName) => { + return moduleNames.map((moduleName, i) => { + const mode = containingSourceFile ? (ts as any as TSInternal).getModeForResolutionAtIndex?.(containingSourceFile, i) : undefined; const { resolvedModule } = ts.resolveModuleName( moduleName, containingFile, @@ -106,9 +107,7 @@ export function createResolverFunctions(kwargs: { host, moduleResolutionCache, redirectedReference, - containingSourceFile?.impliedNodeFormat - - // (ts as any as typeof import('typescript')).getModeForResolutionAtIndex(containingSourceFile) + mode, ); if (resolvedModule) { fixupResolvedModule(resolvedModule); diff --git a/src/ts-compiler-types.ts b/src/ts-compiler-types.ts index 517c9ee8d..6189192c5 100644 --- a/src/ts-compiler-types.ts +++ b/src/ts-compiler-types.ts @@ -117,6 +117,8 @@ export interface TSInternal { basePath: string, usage: 'files' | 'directories' | 'exclude' ): string | undefined; + // Added in TS 4.7 + getModeForResolutionAtIndex?(file: TSInternal.SourceFileImportsList, index: number): _ts.SourceFile['impliedNodeFormat']; } /** @internal */ export namespace TSInternal { @@ -127,4 +129,8 @@ export namespace TSInternal { getCurrentDirectory(): string; useCaseSensitiveFileNames: boolean; } + // Note: is only a partial declaration, TS sources declare other fields + export interface SourceFileImportsList { + impliedNodeFormat?: TSCommon.SourceFile["impliedNodeFormat"]; +}; } From b124d25dd1f120b8408636e2f422a5c461a2fc7f Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 1 Jun 2022 09:53:27 -0400 Subject: [PATCH 7/7] lint --- src/resolver-functions.ts | 9 +++++++-- src/ts-compiler-types.ts | 9 ++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/resolver-functions.ts b/src/resolver-functions.ts index 8154bf7f2..afe13b463 100644 --- a/src/resolver-functions.ts +++ b/src/resolver-functions.ts @@ -99,7 +99,12 @@ export function createResolverFunctions(kwargs: { containingSourceFile?: TSCommon.SourceFile ): (TSCommon.ResolvedModule | undefined)[] => { return moduleNames.map((moduleName, i) => { - const mode = containingSourceFile ? (ts as any as TSInternal).getModeForResolutionAtIndex?.(containingSourceFile, i) : undefined; + const mode = containingSourceFile + ? (ts as any as TSInternal).getModeForResolutionAtIndex?.( + containingSourceFile, + i + ) + : undefined; const { resolvedModule } = ts.resolveModuleName( moduleName, containingFile, @@ -107,7 +112,7 @@ export function createResolverFunctions(kwargs: { host, moduleResolutionCache, redirectedReference, - mode, + mode ); if (resolvedModule) { fixupResolvedModule(resolvedModule); diff --git a/src/ts-compiler-types.ts b/src/ts-compiler-types.ts index 6189192c5..9077d4f99 100644 --- a/src/ts-compiler-types.ts +++ b/src/ts-compiler-types.ts @@ -118,7 +118,10 @@ export interface TSInternal { usage: 'files' | 'directories' | 'exclude' ): string | undefined; // Added in TS 4.7 - getModeForResolutionAtIndex?(file: TSInternal.SourceFileImportsList, index: number): _ts.SourceFile['impliedNodeFormat']; + getModeForResolutionAtIndex?( + file: TSInternal.SourceFileImportsList, + index: number + ): _ts.SourceFile['impliedNodeFormat']; } /** @internal */ export namespace TSInternal { @@ -131,6 +134,6 @@ export namespace TSInternal { } // Note: is only a partial declaration, TS sources declare other fields export interface SourceFileImportsList { - impliedNodeFormat?: TSCommon.SourceFile["impliedNodeFormat"]; -}; + impliedNodeFormat?: TSCommon.SourceFile['impliedNodeFormat']; + } }