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

Lines changed: 4 additions & 1 deletion
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

Lines changed: 151 additions & 86 deletions
Large diffs are not rendered by default.

src/compiler/types.ts

Lines changed: 2 additions & 1 deletion
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

Lines changed: 14 additions & 1 deletion
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

Lines changed: 4 additions & 12 deletions
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

Lines changed: 10 additions & 3 deletions
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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 3 additions & 3 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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.

0 commit comments

Comments
 (0)