diff --git a/tsc/internal/bundled/libs/lib.es5.d.ts b/tsc/internal/bundled/libs/lib.es5.d.ts index e394111cb0e6c..daffbc7127770 100644 --- a/tsc/internal/bundled/libs/lib.es5.d.ts +++ b/tsc/internal/bundled/libs/lib.es5.d.ts @@ -1677,6 +1677,16 @@ type Uncapitalize = intrinsic; */ type NoInfer = intrinsic; +/** + * A module path string, resolved exactly as an import written in the same + * file would be. + * T is the type of the referenced module, as given by `typeof import(...)`. + * + * The files of a program are fixed before type checking begins, so the + * referenced module must already be part of the program. + */ +type ModuleReference = intrinsic; + /** * Marker for contextual 'this' type */ diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index c720e0fafb7ef..b07b08b18939f 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -355,14 +355,16 @@ const ( IntrinsicTypeKindCapitalize IntrinsicTypeKindUncapitalize IntrinsicTypeKindNoInfer + IntrinsicTypeKindModuleReference ) var intrinsicTypeKinds = map[string]IntrinsicTypeKind{ - "Uppercase": IntrinsicTypeKindUppercase, - "Lowercase": IntrinsicTypeKindLowercase, - "Capitalize": IntrinsicTypeKindCapitalize, - "Uncapitalize": IntrinsicTypeKindUncapitalize, - "NoInfer": IntrinsicTypeKindNoInfer, + "Uppercase": IntrinsicTypeKindUppercase, + "Lowercase": IntrinsicTypeKindLowercase, + "Capitalize": IntrinsicTypeKindCapitalize, + "Uncapitalize": IntrinsicTypeKindUncapitalize, + "NoInfer": IntrinsicTypeKindNoInfer, + "ModuleReference": IntrinsicTypeKindModuleReference, } type MappedTypeModifiers uint32 @@ -563,6 +565,7 @@ type Program interface { GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ModuleKind GetResolvedModule(currentSourceFile ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule GetResolvedModules() map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule] + ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule GetPackagesMap() map[string]bool GetSourceFileMetaData(path tspath.Path) ast.SourceFileMetaData GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node) @@ -9469,6 +9472,14 @@ func (c *Checker) isSignatureApplicable(node *ast.Node, args []*ast.Node, signat checkArgType = argType } effectiveCheckArgumentNode := c.getEffectiveCheckNode(arg) + if moduleType, target := c.getModuleReferenceArgumentTypes(arg, paramType, reportErrors); target != nil { + if moduleType == nil { + // no such module; the diagnostic is reported when reportErrors is set + return false + } + + checkArgType, paramType = moduleType, target + } if !c.checkTypeRelatedToAndOptionallyElaborate(checkArgType, paramType, relation, core.IfElse(reportErrors, effectiveCheckArgumentNode, nil), effectiveCheckArgumentNode, headMessage, diagnosticOutput) { c.maybeAddMissingAwaitInfo(arg, checkArgType, paramType, relation, reportErrors, diagnosticOutput) return false @@ -9557,6 +9568,71 @@ func (c *Checker) getEffectiveCheckNode(argument *ast.Node) *ast.Node { return ast.SkipOuterExpressions(argument, flags) } +func isModuleReferenceType(t *Type) bool { + return t.flags&TypeFlagsStringMapping != 0 && intrinsicTypeKinds[t.symbol.Name] == IntrinsicTypeKindModuleReference +} + +// This handles a string literal passed where a `ModuleReference` is expected. +// The literal is resolved as a module specifier exactly as an import written +// in its place would be, so both relative paths and the CommonJS/ESM +// resolution mode follow the call site rather than wherever `ModuleReference` +// was declared. Callers check and infer the referenced module's type +// against T instead of checking the literal against the parameter. +// +// The second result is T, and is nil when this isn't a module reference argument. +// The first is the referenced module's type, and is nil when the specifier names +// no module in the program, in which case the same diagnostic an unresolvable +// import would produce is reported if reportErrors is set. +func (c *Checker) getModuleReferenceArgumentTypes(arg *ast.Node, paramType *Type, reportErrors bool) (moduleType *Type, target *Type) { + if !isModuleReferenceType(paramType) { + return nil, nil + } + + specifier := c.getEffectiveCheckNode(arg) + + if !ast.IsStringLiteralLike(specifier) { + return nil, nil + } + + if moduleSymbol := c.resolveModuleReference(specifier, reportErrors); moduleSymbol != nil { + if resolved := c.resolveExternalModuleSymbol(moduleSymbol, false); resolved != nil { + moduleType = c.getTypeOfSymbol(resolved) + } + } + + return moduleType, paramType.AsStringMappingType().target +} + +// This resolves the specifier named by a `ModuleReference` argument. +// Module specifiers are collected syntactically and resolved before checking +// begins, and a module reference is not discoverable without type information, +// so its specifier usually has no resolution cached. +// Resolving one here cannot change the set of files in the program. +// The result is used only when it names a file the program already contains, +// which is what keeps compilation phased and its output deterministic. +// https://github.com/microsoft/TypeScript/issues/54022 +func (c *Checker) resolveModuleReference(specifier *ast.Node, reportErrors bool) *ast.Symbol { + if moduleSymbol := c.resolveExternalModuleName(specifier, specifier, true, nil); moduleSymbol != nil { + return moduleSymbol + } + + file := ast.GetSourceFileOfNode(specifier) + resolved := c.program.ResolveModuleName(specifier.Text(), file.FileName(), c.program.GetModeForUsageLocation(file, specifier)) + + if resolved.IsResolved() { + if referenced := c.program.GetSourceFileForResolvedModule(resolved.ResolvedFileName); referenced != nil && referenced.Symbol != nil { + return c.getMergedSymbol(referenced.Symbol) + } + } + + if reportErrors { + // Report whatever an import of the same specifier would have reported. + c.resolveExternalModuleName(specifier, specifier, false, nil) + } + + return nil +} + func (c *Checker) inferTypeArguments(node *ast.Node, signature *Signature, args []*ast.Node, checkMode CheckMode, context *InferenceContext) []*Type { if ast.IsJsxOpeningLikeElement(node) { return c.inferJsxTypeArguments(node, signature, checkMode, context) @@ -9653,7 +9729,17 @@ func (c *Checker) inferTypeArguments(node *ast.Node, signature *Signature, args paramType := c.getTypeAtPosition(signature, i) if c.couldContainTypeVariables(paramType) { argType := c.checkExpressionWithContextualType(arg, paramType, context, checkMode) - c.inferTypes(context.inferences, argType, paramType, InferencePriorityNone, false) + + if moduleType, target := c.getModuleReferenceArgumentTypes(arg, paramType, false); target != nil { + // A specifier naming no module leaves moduleType nil, + // and so makes no inference. The error is reported + // when the signature's applicability is checked. + argType, paramType = moduleType, target + } + + if argType != nil { + c.inferTypes(context.inferences, argType, paramType, InferencePriorityNone, false) + } } } } @@ -27878,6 +27964,10 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type { } return c.stringType case t.flags&TypeFlagsStringMapping != 0: + if isModuleReferenceType(t) { + return c.stringType + } + constraint := c.getNextBaseConstraint(t.Target(), stack) if constraint != nil && constraint != t.Target() { return c.getStringMappingType(t.symbol, constraint) @@ -29558,6 +29648,12 @@ func (c *Checker) getTemplateStringForType(t *Type) string { func (c *Checker) getStringMappingType(symbol *ast.Symbol, t *Type) *Type { switch { + case intrinsicTypeKinds[symbol.Name] == IntrinsicTypeKindModuleReference: + // Unlike the string mapping intrinsics, a module reference's type + // argument is the type of the referenced module rather than a + // string, so there is nothing to map. The type stays deferred + // until a string literal is checked against it. + return c.getStringMappingTypeForGenericType(symbol, t) case t.flags&(TypeFlagsUnion|TypeFlagsNever) != 0: return c.mapType(t, func(t *Type) *Type { return c.getStringMappingType(symbol, t) }) case t.flags&TypeFlagsStringLiteral != 0: diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 9008420fb7cd1..030405b66ef93 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -2534,6 +2534,12 @@ func (c *Checker) isMemberOfStringMapping(source *Type, target *Type) bool { return true case target.flags&(TypeFlagsString|TypeFlagsTemplateLiteral) != 0: return c.isTypeAssignableTo(source, target) + case isModuleReferenceType(target): + // Any string literal may name a module. Whether it actually + // resolves is checked where the literal appears, which is + // the only place the containing file, and the meaning of + // a relative specifier, is known. + return source.flags&TypeFlagsStringLiteral != 0 case target.flags&TypeFlagsStringMapping != 0: // We need to see whether applying the same mappings of the target // onto the source would produce an identical type *and* that diff --git a/tsc/internal/fourslash/tests/util/util.go b/tsc/internal/fourslash/tests/util/util.go index 6c2285301d78c..7eb1d0f477420 100644 --- a/tsc/internal/fourslash/tests/util/util.go +++ b/tsc/internal/fourslash/tests/util/util.go @@ -1032,6 +1032,11 @@ var CompletionGlobalTypeDecls = []fourslash.CompletionsExpectedItem{ Kind: new(lsproto.CompletionItemKindClass), SortText: new(string(ls.SortTextGlobalsOrKeywords)), }, + &lsproto.CompletionItem{ + Label: "ModuleReference", + Kind: new(lsproto.CompletionItemKindClass), + SortText: new(string(ls.SortTextGlobalsOrKeywords)), + }, &lsproto.CompletionItem{ Label: "ThisType", Kind: new(lsproto.CompletionItemKindInterface), diff --git a/tsc/internal/ls/autoimport/aliasresolver.go b/tsc/internal/ls/autoimport/aliasresolver.go index 52955e92c3d09..0348bc6763d09 100644 --- a/tsc/internal/ls/autoimport/aliasresolver.go +++ b/tsc/internal/ls/autoimport/aliasresolver.go @@ -126,6 +126,12 @@ func (r *aliasResolver) GetResolvedModule(currentSourceFile ast.HasFileName, mod return resolved } +func (r *aliasResolver) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule { + resolved, _ := r.moduleResolver.ResolveModuleName(moduleName, containingFile, resolutionMode, nil) + + return resolved +} + // GetSourceFileForResolvedModule implements checker.Program. func (r *aliasResolver) GetSourceFileForResolvedModule(fileName string) *ast.SourceFile { return r.GetSourceFile(fileName) diff --git a/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.errors.txt b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.errors.txt new file mode 100644 index 0000000000000..ec454ed60c86a --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.errors.txt @@ -0,0 +1,65 @@ +main.ts(9,8): error TS2339: Property 'missing' does not exist on type 'typeof import("mod")'. +main.ts(12,26): error TS2741: Property 'label' is missing in type '{ getRandom: () => number; }' but required in type 'typeof import("mod")'. +main.ts(17,15): error TS2741: Property 'doThing' is missing in type 'typeof import("notAPlugin")' but required in type '{ doThing(): void; }'. +main.ts(18,36): error TS2741: Property 'doThing' is missing in type 'typeof import("notAPlugin")' but required in type '{ doThing(): void; }'. +main.ts(28,16): error TS2307: Cannot find module './nope' or its corresponding type declarations. +main.ts(29,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'ModuleReference'. + + +==== mod.ts (0 errors) ==== + export const getRandom = () => 10; + export const label = "mod"; + +==== plugin.ts (0 errors) ==== + export function doThing(): void {} + +==== notAPlugin.ts (0 errors) ==== + export function somethingElse(): void {} + +==== main.ts (6 errors) ==== + declare const jest: { + requireActual(ref: ModuleReference): T; + mock(ref: ModuleReference, factory: () => NoInfer): void; + }; + + const actual = jest.requireActual("./mod"); + actual.getRandom(); + actual.label; + actual.missing; // error, no such export + ~~~~~~~ +!!! error TS2339: Property 'missing' does not exist on type 'typeof import("mod")'. + + jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); + jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing + ~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2741: Property 'label' is missing in type '{ getRandom: () => number; }' but required in type 'typeof import("mod")'. +!!! related TS2728 mod.ts:2:14: 'label' is declared here. +!!! related TS6502 main.ts:3:47: The expected type comes from the return type of this signature. + + // The referenced module is checked against the type parameter's constraint. + declare function requirePlugin(ref: ModuleReference): Promise; + requirePlugin("./plugin"); + requirePlugin("./notAPlugin"); // error + ~~~~~~~~~~~~~~ +!!! error TS2741: Property 'doThing' is missing in type 'typeof import("notAPlugin")' but required in type '{ doThing(): void; }'. +!!! related TS2728 main.ts:15:44: 'doThing' is declared here. + requirePlugin<{ doThing(): void }>("./notAPlugin"); // error + ~~~~~~~~~~~~~~ +!!! error TS2741: Property 'doThing' is missing in type 'typeof import("notAPlugin")' but required in type '{ doThing(): void; }'. +!!! related TS2728 main.ts:18:17: 'doThing' is declared here. + + // A module reference is a string, and forwards to another module reference parameter. + declare function importDeferred(ref: ModuleReference): Promise; + function forward(ref: ModuleReference): Promise { + const specifier: string = ref; + specifier.length; + return importDeferred(ref); + } + + importDeferred("./nope"); // error, no such module + ~~~~~~~~ +!!! error TS2307: Cannot find module './nope' or its corresponding type declarations. + importDeferred(String(1)); // error, not a string literal + ~~~~~~~~~ +!!! error TS2345: Argument of type 'string' is not assignable to parameter of type 'ModuleReference'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.js b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.js new file mode 100644 index 0000000000000..3d7979f24c331 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.js @@ -0,0 +1,89 @@ +//// [tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts] //// + +//// [mod.ts] +export const getRandom = () => 10; +export const label = "mod"; + +//// [plugin.ts] +export function doThing(): void {} + +//// [notAPlugin.ts] +export function somethingElse(): void {} + +//// [main.ts] +declare const jest: { + requireActual(ref: ModuleReference): T; + mock(ref: ModuleReference, factory: () => NoInfer): void; +}; + +const actual = jest.requireActual("./mod"); +actual.getRandom(); +actual.label; +actual.missing; // error, no such export + +jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); +jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing + +// The referenced module is checked against the type parameter's constraint. +declare function requirePlugin(ref: ModuleReference): Promise; +requirePlugin("./plugin"); +requirePlugin("./notAPlugin"); // error +requirePlugin<{ doThing(): void }>("./notAPlugin"); // error + +// A module reference is a string, and forwards to another module reference parameter. +declare function importDeferred(ref: ModuleReference): Promise; +function forward(ref: ModuleReference): Promise { + const specifier: string = ref; + specifier.length; + return importDeferred(ref); +} + +importDeferred("./nope"); // error, no such module +importDeferred(String(1)); // error, not a string literal + + +//// [mod.js] +export const getRandom = () => 10; +export const label = "mod"; +//// [plugin.js] +export function doThing() { } +//// [notAPlugin.js] +export function somethingElse() { } +//// [main.js] +"use strict"; +const actual = jest.requireActual("./mod"); +actual.getRandom(); +actual.label; +actual.missing; // error, no such export +jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); +jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing +requirePlugin("./plugin"); +requirePlugin("./notAPlugin"); // error +requirePlugin("./notAPlugin"); // error +function forward(ref) { + const specifier = ref; + specifier.length; + return importDeferred(ref); +} +importDeferred("./nope"); // error, no such module +importDeferred(String(1)); // error, not a string literal + + +//// [mod.d.ts] +export declare const getRandom: () => number; +export declare const label = "mod"; +//// [plugin.d.ts] +export declare function doThing(): void; +//// [notAPlugin.d.ts] +export declare function somethingElse(): void; +//// [main.d.ts] +declare const jest: { + requireActual(ref: ModuleReference): T; + mock(ref: ModuleReference, factory: () => NoInfer): void; +}; +declare const actual: typeof import("./mod"); +declare function requirePlugin(ref: ModuleReference): Promise; +declare function importDeferred(ref: ModuleReference): Promise; +declare function forward(ref: ModuleReference): Promise; diff --git a/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.symbols b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.symbols new file mode 100644 index 0000000000000..793be1740d774 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.symbols @@ -0,0 +1,136 @@ +//// [tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts] //// + +=== mod.ts === +export const getRandom = () => 10; +>getRandom : Symbol(getRandom, Decl(mod.ts, 0, 12)) + +export const label = "mod"; +>label : Symbol(label, Decl(mod.ts, 1, 12)) + +=== plugin.ts === +export function doThing(): void {} +>doThing : Symbol(doThing, Decl(plugin.ts, 0, 0)) + +=== notAPlugin.ts === +export function somethingElse(): void {} +>somethingElse : Symbol(somethingElse, Decl(notAPlugin.ts, 0, 0)) + +=== main.ts === +declare const jest: { +>jest : Symbol(jest, Decl(main.ts, 0, 13)) + + requireActual(ref: ModuleReference): T; +>requireActual : Symbol(requireActual, Decl(main.ts, 0, 21)) +>T : Symbol(T, Decl(main.ts, 1, 18)) +>ref : Symbol(ref, Decl(main.ts, 1, 21)) +>ModuleReference : Symbol(ModuleReference, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 1, 18)) +>T : Symbol(T, Decl(main.ts, 1, 18)) + + mock(ref: ModuleReference, factory: () => NoInfer): void; +>mock : Symbol(mock, Decl(main.ts, 1, 49)) +>T : Symbol(T, Decl(main.ts, 2, 9)) +>ref : Symbol(ref, Decl(main.ts, 2, 12)) +>ModuleReference : Symbol(ModuleReference, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 2, 9)) +>factory : Symbol(factory, Decl(main.ts, 2, 36)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 2, 9)) + +}; + +const actual = jest.requireActual("./mod"); +>actual : Symbol(actual, Decl(main.ts, 5, 5)) +>jest.requireActual : Symbol(requireActual, Decl(main.ts, 0, 21)) +>jest : Symbol(jest, Decl(main.ts, 0, 13)) +>requireActual : Symbol(requireActual, Decl(main.ts, 0, 21)) + +actual.getRandom(); +>actual.getRandom : Symbol(getRandom, Decl(mod.ts, 0, 12)) +>actual : Symbol(actual, Decl(main.ts, 5, 5)) +>getRandom : Symbol(getRandom, Decl(mod.ts, 0, 12)) + +actual.label; +>actual.label : Symbol(label, Decl(mod.ts, 1, 12)) +>actual : Symbol(actual, Decl(main.ts, 5, 5)) +>label : Symbol(label, Decl(mod.ts, 1, 12)) + +actual.missing; // error, no such export +>actual : Symbol(actual, Decl(main.ts, 5, 5)) + +jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); +>jest.mock : Symbol(mock, Decl(main.ts, 1, 49)) +>jest : Symbol(jest, Decl(main.ts, 0, 13)) +>mock : Symbol(mock, Decl(main.ts, 1, 49)) +>jest.requireActual : Symbol(requireActual, Decl(main.ts, 0, 21)) +>jest : Symbol(jest, Decl(main.ts, 0, 13)) +>requireActual : Symbol(requireActual, Decl(main.ts, 0, 21)) +>getRandom : Symbol(getRandom, Decl(main.ts, 10, 59)) + +jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing +>jest.mock : Symbol(mock, Decl(main.ts, 1, 49)) +>jest : Symbol(jest, Decl(main.ts, 0, 13)) +>mock : Symbol(mock, Decl(main.ts, 1, 49)) +>getRandom : Symbol(getRandom, Decl(main.ts, 11, 27)) + +// The referenced module is checked against the type parameter's constraint. +declare function requirePlugin(ref: ModuleReference): Promise; +>requirePlugin : Symbol(requirePlugin, Decl(main.ts, 11, 52)) +>T : Symbol(T, Decl(main.ts, 14, 31)) +>doThing : Symbol(doThing, Decl(main.ts, 14, 42)) +>ref : Symbol(ref, Decl(main.ts, 14, 62)) +>ModuleReference : Symbol(ModuleReference, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 14, 31)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 14, 31)) + +requirePlugin("./plugin"); +>requirePlugin : Symbol(requirePlugin, Decl(main.ts, 11, 52)) + +requirePlugin("./notAPlugin"); // error +>requirePlugin : Symbol(requirePlugin, Decl(main.ts, 11, 52)) + +requirePlugin<{ doThing(): void }>("./notAPlugin"); // error +>requirePlugin : Symbol(requirePlugin, Decl(main.ts, 11, 52)) +>doThing : Symbol(doThing, Decl(main.ts, 17, 15)) + +// A module reference is a string, and forwards to another module reference parameter. +declare function importDeferred(ref: ModuleReference): Promise; +>importDeferred : Symbol(importDeferred, Decl(main.ts, 17, 51)) +>T : Symbol(T, Decl(main.ts, 20, 32)) +>ref : Symbol(ref, Decl(main.ts, 20, 35)) +>ModuleReference : Symbol(ModuleReference, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 20, 32)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 20, 32)) + +function forward(ref: ModuleReference): Promise { +>forward : Symbol(forward, Decl(main.ts, 20, 72)) +>T : Symbol(T, Decl(main.ts, 21, 17)) +>ref : Symbol(ref, Decl(main.ts, 21, 20)) +>ModuleReference : Symbol(ModuleReference, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 21, 17)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --)) +>T : Symbol(T, Decl(main.ts, 21, 17)) + + const specifier: string = ref; +>specifier : Symbol(specifier, Decl(main.ts, 22, 9)) +>ref : Symbol(ref, Decl(main.ts, 21, 20)) + + specifier.length; +>specifier.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) +>specifier : Symbol(specifier, Decl(main.ts, 22, 9)) +>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --)) + + return importDeferred(ref); +>importDeferred : Symbol(importDeferred, Decl(main.ts, 17, 51)) +>ref : Symbol(ref, Decl(main.ts, 21, 20)) +} + +importDeferred("./nope"); // error, no such module +>importDeferred : Symbol(importDeferred, Decl(main.ts, 17, 51)) + +importDeferred(String(1)); // error, not a string literal +>importDeferred : Symbol(importDeferred, Decl(main.ts, 17, 51)) +>String : Symbol(String, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --) ... and 7 more) + diff --git a/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.types b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.types new file mode 100644 index 0000000000000..554279c4fefb9 --- /dev/null +++ b/tsc/testdata/baselines/reference/conformance/moduleReferenceIntrinsic.types @@ -0,0 +1,148 @@ +//// [tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts] //// + +=== mod.ts === +export const getRandom = () => 10; +>getRandom : () => number +>() => 10 : () => number +>10 : 10 + +export const label = "mod"; +>label : "mod" +>"mod" : "mod" + +=== plugin.ts === +export function doThing(): void {} +>doThing : () => void + +=== notAPlugin.ts === +export function somethingElse(): void {} +>somethingElse : () => void + +=== main.ts === +declare const jest: { +>jest : { requireActual(ref: ModuleReference): T; mock(ref: ModuleReference, factory: () => NoInfer): void; } + + requireActual(ref: ModuleReference): T; +>requireActual : (ref: ModuleReference) => T +>ref : ModuleReference + + mock(ref: ModuleReference, factory: () => NoInfer): void; +>mock : (ref: ModuleReference, factory: () => NoInfer) => void +>ref : ModuleReference +>factory : () => NoInfer + +}; + +const actual = jest.requireActual("./mod"); +>actual : typeof import("./mod") +>jest.requireActual("./mod") : typeof import("./mod") +>jest.requireActual : (ref: ModuleReference) => T +>jest : { requireActual(ref: ModuleReference): T; mock(ref: ModuleReference, factory: () => NoInfer): void; } +>requireActual : (ref: ModuleReference) => T +>"./mod" : "./mod" + +actual.getRandom(); +>actual.getRandom() : number +>actual.getRandom : () => number +>actual : typeof import("./mod") +>getRandom : () => number + +actual.label; +>actual.label : "mod" +>actual : typeof import("./mod") +>label : "mod" + +actual.missing; // error, no such export +>actual.missing : any +>actual : typeof import("./mod") +>missing : any + +jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); +>jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })) : void +>jest.mock : (ref: ModuleReference, factory: () => NoInfer) => void +>jest : { requireActual(ref: ModuleReference): T; mock(ref: ModuleReference, factory: () => NoInfer): void; } +>mock : (ref: ModuleReference, factory: () => NoInfer) => void +>"./mod" : "./mod" +>() => ({ ...jest.requireActual("./mod"), getRandom: () => 10 }) : () => { label: "mod"; getRandom: () => number; } +>({ ...jest.requireActual("./mod"), getRandom: () => 10 }) : { label: "mod"; getRandom: () => number; } +>{ ...jest.requireActual("./mod"), getRandom: () => 10 } : { label: "mod"; getRandom: () => number; } +>jest.requireActual("./mod") : typeof import("./mod") +>jest.requireActual : (ref: ModuleReference) => T +>jest : { requireActual(ref: ModuleReference): T; mock(ref: ModuleReference, factory: () => NoInfer): void; } +>requireActual : (ref: ModuleReference) => T +>"./mod" : "./mod" +>getRandom : () => number +>() => 10 : () => number +>10 : 10 + +jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing +>jest.mock("./mod", () => ({ getRandom: () => 10 })) : void +>jest.mock : (ref: ModuleReference, factory: () => NoInfer) => void +>jest : { requireActual(ref: ModuleReference): T; mock(ref: ModuleReference, factory: () => NoInfer): void; } +>mock : (ref: ModuleReference, factory: () => NoInfer) => void +>"./mod" : "./mod" +>() => ({ getRandom: () => 10 }) : () => { getRandom: () => number; } +>({ getRandom: () => 10 }) : { getRandom: () => number; } +>{ getRandom: () => 10 } : { getRandom: () => number; } +>getRandom : () => number +>() => 10 : () => number +>10 : 10 + +// The referenced module is checked against the type parameter's constraint. +declare function requirePlugin(ref: ModuleReference): Promise; +>requirePlugin : (ref: ModuleReference) => Promise +>doThing : () => void +>ref : ModuleReference + +requirePlugin("./plugin"); +>requirePlugin("./plugin") : Promise +>requirePlugin : (ref: ModuleReference) => Promise +>"./plugin" : "./plugin" + +requirePlugin("./notAPlugin"); // error +>requirePlugin("./notAPlugin") : Promise<{ doThing(): void; }> +>requirePlugin : (ref: ModuleReference) => Promise +>"./notAPlugin" : "./notAPlugin" + +requirePlugin<{ doThing(): void }>("./notAPlugin"); // error +>requirePlugin<{ doThing(): void }>("./notAPlugin") : Promise<{ doThing(): void; }> +>requirePlugin : (ref: ModuleReference) => Promise +>doThing : () => void +>"./notAPlugin" : "./notAPlugin" + +// A module reference is a string, and forwards to another module reference parameter. +declare function importDeferred(ref: ModuleReference): Promise; +>importDeferred : (ref: ModuleReference) => Promise +>ref : ModuleReference + +function forward(ref: ModuleReference): Promise { +>forward : (ref: ModuleReference) => Promise +>ref : ModuleReference + + const specifier: string = ref; +>specifier : string +>ref : ModuleReference + + specifier.length; +>specifier.length : number +>specifier : string +>length : number + + return importDeferred(ref); +>importDeferred(ref) : Promise +>importDeferred : (ref: ModuleReference) => Promise +>ref : ModuleReference +} + +importDeferred("./nope"); // error, no such module +>importDeferred("./nope") : Promise +>importDeferred : (ref: ModuleReference) => Promise +>"./nope" : "./nope" + +importDeferred(String(1)); // error, not a string literal +>importDeferred(String(1)) : Promise +>importDeferred : (ref: ModuleReference) => Promise +>String(1) : string +>String : StringConstructor +>1 : 1 + diff --git a/tsc/testdata/tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts b/tsc/testdata/tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts new file mode 100644 index 0000000000000..acb175c869db6 --- /dev/null +++ b/tsc/testdata/tests/cases/conformance/types/typeAliases/moduleReferenceIntrinsic.ts @@ -0,0 +1,45 @@ +// @strict: true +// @module: esnext +// @moduleResolution: bundler +// @declaration: true + +// @filename: mod.ts +export const getRandom = () => 10; +export const label = "mod"; + +// @filename: plugin.ts +export function doThing(): void {} + +// @filename: notAPlugin.ts +export function somethingElse(): void {} + +// @filename: main.ts +declare const jest: { + requireActual(ref: ModuleReference): T; + mock(ref: ModuleReference, factory: () => NoInfer): void; +}; + +const actual = jest.requireActual("./mod"); +actual.getRandom(); +actual.label; +actual.missing; // error, no such export + +jest.mock("./mod", () => ({ ...jest.requireActual("./mod"), getRandom: () => 10 })); +jest.mock("./mod", () => ({ getRandom: () => 10 })); // error, 'label' is missing + +// The referenced module is checked against the type parameter's constraint. +declare function requirePlugin(ref: ModuleReference): Promise; +requirePlugin("./plugin"); +requirePlugin("./notAPlugin"); // error +requirePlugin<{ doThing(): void }>("./notAPlugin"); // error + +// A module reference is a string, and forwards to another module reference parameter. +declare function importDeferred(ref: ModuleReference): Promise; +function forward(ref: ModuleReference): Promise { + const specifier: string = ref; + specifier.length; + return importDeferred(ref); +} + +importDeferred("./nope"); // error, no such module +importDeferred(String(1)); // error, not a string literal