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!)
This commit is contained in:
Nathan Shively-Sanders
2020-08-17 14:00:37 -07:00
committed by GitHub
parent 97a072926f
commit c3d41bbb73
111 changed files with 1926 additions and 526 deletions

View File

@@ -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)) {

View File

@@ -1919,11 +1919,8 @@ namespace ts {
if (lastLocation && (
lastLocation === (location as BindingElement).initializer ||
lastLocation === (location as BindingElement).name && isBindingPattern(lastLocation))) {
const root = getRootDeclaration(location);
if (root.kind === SyntaxKind.Parameter) {
if (!associatedDeclarationForContainingInitializerOrBindingName) {
associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement;
}
if (isParameterDeclaration(location as BindingElement) && !associatedDeclarationForContainingInitializerOrBindingName) {
associatedDeclarationForContainingInitializerOrBindingName = location as BindingElement;
}
}
break;
@@ -2372,32 +2369,41 @@ namespace ts {
* {name: <EntityNameExpression>}
*/
function isAliasSymbolDeclaration(node: Node): boolean {
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
node.kind === SyntaxKind.NamespaceExportDeclaration ||
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
node.kind === SyntaxKind.NamespaceImport ||
node.kind === SyntaxKind.NamespaceExport ||
node.kind === SyntaxKind.ImportSpecifier ||
node.kind === SyntaxKind.ExportSpecifier ||
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node) ||
isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node) ||
isPropertyAccessExpression(node)
return node.kind === SyntaxKind.ImportEqualsDeclaration
|| node.kind === SyntaxKind.NamespaceExportDeclaration
|| node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name
|| node.kind === SyntaxKind.NamespaceImport
|| node.kind === SyntaxKind.NamespaceExport
|| node.kind === SyntaxKind.ImportSpecifier
|| node.kind === SyntaxKind.ExportSpecifier
|| node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node)
|| isBinaryExpression(node) && getAssignmentDeclarationKind(node) === AssignmentDeclarationKind.ModuleExports && exportAssignmentIsAlias(node)
|| isPropertyAccessExpression(node)
&& isBinaryExpression(node.parent)
&& node.parent.left === node
&& node.parent.operatorToken.kind === SyntaxKind.EqualsToken
&& isAliasableOrJsExpression(node.parent.right) ||
node.kind === SyntaxKind.ShorthandPropertyAssignment ||
node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer);
&& isAliasableOrJsExpression(node.parent.right)
|| node.kind === SyntaxKind.ShorthandPropertyAssignment
|| node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer)
|| isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true);
}
function isAliasableOrJsExpression(e: Expression) {
return isAliasableExpression(e) || isFunctionExpression(e) && isJSConstructor(e);
}
function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol | undefined {
if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) {
const immediate = resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node));
const resolved = resolveExternalModuleSymbol(immediate);
function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration | VariableDeclaration, dontResolveAlias: boolean): Symbol | undefined {
if (isVariableDeclaration(node) && node.initializer && isPropertyAccessExpression(node.initializer)) {
const name = (node.initializer.expression as CallExpression).arguments[0] as StringLiteral;
return isIdentifier(node.initializer.name)
? getPropertyOfType(resolveExternalModuleTypeByLiteral(name), node.initializer.name.escapedText)
: undefined;
}
if (isVariableDeclaration(node) || node.moduleReference.kind === SyntaxKind.ExternalModuleReference) {
const immediate = resolveExternalModuleName(
node,
getExternalModuleRequireArgument(node) || getExternalModuleImportEqualsDeclarationExpression(node));
const resolved = resolveExternalModuleSymbol(immediate);
markSymbolOfAliasDeclarationIfTypeOnly(node, immediate, resolved, /*overwriteEmpty*/ false);
return resolved;
}
@@ -2585,10 +2591,13 @@ namespace ts {
return result;
}
function getExportOfModule(symbol: Symbol, specifier: ImportOrExportSpecifier, dontResolveAlias: boolean): Symbol | undefined {
function getExportOfModule(symbol: Symbol, specifier: ImportOrExportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined {
if (symbol.flags & SymbolFlags.Module) {
const name = (specifier.propertyName ?? specifier.name).escapedText;
const exportSymbol = getExportsOfSymbol(symbol).get(name);
const name = specifier.propertyName ?? specifier.name;
if (!isIdentifier(name)) {
return undefined;
}
const exportSymbol = getExportsOfSymbol(symbol).get(name.escapedText);
const resolved = resolveSymbol(exportSymbol, dontResolveAlias);
markSymbolOfAliasDeclarationIfTypeOnly(specifier, exportSymbol, resolved, /*overwriteEmpty*/ false);
return resolved;
@@ -2604,11 +2613,15 @@ namespace ts {
}
}
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier, dontResolveAlias = false): Symbol | undefined {
const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!)!;
function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, specifier: ImportOrExportSpecifier | BindingElement, dontResolveAlias = false): Symbol | undefined {
const moduleSpecifier = getExternalModuleRequireArgument(node) || (node as ImportDeclaration | ExportDeclaration).moduleSpecifier!;
const moduleSymbol = resolveExternalModuleName(node, moduleSpecifier)!; // TODO: GH#18217
const name = specifier.propertyName || specifier.name;
if (!isIdentifier(name)) {
return undefined;
}
const suppressInteropError = name.escapedText === InternalSymbolName.Default && !!(compilerOptions.allowSyntheticDefaultImports || compilerOptions.esModuleInterop);
const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier!, dontResolveAlias, suppressInteropError);
const targetSymbol = resolveESModuleSymbol(moduleSymbol, moduleSpecifier, dontResolveAlias, suppressInteropError);
if (targetSymbol) {
if (name.escapedText) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
@@ -2669,7 +2682,7 @@ namespace ts {
}
}
function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void {
function reportNonExportedMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleSymbol: Symbol, moduleName: string): void {
const localSymbol = moduleSymbol.valueDeclaration.locals?.get(name.escapedText);
const exports = moduleSymbol.exports;
if (localSymbol) {
@@ -2693,7 +2706,7 @@ namespace ts {
}
}
function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration, name: Identifier, declarationName: string, moduleName: string) {
function reportInvalidImportEqualsExportMember(node: ImportDeclaration | ExportDeclaration | VariableDeclaration, name: Identifier, declarationName: string, moduleName: string) {
if (moduleKind >= ModuleKind.ES2015) {
const message = compilerOptions.esModuleInterop ? Diagnostics._0_can_only_be_imported_by_using_a_default_import :
Diagnostics._0_can_only_be_imported_by_turning_on_the_esModuleInterop_flag_and_using_a_default_import;
@@ -2713,8 +2726,8 @@ namespace ts {
}
}
function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol | undefined {
const resolved = getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias);
function getTargetOfImportSpecifier(node: ImportSpecifier | BindingElement, dontResolveAlias: boolean): Symbol | undefined {
const resolved = getExternalModuleMember(isBindingElement(node) ? getRootDeclaration(node) as VariableDeclaration : node.parent.parent.parent, node, dontResolveAlias);
markSymbolOfAliasDeclarationIfTypeOnly(node, /*immediateTarget*/ undefined, resolved, /*overwriteEmpty*/ false);
return resolved;
}
@@ -2771,15 +2784,17 @@ namespace ts {
function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve = false): Symbol | undefined {
switch (node.kind) {
case SyntaxKind.ImportEqualsDeclaration:
return getTargetOfImportEqualsDeclaration(<ImportEqualsDeclaration>node, dontRecursivelyResolve);
case SyntaxKind.VariableDeclaration:
return getTargetOfImportEqualsDeclaration(node as ImportEqualsDeclaration | VariableDeclaration, dontRecursivelyResolve);
case SyntaxKind.ImportClause:
return getTargetOfImportClause(<ImportClause>node, dontRecursivelyResolve);
return getTargetOfImportClause(node as ImportClause, dontRecursivelyResolve);
case SyntaxKind.NamespaceImport:
return getTargetOfNamespaceImport(<NamespaceImport>node, dontRecursivelyResolve);
case SyntaxKind.NamespaceExport:
return getTargetOfNamespaceExport(<NamespaceExport>node, dontRecursivelyResolve);
case SyntaxKind.ImportSpecifier:
return getTargetOfImportSpecifier(<ImportSpecifier>node, dontRecursivelyResolve);
case SyntaxKind.BindingElement:
return getTargetOfImportSpecifier(node as ImportSpecifier | BindingElement, dontRecursivelyResolve);
case SyntaxKind.ExportSpecifier:
return getTargetOfExportSpecifier(<ExportSpecifier>node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, dontRecursivelyResolve);
case SyntaxKind.ExportAssignment:
@@ -6217,43 +6232,60 @@ namespace ts {
if (textRange && isVariableDeclarationList(textRange.parent) && textRange.parent.declarations.length === 1) {
textRange = textRange.parent.parent;
}
const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled))
], flags)), textRange);
addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags);
if (name !== localName && !isPrivate) {
// We rename the variable declaration we generate for Property symbols since they may have a name which
// conflicts with a local declaration. For example, given input:
// ```
// function g() {}
// module.exports.g = g
// ```
// In such a situation, we have a local variable named `g`, and a separate exported variable named `g`.
// Naively, we would emit
// ```
// function g() {}
// export const g: typeof g;
// ```
// That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but
// the export declaration shadows it.
// To work around that, we instead write
// ```
// function g() {}
// const g_1: typeof g;
// export { g_1 as g };
// ```
// To create an export named `g` that does _not_ shadow the local `g`
const propertyAccessRequire = find(symbol.declarations, isPropertyAccessExpression);
if (propertyAccessRequire && isBinaryExpression(propertyAccessRequire.parent) && isIdentifier(propertyAccessRequire.parent.right)
&& type.symbol && isSourceFile(type.symbol.valueDeclaration)) {
const alias = localName === propertyAccessRequire.parent.right.escapedText ? undefined : propertyAccessRequire.parent.right;
addResult(
factory.createExportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(name, localName)])
factory.createNamedExports([factory.createExportSpecifier(alias, localName)])
),
ModifierFlags.None
);
needsExportDeclaration = false;
needsPostExportDefault = false;
context.tracker.trackSymbol!(type.symbol, context.enclosingDeclaration, SymbolFlags.Value);
}
else {
const statement = setTextRange(factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, type, symbol, enclosingDeclaration, includePrivateSymbol, bundled))
], flags)), textRange);
addResult(statement, name !== localName ? modifierFlags & ~ModifierFlags.Export : modifierFlags);
if (name !== localName && !isPrivate) {
// We rename the variable declaration we generate for Property symbols since they may have a name which
// conflicts with a local declaration. For example, given input:
// ```
// function g() {}
// module.exports.g = g
// ```
// In such a situation, we have a local variable named `g`, and a separate exported variable named `g`.
// Naively, we would emit
// ```
// function g() {}
// export const g: typeof g;
// ```
// That's obviously incorrect - the `g` in the type annotation needs to refer to the local `g`, but
// the export declaration shadows it.
// To work around that, we instead write
// ```
// function g() {}
// const g_1: typeof g;
// export { g_1 as g };
// ```
// To create an export named `g` that does _not_ shadow the local `g`
addResult(
factory.createExportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*isTypeOnly*/ false,
factory.createNamedExports([factory.createExportSpecifier(name, localName)])
),
ModifierFlags.None
);
needsExportDeclaration = false;
needsPostExportDefault = false;
}
}
}
}
@@ -6630,17 +6662,45 @@ namespace ts {
const targetName = getInternalSymbolName(target, verbatimTargetName);
includePrivateSymbol(target); // the target may be within the same scope - attempt to serialize it first
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
// commonjs require: const x = require('y')
if (isPropertyAccessExpression((node as VariableDeclaration).initializer!)) {
// const x = require('y').z
const initializer = (node as VariableDeclaration).initializer! as PropertyAccessExpression; // require('y').z
const uniqueName = factory.createUniqueName((getExternalModuleRequireArgument(node) as StringLiteral).text); // _y
const specifier = getSpecifierForModuleSymbol(target.parent || target, context); // 'y'
// import _y = require('y');
addResult(factory.createImportEqualsDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
uniqueName,
factory.createExternalModuleReference(factory.createStringLiteral(specifier))
), ModifierFlags.None);
// import x = _y.z
addResult(factory.createImportEqualsDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
factory.createIdentifier(localName),
factory.createQualifiedName(uniqueName, initializer.name as Identifier),
), modifierFlags);
break;
}
// else fall through and treat commonjs require just like import=
case SyntaxKind.ImportEqualsDeclaration:
if (target.escapedName === InternalSymbolName.ExportEquals) {
serializeMaybeAliasAssignment(symbol);
break;
}
// Could be a local `import localName = ns.member` or
// an external `import localName = require("whatever")`
const isLocalImport = !(target.flags & SymbolFlags.ValueModule);
const isLocalImport = !(target.flags & SymbolFlags.ValueModule) && !isVariableDeclaration(node);
addResult(factory.createImportEqualsDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
factory.createIdentifier(localName),
isLocalImport
? symbolToName(target, context, SymbolFlags.All, /*expectsIdentifier*/ false)
: factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(symbol, context)))
: factory.createExternalModuleReference(factory.createStringLiteral(getSpecifierForModuleSymbol(target, context)))
), isLocalImport ? modifierFlags : ModifierFlags.None);
break;
case SyntaxKind.NamespaceExportDeclaration:
@@ -6816,7 +6876,12 @@ namespace ts {
const statement = factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(varName, /*exclamationToken*/ undefined, serializeTypeForDeclaration(context, typeToSerialize, symbol, enclosingDeclaration, includePrivateSymbol, bundled))
], NodeFlags.Const));
addResult(statement, name === varName ? ModifierFlags.Export : ModifierFlags.None);
// Inlined JSON types exported with [module.]exports= will already emit an export=, so should use `declare`.
// Otherwise, the type itself should be exported.
addResult(statement,
target && target.flags & SymbolFlags.Property && target.escapedName === InternalSymbolName.ExportEquals ? ModifierFlags.Ambient
: name === varName ? ModifierFlags.Export
: ModifierFlags.None);
}
if (isExportAssignment) {
results.push(factory.createExportAssignment(
@@ -12062,8 +12127,7 @@ namespace ts {
/**
* A JSdoc TypeReference may be to a value, but resolve it as a type anyway.
* Note: If the value is imported from commonjs, it should really be an alias,
* but this function's special-case code fakes alias resolution as well.
* Example: import('./b').ConstructorFunction
*/
function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined {
const links = getNodeLinks(node);
@@ -12071,19 +12135,9 @@ namespace ts {
const valueType = getTypeOfSymbol(symbol);
let typeType = valueType;
if (symbol.valueDeclaration) {
const decl = getRootDeclaration(symbol.valueDeclaration);
let isRequireAlias = false;
if (isVariableDeclaration(decl) && decl.initializer) {
let expr = decl.initializer;
// skip past entity names, eg `require("x").a.b.c`
while (isPropertyAccessExpression(expr)) {
expr = expr.expression;
}
isRequireAlias = isCallExpression(expr) && isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !!valueType.symbol;
}
const isImportTypeWithQualifier = node.kind === SyntaxKind.ImportType && (node as ImportTypeNode).qualifier;
// valueType might not have a symbol, eg, {import('./b').STRING_LITERAL}
if (valueType.symbol && (isRequireAlias || isImportTypeWithQualifier)) {
if (valueType.symbol && isImportTypeWithQualifier) {
typeType = getTypeReferenceType(node, valueType.symbol);
}
}
@@ -32822,7 +32876,7 @@ namespace ts {
forEach(node.name.elements, checkSourceElement);
}
// For a parameter declaration with an initializer, error and exit if the containing function doesn't have a body
if (node.initializer && getRootDeclaration(node).kind === SyntaxKind.Parameter && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) {
if (node.initializer && isParameterDeclaration(node) && nodeIsMissing((getContainingFunction(node) as FunctionLikeDeclaration).body)) {
error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation);
return;
}
@@ -32854,7 +32908,13 @@ namespace ts {
}
return;
}
// For a commonjs `const x = require`, validate the alias and exit
const symbol = getSymbolOfNode(node);
if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node, /*requireStringLiteralLikeArgument*/ true)) {
checkAliasSymbol(node);
return;
}
const type = convertAutoToAny(getTypeOfSymbol(symbol));
if (node === symbol.valueDeclaration) {
// Node is the primary declaration of the symbol, just validate the initializer
@@ -35300,7 +35360,7 @@ namespace ts {
return true;
}
function checkAliasSymbol(node: ImportEqualsDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) {
function checkAliasSymbol(node: ImportEqualsDeclaration | VariableDeclaration | ImportClause | NamespaceImport | ImportSpecifier | ExportSpecifier | NamespaceExport) {
let symbol = getSymbolOfNode(node);
const target = resolveAlias(symbol);
@@ -36584,10 +36644,15 @@ namespace ts {
}
/** Returns the target of an export specifier without following aliases */
function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier): Symbol | undefined {
return node.parent.parent.moduleSpecifier ?
getExternalModuleMember(node.parent.parent, node) :
resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
function getExportSpecifierLocalTargetSymbol(node: ExportSpecifier | Identifier): Symbol | undefined {
if (isExportSpecifier(node)) {
return node.parent.parent.moduleSpecifier ?
getExternalModuleMember(node.parent.parent, node) :
resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
}
else {
return resolveEntityName(node, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias);
}
}
function getTypeOfNode(node: Node): Type {

View File

@@ -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.

View File

@@ -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;
}
@@ -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 {
@@ -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) {

View File

@@ -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 {
@@ -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;
}
@@ -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;
}

View File

@@ -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;
@@ -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;
}
@@ -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;