Skip to content

Commit

Permalink
Alias for commonjs require in JS (#39770)
Browse files Browse the repository at this point in the history
* 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!)
  • Loading branch information
sandersn committed Aug 17, 2020
1 parent 97a0729 commit c3d41bb
Show file tree
Hide file tree
Showing 111 changed files with 1,926 additions and 526 deletions.
5 changes: 4 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3209,7 +3209,10 @@ namespace ts {
}

if (!isBindingPattern(node.name)) {
if (isBlockOrCatchScoped(node)) {
if (isInJSFile(node) && isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true) && !getJSDocTypeTag(node)) {
declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
}
else if (isBlockOrCatchScoped(node)) {
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes);
}
else if (isParameterDeclaration(node)) {
Expand Down
237 changes: 151 additions & 86 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3937,7 +3937,8 @@ namespace ts {
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
*/
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;

getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
/**
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
* Otherwise returns its input.
Expand Down
15 changes: 14 additions & 1 deletion src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1856,6 +1856,11 @@ namespace ts {
return (<ExternalModuleReference>(<ImportEqualsDeclaration>node).moduleReference).expression;
}

export function getExternalModuleRequireArgument(node: Node) {
return isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true)
&& (getLeftmostPropertyAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral;
}

export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
return node.kind === SyntaxKind.ImportEqualsDeclaration && (<ImportEqualsDeclaration>node).moduleReference.kind !== SyntaxKind.ExternalModuleReference;
}
Expand Down Expand Up @@ -1923,7 +1928,8 @@ namespace ts {
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: true): node is RequireVariableDeclaration;
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration;
export function isRequireVariableDeclaration(node: Node, requireStringLiteralLikeArgument: boolean): node is VariableDeclaration {
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(node.initializer, requireStringLiteralLikeArgument);
node = getRootDeclaration(node);
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(getLeftmostPropertyAccessExpression(node.initializer), requireStringLiteralLikeArgument);
}

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

export function getLeftmostPropertyAccessExpression(expr: Expression): Expression {
while (isPropertyAccessExpression(expr)) {
expr = expr.expression;
}
return expr;
}

export function getLeftmostExpression(node: Expression, stopAtCallExpressions: boolean) {
while (true) {
switch (node.kind) {
Expand Down
16 changes: 4 additions & 12 deletions src/services/goToDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ namespace ts.GoToDefinition {
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
// For a function, if this is the original function definition, return just sigInfo.
// If this is the original constructor definition, parent is the class.
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration)) ||
// TODO: GH#25533 Following check shouldn't be necessary if 'require' is an alias
symbol.declarations && symbol.declarations.some(d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ false))) {
if (typeChecker.getRootSymbols(symbol).some(s => symbolMatchesSignature(s, calledDeclaration))) {
return [sigInfo];
}
else {
Expand Down Expand Up @@ -210,15 +208,6 @@ namespace ts.GoToDefinition {
return aliased;
}
}
if (symbol && isInJSFile(node)) {
const requireCall = forEach(symbol.declarations, d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ true) ? d.initializer : undefined);
if (requireCall) {
const moduleSymbol = checker.getSymbolAtLocation(requireCall.arguments[0]);
if (moduleSymbol) {
return checker.resolveExternalModuleSymbol(moduleSymbol);
}
}
}
return symbol;
}

Expand All @@ -240,6 +229,9 @@ namespace ts.GoToDefinition {
return true;
case SyntaxKind.ImportSpecifier:
return declaration.parent.kind === SyntaxKind.NamedImports;
case SyntaxKind.BindingElement:
case SyntaxKind.VariableDeclaration:
return isInJSFile(declaration) && isRequireVariableDeclaration(declaration, /*requireStringLiteralLikeArgument*/ true);
default:
return false;
}
Expand Down
13 changes: 10 additions & 3 deletions src/services/importTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,6 @@ namespace ts.FindAllReferences {
break;
}
}

// Don't support re-exporting 'require()' calls, so just add a single indirect user.
addIndirectUser(direct.getSourceFile());
}
break;

