Fix merging module augmentations to pattern ambient modules

This commit is contained in:
Andrew Branch
2019-04-23 12:04:04 -07:00
parent 53c92d608f
commit 7409a04010
6 changed files with 140 additions and 3 deletions

View File

@@ -500,6 +500,7 @@ namespace ts {
* This is only used if there is no exact match.
*/
let patternAmbientModules: PatternAmbientModule[];
let patternAmbientModuleAugmentations: Map<Symbol> | undefined;
let globalObjectType: ObjectType;
let globalFunctionType: ObjectType;
@@ -890,7 +891,7 @@ namespace ts {
* Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it.
* If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it.
*/
function mergeSymbol(target: Symbol, source: Symbol): Symbol {
function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol {
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
(source.flags | target.flags) & SymbolFlags.Assignment) {
Debug.assert(source !== target);
@@ -923,7 +924,9 @@ namespace ts {
if (!target.exports) target.exports = createSymbolTable();
mergeSymbolTable(target.exports, source.exports);
}
recordMergedSymbol(target, source);
if (!unidirectional) {
recordMergedSymbol(target, source);
}
}
else if (target.flags & SymbolFlags.NamespaceModule) {
error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
@@ -1023,7 +1026,22 @@ namespace ts {
// obtain item referenced by 'export='
mainModule = resolveExternalModuleSymbol(mainModule);
if (mainModule.flags & SymbolFlags.Namespace) {
mainModule = mergeSymbol(mainModule, moduleAugmentation.symbol);
// If were merging an augmentation to a pattern ambient module, we want to
// perform the merge unidirectionally from the augmentation ('a.foo') to
// the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you
// all the exports both from the pattern and from the augmentation, but
// 'getMergedSymbol()' on *.foo only gives you exports from *.foo.
if (some(patternAmbientModules, module => mainModule === module.symbol)) {
const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true);
if (!patternAmbientModuleAugmentations) {
patternAmbientModuleAugmentations = createMap();
}
// moduleName will be a StringLiteral since this is not `declare global`.
patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged);
}
else {
mergeSymbol(mainModule, moduleAugmentation.symbol);
}
}
else {
// moduleName will be a StringLiteral since this is not `declare global`.
@@ -2391,6 +2409,14 @@ namespace ts {
if (patternAmbientModules) {
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference);
if (pattern) {
// If the module reference matched a pattern ambient module ('*.foo') but theres also a
// module augmentation by the specific name requested ('a.foo'), we store the merged symbol
// by the augmentation name ('a.foo'), because asking for *.foo should not give you exports
// from a.foo.
const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference);
if (augmentation) {
return getMergedSymbol(augmentation);
}
return getMergedSymbol(pattern.symbol);
}
}

View File

@@ -0,0 +1,20 @@
tests/cases/conformance/ambient/testB.ts(1,22): error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.
==== tests/cases/conformance/ambient/types.ts (0 errors) ====
declare module "*.foo" {
let everywhere: string;
}
==== tests/cases/conformance/ambient/testA.ts (0 errors) ====
import { everywhere, onlyInA } from "a.foo";
declare module "a.foo" {
let onlyInA: number;
}
==== tests/cases/conformance/ambient/testB.ts (1 errors) ====
import { everywhere, onlyInA } from "b.foo"; // Error
~~~~~~~
!!! error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.

View File

@@ -0,0 +1,25 @@
//// [tests/cases/conformance/ambient/ambientDeclarationsPatterns_merging1.ts] ////
//// [types.ts]
declare module "*.foo" {
let everywhere: string;
}
//// [testA.ts]
import { everywhere, onlyInA } from "a.foo";
declare module "a.foo" {
let onlyInA: number;
}
//// [testB.ts]
import { everywhere, onlyInA } from "b.foo"; // Error
//// [types.js]
//// [testA.js]
"use strict";
exports.__esModule = true;
//// [testB.js]
"use strict";
exports.__esModule = true;

View File

@@ -0,0 +1,26 @@
=== tests/cases/conformance/ambient/types.ts ===
declare module "*.foo" {
>"*.foo" : Symbol("*.foo", Decl(types.ts, 0, 0))
let everywhere: string;
>everywhere : Symbol(everywhere, Decl(types.ts, 1, 5))
}
=== tests/cases/conformance/ambient/testA.ts ===
import { everywhere, onlyInA } from "a.foo";
>everywhere : Symbol(everywhere, Decl(testA.ts, 0, 8))
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 0, 20))
declare module "a.foo" {
>"a.foo" : Symbol("a.foo", Decl(testA.ts, 0, 44), Decl(types.ts, 0, 0))
let onlyInA: number;
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 2, 5))
}
=== tests/cases/conformance/ambient/testB.ts ===
import { everywhere, onlyInA } from "b.foo"; // Error
>everywhere : Symbol(everywhere, Decl(testB.ts, 0, 8))
>onlyInA : Symbol(onlyInA, Decl(testB.ts, 0, 20))

View File

@@ -0,0 +1,26 @@
=== tests/cases/conformance/ambient/types.ts ===
declare module "*.foo" {
>"*.foo" : typeof import("*.foo")
let everywhere: string;
>everywhere : string
}
=== tests/cases/conformance/ambient/testA.ts ===
import { everywhere, onlyInA } from "a.foo";
>everywhere : string
>onlyInA : number
declare module "a.foo" {
>"a.foo" : typeof import("a.foo")
let onlyInA: number;
>onlyInA : number
}
=== tests/cases/conformance/ambient/testB.ts ===
import { everywhere, onlyInA } from "b.foo"; // Error
>everywhere : string
>onlyInA : any

View File

@@ -0,0 +1,14 @@
// @filename: types.ts
declare module "*.foo" {
let everywhere: string;
}
// @filename: testA.ts
import { everywhere, onlyInA } from "a.foo";
declare module "a.foo" {
let onlyInA: number;
}
// @filename: testB.ts
import { everywhere, onlyInA } from "b.foo"; // Error