Skip to content

Commit 0fefaf2

Browse files
author
Andy
authored
moveToNewFile: Infer quote preference (microsoft#24652)
1 parent 83c58a4 commit 0fefaf2

7 files changed

Lines changed: 85 additions & 49 deletions

File tree

src/services/codefixes/convertToEs6Module.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ namespace ts.codefix {
55
getCodeActions(context) {
66
const { sourceFile, program, preferences } = context;
77
const changes = textChanges.ChangeTracker.with(context, changes => {
8-
const moduleExportsChangedToDefault = convertFileToEs6Module(sourceFile, program.getTypeChecker(), changes, program.getCompilerOptions().target!, preferences);
8+
const moduleExportsChangedToDefault = convertFileToEs6Module(sourceFile, program.getTypeChecker(), changes, program.getCompilerOptions().target!, getQuotePreference(sourceFile, preferences));
99
if (moduleExportsChangedToDefault) {
1010
for (const importingFile of program.getSourceFiles()) {
11-
fixImportOfModuleExports(importingFile, sourceFile, changes, preferences);
11+
fixImportOfModuleExports(importingFile, sourceFile, changes, getQuotePreference(importingFile, preferences));
1212
}
1313
}
1414
});
@@ -17,7 +17,7 @@ namespace ts.codefix {
1717
},
1818
});
1919

20-
function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker, preferences: UserPreferences) {
20+
function fixImportOfModuleExports(importingFile: SourceFile, exportingFile: SourceFile, changes: textChanges.ChangeTracker, quotePreference: QuotePreference) {
2121
for (const moduleSpecifier of importingFile.imports) {
2222
const imported = getResolvedModule(importingFile, moduleSpecifier.text);
2323
if (!imported || imported.resolvedFileName !== exportingFile.fileName) {
@@ -27,7 +27,7 @@ namespace ts.codefix {
2727
const importNode = importFromModuleSpecifier(moduleSpecifier);
2828
switch (importNode.kind) {
2929
case SyntaxKind.ImportEqualsDeclaration:
30-
changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, moduleSpecifier, preferences));
30+
changes.replaceNode(importingFile, importNode, makeImport(importNode.name, /*namedImports*/ undefined, moduleSpecifier, quotePreference));
3131
break;
3232
case SyntaxKind.CallExpression:
3333
if (isRequireCall(importNode, /*checkArgumentIsStringLiteralLike*/ false)) {
@@ -39,13 +39,13 @@ namespace ts.codefix {
3939
}
4040

4141
/** @returns Whether we converted a `module.exports =` to a default export. */
42-
function convertFileToEs6Module(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, target: ScriptTarget, preferences: UserPreferences): ModuleExportsChanged {
42+
function convertFileToEs6Module(sourceFile: SourceFile, checker: TypeChecker, changes: textChanges.ChangeTracker, target: ScriptTarget, quotePreference: QuotePreference): ModuleExportsChanged {
4343
const identifiers: Identifiers = { original: collectFreeIdentifiers(sourceFile), additional: createMap<true>() };
4444
const exports = collectExportRenames(sourceFile, checker, identifiers);
4545
convertExportsAccesses(sourceFile, exports, changes);
4646
let moduleExportsChangedToDefault = false;
4747
for (const statement of sourceFile.statements) {
48-
const moduleExportsChanged = convertStatement(sourceFile, statement, checker, changes, identifiers, target, exports, preferences);
48+
const moduleExportsChanged = convertStatement(sourceFile, statement, checker, changes, identifiers, target, exports, quotePreference);
4949
moduleExportsChangedToDefault = moduleExportsChangedToDefault || moduleExportsChanged;
5050
}
5151
return moduleExportsChangedToDefault;
@@ -98,18 +98,18 @@ namespace ts.codefix {
9898
/** Whether `module.exports =` was changed to `export default` */
9999
type ModuleExportsChanged = boolean;
100100

101-
function convertStatement(sourceFile: SourceFile, statement: Statement, checker: TypeChecker, changes: textChanges.ChangeTracker, identifiers: Identifiers, target: ScriptTarget, exports: ExportRenames, preferences: UserPreferences): ModuleExportsChanged {
101+
function convertStatement(sourceFile: SourceFile, statement: Statement, checker: TypeChecker, changes: textChanges.ChangeTracker, identifiers: Identifiers, target: ScriptTarget, exports: ExportRenames, quotePreference: QuotePreference): ModuleExportsChanged {
102102
switch (statement.kind) {
103103
case SyntaxKind.VariableStatement:
104-
convertVariableStatement(sourceFile, statement as VariableStatement, changes, checker, identifiers, target, preferences);
104+
convertVariableStatement(sourceFile, statement as VariableStatement, changes, checker, identifiers, target, quotePreference);
105105
return false;
106106
case SyntaxKind.ExpressionStatement: {
107107
const { expression } = statement as ExpressionStatement;
108108
switch (expression.kind) {
109109
case SyntaxKind.CallExpression: {
110110
if (isRequireCall(expression, /*checkArgumentIsStringLiteralLike*/ true)) {
111111
// For side-effecting require() call, just make a side-effecting import.
112-
changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0], preferences));
112+
changes.replaceNode(sourceFile, statement, makeImport(/*name*/ undefined, /*namedImports*/ undefined, expression.arguments[0], quotePreference));
113113
}
114114
return false;
115115
}
@@ -125,7 +125,15 @@ namespace ts.codefix {
125125
}
126126
}
127127

128-
function convertVariableStatement(sourceFile: SourceFile, statement: VariableStatement, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, target: ScriptTarget, preferences: UserPreferences): void {
128+
function convertVariableStatement(
129+
sourceFile: SourceFile,
130+
statement: VariableStatement,
131+
changes: textChanges.ChangeTracker,
132+
checker: TypeChecker,
133+
identifiers: Identifiers,
134+
target: ScriptTarget,
135+
quotePreference: QuotePreference,
136+
): void {
129137
const { declarationList } = statement;
130138
let foundImport = false;
131139
const newNodes = flatMap(declarationList.declarations, decl => {
@@ -138,11 +146,11 @@ namespace ts.codefix {
138146
}
139147
else if (isRequireCall(initializer, /*checkArgumentIsStringLiteralLike*/ true)) {
140148
foundImport = true;
141-
return convertSingleImport(sourceFile, name, initializer.arguments[0], changes, checker, identifiers, target, preferences);
149+
return convertSingleImport(sourceFile, name, initializer.arguments[0], changes, checker, identifiers, target, quotePreference);
142150
}
143151
else if (isPropertyAccessExpression(initializer) && isRequireCall(initializer.expression, /*checkArgumentIsStringLiteralLike*/ true)) {
144152
foundImport = true;
145-
return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0], identifiers, preferences);
153+
return convertPropertyAccessImport(name, initializer.name.text, initializer.expression.arguments[0], identifiers, quotePreference);
146154
}
147155
}
148156
// Move it out to its own variable statement. (This will not be used if `!foundImport`)
@@ -155,20 +163,20 @@ namespace ts.codefix {
155163
}
156164

157165
/** Converts `const name = require("moduleSpecifier").propertyName` */
158-
function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: StringLiteralLike, identifiers: Identifiers, preferences: UserPreferences): ReadonlyArray<Node> {
166+
function convertPropertyAccessImport(name: BindingName, propertyName: string, moduleSpecifier: StringLiteralLike, identifiers: Identifiers, quotePreference: QuotePreference): ReadonlyArray<Node> {
159167
switch (name.kind) {
160168
case SyntaxKind.ObjectBindingPattern:
161169
case SyntaxKind.ArrayBindingPattern: {
162170
// `const [a, b] = require("c").d` --> `import { d } from "c"; const [a, b] = d;`
163171
const tmp = makeUniqueName(propertyName, identifiers);
164172
return [
165-
makeSingleImport(tmp, propertyName, moduleSpecifier, preferences),
173+
makeSingleImport(tmp, propertyName, moduleSpecifier, quotePreference),
166174
makeConst(/*modifiers*/ undefined, name, createIdentifier(tmp)),
167175
];
168176
}
169177
case SyntaxKind.Identifier:
170178
// `const a = require("b").c` --> `import { c as a } from "./b";
171-
return [makeSingleImport(name.text, propertyName, moduleSpecifier, preferences)];
179+
return [makeSingleImport(name.text, propertyName, moduleSpecifier, quotePreference)];
172180
default:
173181
return Debug.assertNever(name);
174182
}
@@ -340,7 +348,7 @@ namespace ts.codefix {
340348
checker: TypeChecker,
341349
identifiers: Identifiers,
342350
target: ScriptTarget,
343-
preferences: UserPreferences,
351+
quotePreference: QuotePreference,
344352
): ReadonlyArray<Node> {
345353
switch (name.kind) {
346354
case SyntaxKind.ObjectBindingPattern: {
@@ -349,7 +357,7 @@ namespace ts.codefix {
349357
? undefined
350358
: makeImportSpecifier(e.propertyName && (e.propertyName as Identifier).text, e.name.text)); // tslint:disable-line no-unnecessary-type-assertion (TODO: GH#18217)
351359
if (importSpecifiers) {
352-
return [makeImport(/*name*/ undefined, importSpecifiers, moduleSpecifier, preferences)];
360+
return [makeImport(/*name*/ undefined, importSpecifiers, moduleSpecifier, quotePreference)];
353361
}
354362
}
355363
// falls through -- object destructuring has an interesting pattern and must be a variable declaration
@@ -360,12 +368,12 @@ namespace ts.codefix {
360368
*/
361369
const tmp = makeUniqueName(moduleSpecifierToValidIdentifier(moduleSpecifier.text, target), identifiers);
362370
return [
363-
makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier, preferences),
371+
makeImport(createIdentifier(tmp), /*namedImports*/ undefined, moduleSpecifier, quotePreference),
364372
makeConst(/*modifiers*/ undefined, getSynthesizedDeepClone(name), createIdentifier(tmp)),
365373
];
366374
}
367375
case SyntaxKind.Identifier:
368-
return convertSingleIdentifierImport(file, name, moduleSpecifier, changes, checker, identifiers, preferences);
376+
return convertSingleIdentifierImport(file, name, moduleSpecifier, changes, checker, identifiers, quotePreference);
369377
default:
370378
return Debug.assertNever(name);
371379
}
@@ -375,7 +383,7 @@ namespace ts.codefix {
375383
* Convert `import x = require("x").`
376384
* Also converts uses like `x.y()` to `y()` and uses a named import.
377385
*/
378-
function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, preferences: UserPreferences): ReadonlyArray<Node> {
386+
function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, quotePreference: QuotePreference): ReadonlyArray<Node> {
379387
const nameSymbol = checker.getSymbolAtLocation(name);
380388
// Maps from module property name to name actually used. (The same if there isn't shadowing.)
381389
const namedBindingsNames = createMap<string>();
@@ -410,7 +418,7 @@ namespace ts.codefix {
410418
// If it was unused, ensure that we at least import *something*.
411419
needDefaultImport = true;
412420
}
413-
return [makeImport(needDefaultImport ? getSynthesizedDeepClone(name) : undefined, namedBindings, moduleSpecifier, preferences)];
421+
return [makeImport(needDefaultImport ? getSynthesizedDeepClone(name) : undefined, namedBindings, moduleSpecifier, quotePreference)];
414422
}
415423

416424
// Identifiers helpers
@@ -488,10 +496,10 @@ namespace ts.codefix {
488496
getSynthesizedDeepClones(cls.members));
489497
}
490498

491-
function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: StringLiteralLike, preferences: UserPreferences): ImportDeclaration {
499+
function makeSingleImport(localName: string, propertyName: string, moduleSpecifier: StringLiteralLike, quotePreference: QuotePreference): ImportDeclaration {
492500
return propertyName === "default"
493-
? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier, preferences)
494-
: makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier, preferences);
501+
? makeImport(createIdentifier(localName), /*namedImports*/ undefined, moduleSpecifier, quotePreference)
502+
: makeImport(/*name*/ undefined, [makeImportSpecifier(propertyName, localName)], moduleSpecifier, quotePreference);
495503
}
496504

497505
function makeImportSpecifier(propertyName: string | undefined, name: string): ImportSpecifier {

src/services/codefixes/fixInvalidImportSyntax.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace ts.codefix {
2828
const variations: CodeFixAction[] = [];
2929

3030
// import Bluebird from "bluebird";
31-
variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, context.preferences)));
31+
variations.push(createAction(context, sourceFile, node, makeImport(namespace.name, /*namedImports*/ undefined, node.moduleSpecifier, getQuotePreference(sourceFile, context.preferences))));
3232

3333
if (getEmitModuleKind(opts) === ModuleKind.CommonJS) {
3434
// import Bluebird = require("bluebird");

src/services/codefixes/importFixes.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ namespace ts.codefix {
197197
const lastImportDeclaration = findLast(sourceFile.statements, isAnyImportSyntax);
198198

199199
const moduleSpecifierWithoutQuotes = stripQuotes(moduleSpecifier);
200-
const quotedModuleSpecifier = createLiteral(moduleSpecifierWithoutQuotes, shouldUseSingleQuote(sourceFile, preferences));
200+
const quotedModuleSpecifier = makeStringLiteral(moduleSpecifierWithoutQuotes, getQuotePreference(sourceFile, preferences));
201201
const importDecl = importKind !== ImportKind.Equals
202202
? createImportDeclaration(
203203
/*decorators*/ undefined,
@@ -225,16 +225,6 @@ namespace ts.codefix {
225225
return createCodeAction(Diagnostics.Import_0_from_module_1, [symbolName, moduleSpecifierWithoutQuotes], changes);
226226
}
227227

228-
function shouldUseSingleQuote(sourceFile: SourceFile, preferences: UserPreferences): boolean {
229-
if (preferences.quotePreference) {
230-
return preferences.quotePreference === "single";
231-
}
232-
else {
233-
const firstModuleSpecifier = firstOrUndefined(sourceFile.imports);
234-
return !!firstModuleSpecifier && !isStringDoubleQuoted(firstModuleSpecifier, sourceFile);
235-
}
236-
}
237-
238228
function createImportClauseOfKind(kind: ImportKind.Default | ImportKind.Named | ImportKind.Namespace, symbolName: string) {
239229
const id = createIdentifier(symbolName);
240230
switch (kind) {

src/services/codefixes/useDefaultImport.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ namespace ts.codefix {
3737
}
3838

3939
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, info: Info, preferences: UserPreferences): void {
40-
changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, preferences));
40+
changes.replaceNode(sourceFile, info.importNode, makeImport(info.name, /*namedImports*/ undefined, info.moduleSpecifier, getQuotePreference(sourceFile, preferences)));
4141
}
4242
}

0 commit comments

Comments
 (0)