Skip to content

Commit dc9b98b

Browse files
author
Andy Hanson
committed
Add code fix to convert 'require' in a '.ts' file to an 'import'
1 parent aa10243 commit dc9b98b

11 files changed

+99
-2
lines changed

src/compiler/diagnosticMessages.json

+12
Original file line numberDiff line numberDiff line change
@@ -3898,6 +3898,10 @@
38983898
"category": "Suggestion",
38993899
"code": 80004
39003900
},
3901+
"'require' call may be converted to an import.": {
3902+
"category": "Suggestion",
3903+
"code": 80005
3904+
},
39013905

39023906
"Add missing 'super()' call": {
39033907
"category": "Message",
@@ -4174,5 +4178,13 @@
41744178
"Generate 'get' and 'set' accessors": {
41754179
"category": "Message",
41764180
"code": 95046
4181+
},
4182+
"Convert 'require' to 'import'": {
4183+
"category": "Message",
4184+
"code": 95047
4185+
},
4186+
"Convert all 'require' to 'import'": {
4187+
"category": "Message",
4188+
"code": 95048
41774189
}
41784190
}

src/harness/fourslash.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -324,8 +324,10 @@ namespace FourSlash {
324324
this.languageServiceAdapterHost.addScript(fileName, file, /*isRootFile*/ true);
325325
}
326326
});
327-
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName,
328-
Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false);
327+
if (!compilationOptions.noLib) {
328+
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName,
329+
Harness.Compiler.getDefaultLibrarySourceFile().text, /*isRootFile*/ false);
330+
}
329331
}
330332

331333
for (const file of testData.files) {

src/harness/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"../services/codefixes/inferFromUsage.ts",
113113
"../services/codefixes/fixInvalidImportSyntax.ts",
114114
"../services/codefixes/fixStrictClassInitialization.ts",
115+
"../services/codefixes/requireInTs.ts",
115116
"../services/codefixes/useDefaultImport.ts",
116117
"../services/refactors/extractSymbol.ts",
117118
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/server/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
"../services/codefixes/inferFromUsage.ts",
109109
"../services/codefixes/fixInvalidImportSyntax.ts",
110110
"../services/codefixes/fixStrictClassInitialization.ts",
111+
"../services/codefixes/requireInTs.ts",
111112
"../services/codefixes/useDefaultImport.ts",
112113
"../services/refactors/extractSymbol.ts",
113114
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/server/tsconfig.library.json

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
"../services/codefixes/inferFromUsage.ts",
115115
"../services/codefixes/fixInvalidImportSyntax.ts",
116116
"../services/codefixes/fixStrictClassInitialization.ts",
117+
"../services/codefixes/requireInTs.ts",
117118
"../services/codefixes/useDefaultImport.ts",
118119
"../services/refactors/extractSymbol.ts",
119120
"../services/refactors/generateGetAccessorAndSetAccessor.ts",

src/services/codefixes/requireInTs.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "requireInTs";
4+
const errorCodes = [Diagnostics.require_call_may_be_converted_to_an_import.code];
5+
registerCodeFix({
6+
errorCodes,
7+
getCodeActions(context) {
8+
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, context.sourceFile, context.span.start, context.program));
9+
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_require_to_import, fixId, Diagnostics.Convert_all_require_to_import)];
10+
},
11+
fixIds: [fixId],
12+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => doChange(changes, diag.file, diag.start, context.program)),
13+
});
14+
15+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program) {
16+
const { statement, name, required } = getInfo(sourceFile, pos);
17+
changes.replaceNode(sourceFile, statement, getAllowSyntheticDefaultImports(program.getCompilerOptions())
18+
? createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createImportClause(name, /*namedBindings*/ undefined), required)
19+
: createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, name, createExternalModuleReference(required)));
20+
}
21+
22+
interface Info { readonly statement: VariableStatement; readonly name: Identifier; readonly required: StringLiteralLike; }
23+
function getInfo(sourceFile: SourceFile, pos: number): Info {
24+
const { parent } = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false);
25+
if (!isRequireCall(parent, /*checkArgumentIsStringLiteralLike*/ true)) throw Debug.failBadSyntaxKind(parent);
26+
const decl = cast(parent.parent, isVariableDeclaration);
27+
return { statement: cast(decl.parent.parent, isVariableStatement), name: cast(decl.name, isIdentifier), required: parent.arguments[0] };
28+
}
29+
}

src/services/suggestionDiagnostics.ts

+11
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ namespace ts {
3232
}
3333
check(sourceFile);
3434

35+
if (!isJsFile) {
36+
for (const statement of sourceFile.statements) {
37+
if (isVariableStatement(statement) &&
38+
statement.declarationList.flags & NodeFlags.Const &&
39+
statement.declarationList.declarations.length === 1 &&
40+
isRequireCall(statement.declarationList.declarations[0].initializer, /*checkArgumentIsStringLiteralLike*/ true)) {
41+
diags.push(createDiagnosticForNode(statement.declarationList.declarations[0].initializer, Diagnostics.require_call_may_be_converted_to_an_import));
42+
}
43+
}
44+
}
45+
3546
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
3647
for (const moduleSpecifier of sourceFile.imports) {
3748
const importNode = importFromModuleSpecifier(moduleSpecifier);

src/services/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
"codefixes/inferFromUsage.ts",
106106
"codefixes/fixInvalidImportSyntax.ts",
107107
"codefixes/fixStrictClassInitialization.ts",
108+
"codefixes/requireInTs.ts",
108109
"codefixes/useDefaultImport.ts",
109110
"refactors/extractSymbol.ts",
110111
"refactors/generateGetAccessorAndSetAccessor.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////const a = [|require("a")|];
5+
6+
verify.getSuggestionDiagnostics([{
7+
message: "'require' call may be converted to an import.",
8+
code: 80005,
9+
}]);
10+
11+
verify.codeFix({
12+
description: "Convert 'require' to 'import'",
13+
newFileContent:
14+
`import a = require("a");`,
15+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @Filename: /a.ts
4+
////const a = [|require("a")|];
5+
////const b = [|require("b")|];
6+
7+
verify.codeFixAll({
8+
fixId: "requireInTs",
9+
fixAllDescription: "Convert all 'require' to 'import'",
10+
newFileContent:
11+
`import a = require("a");
12+
import b = require("b");`,
13+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @allowSyntheticDefaultImports: true
4+
5+
// @Filename: /a.ts
6+
////const a = [|require("a")|];
7+
8+
verify.codeFix({
9+
description: "Convert 'require' to 'import'",
10+
newFileContent: `import a from "a";`,
11+
});

0 commit comments

Comments
 (0)