Expand Down Expand Up @@ -607,6 +604,8 @@ namespace ts.FindAllReferences {
case SyntaxKind.NamespaceImport:
Debug.assert((parent as ImportClause | NamespaceImport).name === node);
return true;
case SyntaxKind.BindingElement:
return isInJSFile(node) && isRequireVariableDeclaration(parent, /*requireStringLiteralLikeArgument*/ true);
default:
return false;
}
Expand All @@ -628,6 +627,14 @@ namespace ts.FindAllReferences {
if (isExportSpecifier(declaration) && !declaration.propertyName && !declaration.parent.parent.moduleSpecifier) {
return checker.getExportSpecifierLocalTargetSymbol(declaration)!;
}
else if (isPropertyAccessExpression(declaration) && isModuleExportsAccessExpression(declaration.expression) && !isPrivateIdentifier(declaration.name)) {
return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!;
}
else if (isShorthandPropertyAssignment(declaration)
&& isBinaryExpression(declaration.parent.parent)
&& getAssignmentDeclarationKind(declaration.parent.parent) === AssignmentDeclarationKind.ModuleExports) {
return checker.getExportSpecifierLocalTargetSymbol(declaration.name)!;
}
}
}
return symbol;
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/ambientRequireFunction.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
const fs = require("fs");
>fs : Symbol(fs, Decl(app.js, 2, 5))
>require : Symbol(require, Decl(node.d.ts, 0, 0))
>"fs" : Symbol("fs", Decl(node.d.ts, 0, 50))
>"fs" : Symbol(fs, Decl(node.d.ts, 0, 50))

const text = fs.readFileSync("/a/b/c");
>text : Symbol(text, Decl(app.js, 3, 5))
>fs.readFileSync : Symbol(readFileSync, Decl(node.d.ts, 2, 21))
>fs.readFileSync : Symbol(fs.readFileSync, Decl(node.d.ts, 2, 21))
>fs : Symbol(fs, Decl(app.js, 2, 5))
>readFileSync : Symbol(readFileSync, Decl(node.d.ts, 2, 21))
>readFileSync : Symbol(fs.readFileSync, Decl(node.d.ts, 2, 21))

=== tests/cases/compiler/node.d.ts ===
declare function require(moduleName: string): any;
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/ambientRequireFunction.types
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
/// <reference path="node.d.ts"/>

const fs = require("fs");
>fs : typeof import("fs")
>require("fs") : typeof import("fs")
>fs : typeof fs
>require("fs") : typeof fs
>require : (moduleName: string) => any
>"fs" : "fs"

