Skip to content

Commit 1d339de

Browse files
committed
Fix #14171: Recognize property assignements to module.export aliases as exports
1 parent 0eec41e commit 1d339de

File tree

5 files changed

+761
-1
lines changed

5 files changed

+761
-1
lines changed

src/compiler/binder.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2227,7 +2227,42 @@ namespace ts {
22272227
declareSymbol(file.symbol.exports, file.symbol, <PropertyAccessExpression>node.left, SymbolFlags.Property | SymbolFlags.Export, SymbolFlags.None);
22282228
}
22292229

2230+
function isExportsOrModuleExportsOrAlias(node: Node): boolean {
2231+
return isExportsIdentifier(node) ||
2232+
isModuleExportsPropertyAccessExpression(node) ||
2233+
isNameOfExportsOrModuleExportsAliasDeclaration(node);
2234+
}
2235+
2236+
function isNameOfExportsOrModuleExportsAliasDeclaration(node: Node) {
2237+
if (node.kind === SyntaxKind.Identifier) {
2238+
const symbol = container.locals.get((<Identifier>node).text);
2239+
if (symbol && symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
2240+
const declaration = symbol.valueDeclaration as VariableDeclaration;
2241+
if (declaration.initializer) {
2242+
return isExportsOrModuleExportsOrAliasOrAssignemnt(declaration.initializer);
2243+
}
2244+
}
2245+
}
2246+
return false;
2247+
}
2248+
2249+
function isExportsOrModuleExportsOrAliasOrAssignemnt(node: Node): boolean {
2250+
return isExportsOrModuleExportsOrAlias(node) ||
2251+
(isAssignmentExpression(node, /*excludeCompoundAssignements*/ true) && (isExportsOrModuleExportsOrAliasOrAssignemnt(node.left) || isExportsOrModuleExportsOrAliasOrAssignemnt(node.right)));
2252+
}
2253+
22302254
function bindModuleExportsAssignment(node: BinaryExpression) {
2255+
// A common practice in node modules is to set 'export = module.exports = {}', this ensures that 'exports'
2256+
// is still pointing to 'module.exports'.
2257+
// We do not want to consider this as 'export=' since a module can have only one of these.
2258+
// Similarlly we do not want to treat 'module.exports = exports' as an 'export='.
2259+
const assignedExpression = getRightMostAssignedExpression(node.right);
2260+
if (isEmptyObjectLiteral(assignedExpression) || isExportsOrModuleExportsOrAlias(assignedExpression)) {
2261+
// Mark it as a module in case there are no other exports in the file
2262+
setCommonJsModuleIndicator(node);
2263+
return;
2264+
}
2265+
22312266
// 'module.exports = expr' assignment
22322267
setCommonJsModuleIndicator(node);
22332268
declareSymbol(file.symbol.exports, file.symbol, node, SymbolFlags.Property | SymbolFlags.Export | SymbolFlags.ValueModule, SymbolFlags.None);
@@ -2284,11 +2319,20 @@ namespace ts {
22842319
leftSideOfAssignment.parent = node;
22852320
target.parent = leftSideOfAssignment;
22862321

2287-
bindPropertyAssignment(target.text, leftSideOfAssignment, /*isPrototypeProperty*/ false);
2322+
if (isNameOfExportsOrModuleExportsAliasDeclaration(target)) {
2323+
// This can be an alias for the 'exports' or 'module.exports' names, e.g.
2324+
// var util = module.exports;
2325+
// util.property = function ...
2326+
bindExportsPropertyAssignment(node);
2327+
}
2328+
else {
2329+
bindPropertyAssignment(target.text, leftSideOfAssignment, /*isPrototypeProperty*/ false);
2330+
}
22882331
}
22892332

22902333
function bindPropertyAssignment(functionName: string, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
22912334
let targetSymbol = container.locals.get(functionName);
2335+
22922336
if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
22932337
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
22942338
}

src/compiler/utilities.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,21 @@ namespace ts {
14251425
return false;
14261426
}
14271427

1428+
export function getRightMostAssignedExpression(node: Node) {
1429+
while (isAssignmentExpression(node, /*excludeCompoundAssignements*/ true)) {
1430+
node = node.right;
1431+
}
1432+
return node;
1433+
}
1434+
1435+
export function isExportsIdentifier(node: Node) {
1436+
return isIdentifier(node) && node.text === "exports";
1437+
}
1438+
1439+
export function isModuleExportsPropertyAccessExpression(node: Node) {
1440+
return isPropertyAccessExpression(node) && isIdentifier(node.expression) && node.expression.text === "module" && node.name.text === "exports";
1441+
}
1442+
14281443
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
14291444
/// assignments we treat as special in the binder
14301445
export function getSpecialPropertyAssignmentKind(expression: Node): SpecialPropertyAssignmentKind {
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
=== tests/cases/conformance/salsa/a.ts ===
2+
3+
import b = require("./b.js");
4+
>b : Symbol(b, Decl(a.ts, 0, 0))
5+
6+
b.func1;
7+
>b.func1 : Symbol(b.func1, Decl(b.js, 0, 27))
8+
>b : Symbol(b, Decl(a.ts, 0, 0))
9+
>func1 : Symbol(b.func1, Decl(b.js, 0, 27))
10+
11+
b.func2;
12+
>b.func2 : Symbol(b.func2, Decl(b.js, 1, 37))
13+
>b : Symbol(b, Decl(a.ts, 0, 0))
14+
>func2 : Symbol(b.func2, Decl(b.js, 1, 37))
15+
16+
b.func3;
17+
>b.func3 : Symbol(b.func3, Decl(b.js, 4, 40))
18+
>b : Symbol(b, Decl(a.ts, 0, 0))
19+
>func3 : Symbol(b.func3, Decl(b.js, 4, 40))
20+
21+
b.func4;
22+
>b.func4 : Symbol(b.func4, Decl(b.js, 5, 43))
23+
>b : Symbol(b, Decl(a.ts, 0, 0))
24+
>func4 : Symbol(b.func4, Decl(b.js, 5, 43))
25+
26+
b.func5;
27+
>b.func5 : Symbol(b.func5, Decl(b.js, 8, 57))
28+
>b : Symbol(b, Decl(a.ts, 0, 0))
29+
>func5 : Symbol(b.func5, Decl(b.js, 8, 57))
30+
31+
b.func6;
32+
>b.func6 : Symbol(b.func6, Decl(b.js, 11, 57))
33+
>b : Symbol(b, Decl(a.ts, 0, 0))
34+
>func6 : Symbol(b.func6, Decl(b.js, 11, 57))
35+
36+
b.func7;
37+
>b.func7 : Symbol(b.func7, Decl(b.js, 15, 60))
38+
>b : Symbol(b, Decl(a.ts, 0, 0))
39+
>func7 : Symbol(b.func7, Decl(b.js, 15, 60))
40+
41+
b.func8;
42+
>b.func8 : Symbol(b.func8, Decl(b.js, 18, 67))
43+
>b : Symbol(b, Decl(a.ts, 0, 0))
44+
>func8 : Symbol(b.func8, Decl(b.js, 18, 67))
45+
46+
b.func9;
47+
>b.func9 : Symbol(b.func9, Decl(b.js, 21, 62))
48+
>b : Symbol(b, Decl(a.ts, 0, 0))
49+
>func9 : Symbol(b.func9, Decl(b.js, 21, 62))
50+
51+
b.func10;
52+
>b.func10 : Symbol(b.func10, Decl(b.js, 24, 62))
53+
>b : Symbol(b, Decl(a.ts, 0, 0))
54+
>func10 : Symbol(b.func10, Decl(b.js, 24, 62))
55+
56+
b.func11;
57+
>b.func11 : Symbol(b.func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
58+
>b : Symbol(b, Decl(a.ts, 0, 0))
59+
>func11 : Symbol(b.func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
60+
61+
b.func12;
62+
>b.func12 : Symbol(b.func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
63+
>b : Symbol(b, Decl(a.ts, 0, 0))
64+
>func12 : Symbol(b.func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
65+
66+
b.func13;
67+
>b.func13 : Symbol(b.func13, Decl(b.js, 35, 30))
68+
>b : Symbol(b, Decl(a.ts, 0, 0))
69+
>func13 : Symbol(b.func13, Decl(b.js, 35, 30))
70+
71+
b.func14;
72+
>b.func14 : Symbol(b.func14, Decl(b.js, 36, 33))
73+
>b : Symbol(b, Decl(a.ts, 0, 0))
74+
>func14 : Symbol(b.func14, Decl(b.js, 36, 33))
75+
76+
b.func15;
77+
>b.func15 : Symbol(b.func15, Decl(b.js, 39, 30))
78+
>b : Symbol(b, Decl(a.ts, 0, 0))
79+
>func15 : Symbol(b.func15, Decl(b.js, 39, 30))
80+
81+
b.func16;
82+
>b.func16 : Symbol(b.func16, Decl(b.js, 40, 33))
83+
>b : Symbol(b, Decl(a.ts, 0, 0))
84+
>func16 : Symbol(b.func16, Decl(b.js, 40, 33))
85+
86+
b.func17;
87+
>b.func17 : Symbol(b.func17, Decl(b.js, 43, 30))
88+
>b : Symbol(b, Decl(a.ts, 0, 0))
89+
>func17 : Symbol(b.func17, Decl(b.js, 43, 30))
90+
91+
b.func18;
92+
>b.func18 : Symbol(b.func18, Decl(b.js, 44, 33))
93+
>b : Symbol(b, Decl(a.ts, 0, 0))
94+
>func18 : Symbol(b.func18, Decl(b.js, 44, 33))
95+
96+
b.func19;
97+
>b.func19 : Symbol(b.func19, Decl(b.js, 47, 20))
98+
>b : Symbol(b, Decl(a.ts, 0, 0))
99+
>func19 : Symbol(b.func19, Decl(b.js, 47, 20))
100+
101+
b.func20;
102+
>b.func20 : Symbol(b.func20, Decl(b.js, 48, 33))
103+
>b : Symbol(b, Decl(a.ts, 0, 0))
104+
>func20 : Symbol(b.func20, Decl(b.js, 48, 33))
105+
106+
107+
=== tests/cases/conformance/salsa/b.js ===
108+
var exportsAlias = exports;
109+
>exportsAlias : Symbol(exportsAlias, Decl(b.js, 0, 3))
110+
111+
exportsAlias.func1 = function () { };
112+
>exportsAlias : Symbol(exportsAlias, Decl(b.js, 0, 3))
113+
114+
exports.func2 = function () { };
115+
>exports : Symbol(func2, Decl(b.js, 1, 37))
116+
>func2 : Symbol(func2, Decl(b.js, 1, 37))
117+
118+
var moduleExportsAlias = module.exports;
119+
>moduleExportsAlias : Symbol(moduleExportsAlias, Decl(b.js, 4, 3))
120+
121+
moduleExportsAlias.func3 = function () { };
122+
>moduleExportsAlias : Symbol(moduleExportsAlias, Decl(b.js, 4, 3))
123+
124+
module.exports.func4 = function () { };
125+
>module.exports : Symbol(func4, Decl(b.js, 5, 43))
126+
>func4 : Symbol(func4, Decl(b.js, 5, 43))
127+
128+
var multipleDeclarationAlias1 = exports = module.exports;
129+
>multipleDeclarationAlias1 : Symbol(multipleDeclarationAlias1, Decl(b.js, 8, 3))
130+
131+
multipleDeclarationAlias1.func5 = function () { };
132+
>multipleDeclarationAlias1 : Symbol(multipleDeclarationAlias1, Decl(b.js, 8, 3))
133+
134+
var multipleDeclarationAlias2 = module.exports = exports;
135+
>multipleDeclarationAlias2 : Symbol(multipleDeclarationAlias2, Decl(b.js, 11, 3))
136+
137+
multipleDeclarationAlias2.func6 = function () { };
138+
>multipleDeclarationAlias2 : Symbol(multipleDeclarationAlias2, Decl(b.js, 11, 3))
139+
140+
var someOtherVariable;
141+
>someOtherVariable : Symbol(someOtherVariable, Decl(b.js, 14, 3))
142+
143+
var multipleDeclarationAlias3 = someOtherVariable = exports;
144+
>multipleDeclarationAlias3 : Symbol(multipleDeclarationAlias3, Decl(b.js, 15, 3))
145+
>someOtherVariable : Symbol(someOtherVariable, Decl(b.js, 14, 3))
146+
147+
multipleDeclarationAlias3.func7 = function () { };
148+
>multipleDeclarationAlias3 : Symbol(multipleDeclarationAlias3, Decl(b.js, 15, 3))
149+
150+
var multipleDeclarationAlias4 = someOtherVariable = module.exports;
151+
>multipleDeclarationAlias4 : Symbol(multipleDeclarationAlias4, Decl(b.js, 18, 3))
152+
>someOtherVariable : Symbol(someOtherVariable, Decl(b.js, 14, 3))
153+
154+
multipleDeclarationAlias4.func8 = function () { };
155+
>multipleDeclarationAlias4 : Symbol(multipleDeclarationAlias4, Decl(b.js, 18, 3))
156+
157+
var multipleDeclarationAlias5 = module.exports = exports = {};
158+
>multipleDeclarationAlias5 : Symbol(multipleDeclarationAlias5, Decl(b.js, 21, 3))
159+
160+
multipleDeclarationAlias5.func9 = function () { };
161+
>multipleDeclarationAlias5 : Symbol(multipleDeclarationAlias5, Decl(b.js, 21, 3))
162+
163+
var multipleDeclarationAlias6 = exports = module.exports = {};
164+
>multipleDeclarationAlias6 : Symbol(multipleDeclarationAlias6, Decl(b.js, 24, 3))
165+
166+
multipleDeclarationAlias6.func10 = function () { };
167+
>multipleDeclarationAlias6 : Symbol(multipleDeclarationAlias6, Decl(b.js, 24, 3))
168+
169+
exports = module.exports = someOtherVariable = {};
170+
>someOtherVariable : Symbol(someOtherVariable, Decl(b.js, 14, 3))
171+
172+
exports.func11 = function () { };
173+
>exports : Symbol(func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
174+
>func11 : Symbol(func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
175+
176+
module.exports.func12 = function () { };
177+
>module.exports : Symbol(func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
178+
>func12 : Symbol(func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
179+
180+
exports = module.exports = someOtherVariable = {};
181+
>someOtherVariable : Symbol(someOtherVariable, Decl(b.js, 14, 3))
182+
183+
exports.func11 = function () { };
184+
>exports : Symbol(func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
185+
>func11 : Symbol(func11, Decl(b.js, 27, 50), Decl(b.js, 31, 50))
186+
187+
module.exports.func12 = function () { };
188+
>module.exports : Symbol(func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
189+
>func12 : Symbol(func12, Decl(b.js, 28, 33), Decl(b.js, 32, 33))
190+
191+
exports = module.exports = {};
192+
exports.func13 = function () { };
193+
>exports : Symbol(func13, Decl(b.js, 35, 30))
194+
>func13 : Symbol(func13, Decl(b.js, 35, 30))
195+
196+
module.exports.func14 = function () { };
197+
>module.exports : Symbol(func14, Decl(b.js, 36, 33))
198+
>func14 : Symbol(func14, Decl(b.js, 36, 33))
199+
200+
exports = module.exports = {};
201+
exports.func15 = function () { };
202+
>exports : Symbol(func15, Decl(b.js, 39, 30))
203+
>func15 : Symbol(func15, Decl(b.js, 39, 30))
204+
205+
module.exports.func16 = function () { };
206+
>module.exports : Symbol(func16, Decl(b.js, 40, 33))
207+
>func16 : Symbol(func16, Decl(b.js, 40, 33))
208+
209+
module.exports = exports = {};
210+
exports.func17 = function () { };
211+
>exports : Symbol(func17, Decl(b.js, 43, 30))
212+
>func17 : Symbol(func17, Decl(b.js, 43, 30))
213+
214+
module.exports.func18 = function () { };
215+
>module.exports : Symbol(func18, Decl(b.js, 44, 33))
216+
>func18 : Symbol(func18, Decl(b.js, 44, 33))
217+
218+
module.exports = {};
219+
exports.func19 = function () { };
220+
>exports : Symbol(func19, Decl(b.js, 47, 20))
221+
>func19 : Symbol(func19, Decl(b.js, 47, 20))
222+
223+
module.exports.func20 = function () { };
224+
>module.exports : Symbol(func20, Decl(b.js, 48, 33))
225+
>func20 : Symbol(func20, Decl(b.js, 48, 33))
226+
227+

0 commit comments

Comments
 (0)