Skip to content

Commit ad5ca67

Browse files
Avoid crash for import code fixes with dotted require (#47433)
* Add failing test. * Update failing test. * Finalized failing test case. * Separate our usages of utilities that expect variables initialized to require(...) and require(...).foo. * Renamed types and utilities, removed accidental indentation. * Renamed both utilitiy functions uniformly.
1 parent 049d4e0 commit ad5ca67

File tree

9 files changed

+49
-15
lines changed

9 files changed

+49
-15
lines changed

src/compiler/binder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3287,7 +3287,7 @@ namespace ts {
32873287
}
32883288

32893289
if (!isBindingPattern(node.name)) {
3290-
if (isInJSFile(node) && isRequireVariableDeclaration(node) && !getJSDocTypeTag(node)) {
3290+
if (isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(node) && !getJSDocTypeTag(node)) {
32913291
declareSymbolAndAddToSymbolTable(node as Declaration, SymbolFlags.Alias, SymbolFlags.AliasExcludes);
32923292
}
32933293
else if (isBlockOrCatchScoped(node)) {

src/compiler/checker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,7 +2596,7 @@ namespace ts {
25962596
&& isAliasableOrJsExpression(node.parent.right)
25972597
|| node.kind === SyntaxKind.ShorthandPropertyAssignment
25982598
|| node.kind === SyntaxKind.PropertyAssignment && isAliasableOrJsExpression((node as PropertyAssignment).initializer)
2599-
|| isRequireVariableDeclaration(node);
2599+
|| isVariableDeclarationInitializedToBareOrAccessedRequire(node);
26002600
}
26012601

26022602
function isAliasableOrJsExpression(e: Expression) {
@@ -37010,7 +37010,7 @@ namespace ts {
3701037010
}
3701137011
// For a commonjs `const x = require`, validate the alias and exit
3701237012
const symbol = getSymbolOfNode(node);
37013-
if (symbol.flags & SymbolFlags.Alias && isRequireVariableDeclaration(node)) {
37013+
if (symbol.flags & SymbolFlags.Alias && isVariableDeclarationInitializedToBareOrAccessedRequire(node)) {
3701437014
checkAliasSymbol(node);
3701537015
return;
3701637016
}

src/compiler/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4639,7 +4639,7 @@ namespace ts {
46394639
export type AnyImportSyntax = ImportDeclaration | ImportEqualsDeclaration;
46404640

46414641
/* @internal */
4642-
export type AnyImportOrRequire = AnyImportSyntax | RequireVariableDeclaration;
4642+
export type AnyImportOrRequire = AnyImportSyntax | VariableDeclarationInitializedTo<RequireOrImportCall>;
46434643

46444644
/* @internal */
46454645
export type AnyImportOrRequireStatement = AnyImportSyntax | RequireVariableStatement;
@@ -4664,8 +4664,8 @@ namespace ts {
46644664
export type RequireOrImportCall = CallExpression & { expression: Identifier, arguments: [StringLiteralLike] };
46654665

46664666
/* @internal */
4667-
export interface RequireVariableDeclaration extends VariableDeclaration {
4668-
readonly initializer: RequireOrImportCall;
4667+
export interface VariableDeclarationInitializedTo<T extends Expression> extends VariableDeclaration {
4668+
readonly initializer: T;
46694669
}
46704670

46714671
/* @internal */
@@ -4675,7 +4675,7 @@ namespace ts {
46754675

46764676
/* @internal */
46774677
export interface RequireVariableDeclarationList extends VariableDeclarationList {
4678-
readonly declarations: NodeArray<RequireVariableDeclaration>;
4678+
readonly declarations: NodeArray<VariableDeclarationInitializedTo<RequireOrImportCall>>;
46794679
}
46804680

46814681
/* @internal */

src/compiler/utilities.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2074,7 +2074,7 @@ namespace ts {
20742074
}
20752075

20762076
export function getExternalModuleRequireArgument(node: Node) {
2077-
return isRequireVariableDeclaration(node) && (getLeftmostAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral;
2077+
return isVariableDeclarationInitializedToBareOrAccessedRequire(node) && (getLeftmostAccessExpression(node.initializer) as CallExpression).arguments[0] as StringLiteral;
20782078
}
20792079

20802080
export function isInternalModuleImportEqualsDeclaration(node: Node): node is ImportEqualsDeclaration {
@@ -2141,17 +2141,30 @@ namespace ts {
21412141
* Returns true if the node is a VariableDeclaration initialized to a require call (see `isRequireCall`).
21422142
* This function does not test if the node is in a JavaScript file or not.
21432143
*/
2144-
export function isRequireVariableDeclaration(node: Node): node is RequireVariableDeclaration {
2144+
export function isVariableDeclarationInitializedToRequire(node: Node): node is VariableDeclarationInitializedTo<RequireOrImportCall> {
2145+
return isVariableDeclarationInitializedWithRequireHelper(node, /*allowAccessedRequire*/ false);
2146+
}
2147+
2148+
/**
2149+
* Like {@link isVariableDeclarationInitializedToRequire} but allows things like `require("...").foo.bar` or `require("...")["baz"]`.
2150+
*/
2151+
export function isVariableDeclarationInitializedToBareOrAccessedRequire(node: Node): node is VariableDeclarationInitializedTo<RequireOrImportCall | AccessExpression> {
2152+
return isVariableDeclarationInitializedWithRequireHelper(node, /*allowAccessedRequire*/ true);
2153+
}
2154+
2155+
function isVariableDeclarationInitializedWithRequireHelper(node: Node, allowAccessedRequire: boolean) {
21452156
if (node.kind === SyntaxKind.BindingElement) {
21462157
node = node.parent.parent;
21472158
}
2148-
return isVariableDeclaration(node) && !!node.initializer && isRequireCall(getLeftmostAccessExpression(node.initializer), /*requireStringLiteralLikeArgument*/ true);
2159+
return isVariableDeclaration(node) &&
2160+
!!node.initializer &&
2161+
isRequireCall(allowAccessedRequire ? getLeftmostAccessExpression(node.initializer) : node.initializer, /*requireStringLiteralLikeArgument*/ true);
21492162
}
21502163

21512164
export function isRequireVariableStatement(node: Node): node is RequireVariableStatement {
21522165
return isVariableStatement(node)
21532166
&& node.declarationList.declarations.length > 0
2154-
&& every(node.declarationList.declarations, decl => isRequireVariableDeclaration(decl));
2167+
&& every(node.declarationList.declarations, decl => isVariableDeclarationInitializedToRequire(decl));
21552168
}
21562169

21572170
export function isSingleOrDoubleQuote(charCode: number) {

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ namespace ts.codefix {
537537
const importKind = getImportKind(importingFile, exportKind, compilerOptions);
538538
return mapDefined(importingFile.imports, (moduleSpecifier): FixAddToExistingImportInfo | undefined => {
539539
const i = importFromModuleSpecifier(moduleSpecifier);
540-
if (isRequireVariableDeclaration(i.parent)) {
540+
if (isVariableDeclarationInitializedToRequire(i.parent)) {
541541
return checker.resolveExternalModuleName(moduleSpecifier) === moduleSymbol ? { declaration: i.parent, importKind, symbol, targetFlags } : undefined;
542542
}
543543
if (i.kind === SyntaxKind.ImportDeclaration || i.kind === SyntaxKind.ImportEqualsDeclaration) {

src/services/findAllReferences.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,7 @@ namespace ts.FindAllReferences {
15311531
// Use the parent symbol if the location is commonjs require syntax on javascript files only.
15321532
if (isInJSFile(referenceLocation)
15331533
&& referenceLocation.parent.kind === SyntaxKind.BindingElement
1534-
&& isRequireVariableDeclaration(referenceLocation.parent)) {
1534+
&& isVariableDeclarationInitializedToBareOrAccessedRequire(referenceLocation.parent)) {
15351535
referenceSymbol = referenceLocation.parent.symbol;
15361536
// The parent will not have a symbol if it's an ObjectBindingPattern (when destructuring is used). In
15371537
// this case, just skip it, since the bound identifiers are not an alias of the import.

src/services/goToDefinition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ namespace ts.GoToDefinition {
290290
return declaration.parent.kind === SyntaxKind.NamedImports;
291291
case SyntaxKind.BindingElement:
292292
case SyntaxKind.VariableDeclaration:
293-
return isInJSFile(declaration) && isRequireVariableDeclaration(declaration);
293+
return isInJSFile(declaration) && isVariableDeclarationInitializedToBareOrAccessedRequire(declaration);
294294
default:
295295
return false;
296296
}

src/services/importTracker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ namespace ts.FindAllReferences {
621621
Debug.assert((parent as ImportClause | NamespaceImport).name === node);
622622
return true;
623623
case SyntaxKind.BindingElement:
624-
return isInJSFile(node) && isRequireVariableDeclaration(parent);
624+
return isInJSFile(node) && isVariableDeclarationInitializedToBareOrAccessedRequire(parent);
625625
default:
626626
return false;
627627
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path="./fourslash.ts" />
2+
3+
// @module: commonjs
4+
// @checkJs: true
5+
6+
// @Filename: ./library.js
7+
//// module.exports.aaa = function() {}
8+
//// module.exports.bbb = function() {}
9+
10+
// @Filename: ./foo.js
11+
//// var aaa = require("./library.js").aaa;
12+
//// aaa();
13+
//// /*$*/bbb
14+
15+
goTo.marker("$")
16+
verify.codeFixAvailable([
17+
{ description: "Import 'bbb' from module \"./library.js\"" },
18+
{ description: "Ignore this error message" },
19+
{ description: "Disable checking for this file" },
20+
{ description: "Convert to ES module" },
21+
]);

0 commit comments

Comments
 (0)