Skip to content

Commit c3d41bb

Browse files
authored
Alias for commonjs require in JS (#39770)
* First attempt at aliases for require * test+initial support for const x=require * 1st round of baseline improvements * 2nd round of baseline updates * support property access after require * check @type tag on require * forbid expando missing namespaces on aliases taken from #39558 as soon as it was created * accept error baselines that are good, actually * Scribbling on d.ts emit code * use getSpecifierForModuleSymbol * hideous hack for module.exports of aliases * Fix module.exports.x --> export list emit * fix isLocalImport predicate * require only creates aliases in JS * re-handle json imports * update fourslash baseline * Cleanup in the checker 1. Simplify alias resolution. 2. Simplify variable-like checking. 3. Make binding skip require calls with type tags -- they fall back to the old require-call code and then check from there. I haven't started on the declaration emit code since I don't know what is going on there nearly as well. * Function for getting module name from require call * First round of cleanup plus a new test Found one missing feature, not sure it's worth adding. * more small cleanup * more cleanup, including lint * use trackSymbol, not serializeTypeForDeclaration * Code review comments, plus remove unneeded code Ad-hoc type reference resolution for `require` isn't needed anymore. * find all refs works * remove old ad-hoc code * make it clear that old behaviour is not that correct * update api baselines * remove outdated comment * PR feedback 1. Fix indentation 2. Add comment for exported JSON emit 3. Add test case for nested-namespace exports. * add a fail-case test (which passes!)
1 parent 97a0729 commit c3d41bb

File tree

111 files changed

+1926
-526
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

111 files changed

+1926
-526
lines changed

src/compiler/binder.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -3209,7 +3209,10 @@ namespace ts {
32093209
}
32103210

32113211
if (!isBindingPattern(node.name)) {
3212-
if (isBlockOrCatchScoped(node)) {
3212+
if (isInJSFile(node) && isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true) && !getJSDocTypeTag(node)) {
3213+
declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
3214+
}
3215+
else if (isBlockOrCatchScoped(node)) {
32133216
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes);
32143217
}
32153218
else if (isParameterDeclaration(node)) {

src/compiler/checker.ts

+151-86
Large diffs are not rendered by default.

src/compiler/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3937,7 +3937,8 @@ namespace ts {
39373937
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
39383938
*/
39393939
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
3940-
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;
3940+
3941+
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
39413942
/**
39423943
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
39433944
* Otherwise returns its input.

src/compiler/utilities.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1856,6 +1856,11 @@ namespace ts {
18561856
return (<ExternalModuleReference>(<ImportEqualsDeclaration>node).moduleReference).expression;
18571857
}
18581858

1859+
export function getExternalModuleRequireArgument(node: Node) {
1860+
return isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true)
1861+
&& (getLeftmostPropertyAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral;
1862+
}
1863+
18591864
export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
18601865
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
18611866
}
@@ -1923,7 +1928,8 @@ namespace ts {
19231928
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: true): node is RequireVariableDeclaration;
19241929
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration;
19251930
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration {
1926-
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(node.initializer, requireStringLiteralLikeArgument);
1931+
node = getRootDeclaration(node);
1932+
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(getLeftmostPropertyAccessExpression(node.initializer), requireStringLiteralLikeArgument);
19271933
}
19281934

19291935
export function isRequireVariableStatement(node: Node, requireStringLiteralLikeArgument = true): node is RequireVariableStatement {
@@ -5446,6 +5452,13 @@ namespace ts {
54465452
return node.kind === SyntaxKind.NamedImports || node.kind === SyntaxKind.NamedExports;
54475453
}
54485454

5455+
export function getLeftmostPropertyAccessExpression(expr: Expression): Expression {
5456+
while (isPropertyAccessExpression(expr)) {
5457+
expr = expr.expression;
5458+
}
5459+
return expr;
5460+
}
5461+
54495462
export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
54505463
while (true) {
54515464
switch (node.kind) {

src/services/goToDefinition.ts

+4-12
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@ namespace ts.GoToDefinition {
3434
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
3535
// For a function, if this is the original function definition, return just sigInfo.
3636
// If this is the original constructor definition, parent is the class.
37-
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration)) ||
38-
// TODO: GH#25533 Following check shouldn't be necessary if 'require' is an alias
39-
symbol.declarations && symbol.declarations.some(d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ false))) {
37+
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
4038
return [sigInfo];
4139
}
4240
else {
@@ -210,15 +208,6 @@ namespace ts.GoToDefinition {
210208
return aliased;
211209
}
212210
}
213-
if (symbol && isInJSFile(node)) {
214-
const requireCall = forEach(symbol.declarations, d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true) ? d.initializer : undefined);
215-
if (requireCall) {
216-
const moduleSymbol = checker.getSymbolAtLocation(requireCall.arguments[0]);
217-
if (moduleSymbol) {
218-
return checker.resolveExternalModuleSymbol(moduleSymbol);
219-
}
220-
}
221-
}
222211
return symbol;
223212
}
224213

@@ -240,6 +229,9 @@ namespace ts.GoToDefinition {
240229
return true;
241230
case SyntaxKind.ImportSpecifier:
242231
return declaration.parent.kind === SyntaxKind.NamedImports;
232+
case SyntaxKind.BindingElement:
233+
case SyntaxKind.VariableDeclaration:
234+
return isInJSFile(declaration) && isRequireVariableDeclaration(declaration, /*requireStringLiteralLikeArgument*/ true);
243235
default:
244236
return false;
245237
}

src/services/importTracker.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ namespace ts.FindAllReferences {
9393
break;
9494
}
9595
}
96-
97-
// Don't support re-exporting 'require()' calls, so just add a single indirect user.
98-
addIndirectUser(direct.getSourceFile());
9996
}
10097
break;
10198