const text = fs.readFileSync("/a/b/c");
>text : string
>fs.readFileSync("/a/b/c") : string
>fs.readFileSync : (s: string) => string
>fs : typeof import("fs")
>fs : typeof fs
>readFileSync : (s: string) => string
>"/a/b/c" : "/a/b/c"

Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2159,7 +2159,7 @@ declare namespace ts {
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
*/
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
/**
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
* Otherwise returns its input.
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2159,7 +2159,7 @@ declare namespace ts {
* This is necessary as an identifier in short-hand property assignment can contains two meaning: property name and property value.
*/
getShorthandAssignmentValueSymbol(location: Node): Symbol | undefined;
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier): Symbol | undefined;
getExportSpecifierLocalTargetSymbol(location: ExportSpecifier | Identifier): Symbol | undefined;
/**
* If a symbol is a local symbol with an associated exported symbol, returns the exported symbol.
* Otherwise returns its input.
Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/chainedPrototypeAssignment.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@
var mod = require('./mod');
>mod : Symbol(mod, Decl(use.js, 1, 3))
>require : Symbol(require, Decl(types.d.ts, 0, 0))
>'./mod' : Symbol("tests/cases/conformance/salsa/mod", Decl(mod.js, 0, 0))
>'./mod' : Symbol(mod, Decl(mod.js, 0, 0))

var a = new mod.A()
>a : Symbol(a, Decl(use.js, 2, 3))
>mod.A : Symbol(A, Decl(mod.js, 6, 1))
>mod.A : Symbol(mod.A, Decl(mod.js, 6, 1))
>mod : Symbol(mod, Decl(use.js, 1, 3))
>A : Symbol(A, Decl(mod.js, 6, 1))
>A : Symbol(mod.A, Decl(mod.js, 6, 1))

var b = new mod.B()
>b : Symbol(b, Decl(use.js, 3, 3))
>mod.B : Symbol(B, Decl(mod.js, 7, 13))
>mod.B : Symbol(mod.B, Decl(mod.js, 7, 13))
>mod : Symbol(mod, Decl(use.js, 1, 3))
>B : Symbol(B, Decl(mod.js, 7, 13))
>B : Symbol(mod.B, Decl(mod.js, 7, 13))

a.m('nope')
>a.m : Symbol(m, Decl(mod.js, 9, 29))
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/chainedPrototypeAssignment.types
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
=== tests/cases/conformance/salsa/use.js ===
/// <reference path='./types.d.ts'/>
var mod = require('./mod');
>mod : typeof import("tests/cases/conformance/salsa/mod")
>require('./mod') : typeof import("tests/cases/conformance/salsa/mod")
>mod : typeof mod
>require('./mod') : typeof mod
>require : (name: string) => any
>'./mod' : "./mod"

var a = new mod.A()
>a : A
>new mod.A() : A
>mod.A : typeof A
>mod : typeof import("tests/cases/conformance/salsa/mod")
>mod : typeof mod
>A : typeof A

var b = new mod.B()
>b : B
>new mod.B() : B
>mod.B : typeof B
>mod : typeof import("tests/cases/conformance/salsa/mod")
>mod : typeof mod
>B : typeof B

a.m('nope')
Expand Down
60 changes: 30 additions & 30 deletions tests/baselines/reference/checkOtherObjectAssignProperty.symbols
Original file line number Diff line number Diff line change
@@ -1,61 +1,61 @@
=== tests/cases/conformance/jsdoc/importer.js ===
const mod = require("./mod1");
>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)
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>require : Symbol(require)
>"./mod1" : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0))
>"./mod1" : Symbol(mod, Decl(mod1.js, 0, 0))

mod.thing;
>mod.thing : Symbol(thing, Decl(mod1.js, 0, 42))
>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)
>thing : Symbol(thing, Decl(mod1.js, 0, 42))
>mod.thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))

mod.other;
>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)
>mod : Symbol(mod, Decl(importer.js, 0, 5))

mod.prop;
>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)
>mod : Symbol(mod, Decl(importer.js, 0, 5))

mod.bad1;
>mod.bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
>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)
>bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
>mod.bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))

mod.bad2;
>mod.bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
>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)
>bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
>mod.bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))

mod.bad3;
>mod.bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
>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)
>bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
>mod.bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))


mod.thing = 0;
>mod.thing : Symbol(thing, Decl(mod1.js, 0, 42))
>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)
>thing : Symbol(thing, Decl(mod1.js, 0, 42))
>mod.thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>thing : Symbol(mod.thing, Decl(mod1.js, 0, 42))

mod.other = 0;
>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)
>mod : Symbol(mod, Decl(importer.js, 0, 5))

mod.prop = 0;
>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)
>mod : Symbol(mod, Decl(importer.js, 0, 5))

mod.bad1 = 0;
>mod.bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
>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)
>bad1 : Symbol(bad1, Decl(mod1.js, 10, 72))
>mod.bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad1 : Symbol(mod.bad1, Decl(mod1.js, 10, 72))

mod.bad2 = 0;
>mod.bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
>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)
>bad2 : Symbol(bad2, Decl(mod1.js, 13, 44))
>mod.bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad2 : Symbol(mod.bad2, Decl(mod1.js, 13, 44))

mod.bad3 = 0;
>mod.bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
>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)
>bad3 : Symbol(bad3, Decl(mod1.js, 14, 77))
>mod.bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))
>mod : Symbol(mod, Decl(importer.js, 0, 5))
>bad3 : Symbol(mod.bad3, Decl(mod1.js, 14, 77))

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

0 comments on commit c3d41bb

Please sign in to comment.