@@ -607,6 +604,8 @@ namespace ts.FindAllReferences {
607604
case SyntaxKind.NamespaceImport:
608605
Debug.assert((parent as ImportClause | NamespaceImport).name === node);
609606
return true;
607+
case SyntaxKind.BindingElement:
608+
return isInJSFile(node) && isRequireVariableDeclaration(parent, /*requireStringLiteralLikeArgument*/ true);
610609
default:
611610
return false;
612611
}
@@ -628,6 +627,14 @@ namespace ts.FindAllReferences {
628627
if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) {
629628
return checker.getExportSpecifierLocalTargetSymbol(declaration)!;
630629
}
630+
else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) {
631+
return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!;
632+
}
633+
else if (isShorthandPropertyAssignment(declaration)
634+
&& isBinaryExpression(declaration.parent.parent)
635+
&& getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) {
636+
return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!;
637+
}
631638
}
632639
}
633640
return symbol;

tests/baselines/reference/ambientRequireFunction.symbols

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
const fs = require("fs");
55
>fs : Symbol(fs, Decl(app.js, 2, 5))
66
>require : Symbol(require, Decl(node.d.ts, 0, 0))
7-
>"fs" : Symbol("fs", Decl(node.d.ts, 0, 50))
7+
>"fs" : Symbol(fs, Decl(node.d.ts, 0, 50))
88

99
const text = fs.readFileSync("/a/b/c");
1010
>text : Symbol(text, Decl(app.js, 3, 5))
11-
>fs.readFileSync : Symbol(readFileSync, Decl(node.d.ts, 2, 21))
11+
>fs.readFileSync : Symbol(fs.readFileSync, Decl(node.d.ts, 2, 21))
1212
>fs : Symbol(fs, Decl(app.js, 2, 5))
13-
>readFileSync : Symbol(readFileSync, Decl(node.d.ts, 2, 21))
13+
>readFileSync : Symbol(fs.readFileSync, Decl(node.d.ts, 2, 21))
1414

1515
=== tests/cases/compiler/node.d.ts ===
1616
declare function require(moduleName: string): any;

tests/baselines/reference/ambientRequireFunction.types

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
/// <reference path="node.d.ts"/>
33

44
const fs = require("fs");
5-
>fs : typeof import("fs")
6-
>require("fs") : typeof import("fs")
5+
>fs : typeof fs
6+
>require("fs") : typeof fs
77
>require : (moduleName: string) => any
88
>"fs" : "fs"
99

1010
const text = fs.readFileSync("/a/b/c");
1111
>text : string
1212
>fs.readFileSync("/a/b/c") : string
1313
>fs.readFileSync : (s: string) => string
14-
>fs : typeof import("fs")
14+
>fs : typeof fs
1515
>readFileSync : (s: string) => string
1616
>"/a/b/c" : "/a/b/c"
1717

tests/baselines/reference/api/tsserverlibrary.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,7 @@ declare namespace ts {
21592159
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
21602160
*/
21612161
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
2162-
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;
2162+
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
21632163
/**
21642164
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
21652165
* Otherwise returns its input.

tests/baselines/reference/api/typescript.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2159,7 +2159,7 @@ declare namespace ts {
21592159
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
21602160
*/
21612161
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
2162-
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;
2162+
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
21632163
/**
21642164
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
21652165
* Otherwise returns its input.

tests/baselines/reference/chainedPrototypeAssignment.symbols

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33
var mod = require('./mod');
44
>mod : Symbol(mod, Decl(use.js, 1, 3))
55
>require : Symbol(require, Decl(types.d.ts, 0, 0))
6-
>'./mod' : Symbol("tests/cases/conformance/salsa/mod", Decl(mod.js, 0, 0))
6+
>'./mod' : Symbol(mod, Decl(mod.js, 0, 0))
77

88
var a = new mod.A()
99
>a : Symbol(a, Decl(use.js, 2, 3))
10-
>mod.A : Symbol(A, Decl(mod.js, 6, 1))
10+
>mod.A : Symbol(mod.A, Decl(mod.js, 6, 1))
1111
>mod : Symbol(mod, Decl(use.js, 1, 3))
12-
>A : Symbol(A, Decl(mod.js, 6, 1))
12+
>A : Symbol(mod.A, Decl(mod.js, 6, 1))
1313

1414
var b = new mod.B()
1515
>b : Symbol(b, Decl(use.js, 3, 3))
16-
>mod.B : Symbol(B, Decl(mod.js, 7, 13))
16+
>mod.B : Symbol(mod.B, Decl(mod.js, 7, 13))
1717
>mod : Symbol(mod, Decl(use.js, 1, 3))
18-
>B : Symbol(B, Decl(mod.js, 7, 13))
18+
>B : Symbol(mod.B, Decl(mod.js, 7, 13))
1919

2020
a.m('nope')
2121
>a.m : Symbol(m, Decl(mod.js, 9, 29))

tests/baselines/reference/chainedPrototypeAssignment.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
=== tests/cases/conformance/salsa/use.js ===
22
/// <reference path='./types.d.ts'/>
33
var mod = require('./mod');
4-
>mod : typeof import("tests/cases/conformance/salsa/mod")
5-
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
4+
>mod : typeof mod
5+
>require('./mod') : typeof mod
66
>require : (name: string) => any
77
>'./mod' : "./mod"
88

99
var a = new mod.A()
1010
>a : A
1111
>new mod.A() : A
1212
>mod.A : typeof A
13-
>mod : typeof import("tests/cases/conformance/salsa/mod")
13+
>mod : typeof mod
1414
>A : typeof A
1515

1616
var b = new mod.B()
1717
>b : B
1818
>new mod.B() : B
1919
>mod.B : typeof B
20-
>mod : typeof import("tests/cases/conformance/salsa/mod")
20+
>mod : typeof mod
2121
>B : typeof B
2222

2323
a.m('nope')

tests/baselines/reference/checkOtherObjectAssignProperty.symbols

+30-30
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
11
=== tests/cases/conformance/jsdoc/importer.js ===
22
const mod = require("./mod1");
3-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
3+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
44
>require : Symbol(require)
5-
>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0))
5+
>"./mod1" : Symbol(mod, Decl(mod1.js, 0, 0))
66

77
mod.thing;
8-
>mod.thing : Symbol(thing, Decl(mod1.js, 0, 42))
9-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
10-
>thing : Symbol(thing, Decl(mod1.js, 0, 42))
8+
>mod.thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
9+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
10+
>thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
1111

1212
mod.other;
13-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
13+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
1414

1515
mod.prop;
16-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
16+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
1717

1818
mod.bad1;
19-
>mod.bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
20-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
21-
>bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
19+
>mod.bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
20+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
21+
>bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
2222

2323
mod.bad2;
24-
>mod.bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
25-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
26-
>bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
24+
>mod.bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
25+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
26+
>bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
2727

2828
mod.bad3;
29-
>mod.bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
30-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
31-
>bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
29+
>mod.bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
30+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
31+
>bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
3232

3333

3434
mod.thing = 0;
35-
>mod.thing : Symbol(thing, Decl(mod1.js, 0, 42))
36-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
37-
>thing : Symbol(thing, Decl(mod1.js, 0, 42))
35+
>mod.thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
36+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
37+
>thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
3838

3939
mod.other = 0;
40-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
40+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
4141

4242
mod.prop = 0;
43-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
43+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
4444

4545
mod.bad1 = 0;
46-
>mod.bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
47-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
48-
>bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
46+
>mod.bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
47+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
48+
>bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
4949

5050
mod.bad2 = 0;
51-
>mod.bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
52-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
53-
>bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
51+
>mod.bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
52+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
53+
>bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
5454

5555
mod.bad3 = 0;
56-
>mod.bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
57-
>mod : Symbol(mod, Decl(importer.js, 0, 5), Decl(importer.js, 6, 9), Decl(importer.js, 9, 14), Decl(importer.js, 10, 14), Decl(importer.js, 11, 13) ... and 2 more)
58-
>bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
56+
>mod.bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
57+
>mod : Symbol(mod, Decl(importer.js, 0, 5))
58+
>bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
5959

6060
=== tests/cases/conformance/jsdoc/mod1.js ===
6161
const obj = { value: 42, writable: true };

0 commit comments

Comments
 (0)