Skip to content

Commit 03f47ac

Browse files
crisbetodylhunn
authored andcommitted
fix(migrations): use consistent quotes in generated imports (#48876)
Adds some logic so that the imports generated by the `ImportManager` use the same styles as the other imports. PR Close #48876
1 parent d3a14d9 commit 03f47ac

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

packages/core/schematics/test/standalone_migration_spec.ts

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,8 @@ describe('standalone migration', () => {
493493

494494
const myCompContent = tree.readContent('comp.ts');
495495

496-
expect(myCompContent).toContain(`import { MyButton } from "./button";`);
497-
expect(myCompContent).toContain(`import { MyTooltip } from "./tooltip";`);
496+
expect(myCompContent).toContain(`import { MyButton } from './button';`);
497+
expect(myCompContent).toContain(`import { MyTooltip } from './tooltip';`);
498498
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
499499
@Component({
500500
selector: 'my-comp',
@@ -586,7 +586,7 @@ describe('standalone migration', () => {
586586
await runMigration('convert-to-standalone');
587587
const myCompContent = tree.readContent('comp.ts');
588588

589-
expect(myCompContent).toContain(`import { MyButton } from "./button";`);
589+
expect(myCompContent).toContain(`import { MyButton } from './button';`);
590590
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
591591
@Component({
592592
selector: 'my-comp',
@@ -636,7 +636,7 @@ describe('standalone migration', () => {
636636

637637
const myCompContent = tree.readContent('comp.ts');
638638

639-
expect(myCompContent).toContain(`import { MyButton } from "./button";`);
639+
expect(myCompContent).toContain(`import { MyButton } from './button';`);
640640
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
641641
@Component({
642642
selector: 'my-comp',
@@ -698,7 +698,7 @@ describe('standalone migration', () => {
698698
const myCompContent = tree.readContent('./should-migrate/comp.ts');
699699

700700
expect(myCompContent)
701-
.toContain(`import { ButtonModule } from "../do-not-migrate/button.module";`);
701+
.toContain(`import { ButtonModule } from '../do-not-migrate/button.module';`);
702702
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
703703
@Component({
704704
selector: 'my-comp',
@@ -752,8 +752,8 @@ describe('standalone migration', () => {
752752

753753
const myCompContent = tree.readContent('comp.ts');
754754

755-
expect(myCompContent).toContain(`import { MyButton } from "./button";`);
756-
expect(myCompContent).toContain(`import { MyTooltip } from "./tooltip";`);
755+
expect(myCompContent).toContain(`import { MyButton } from './button';`);
756+
expect(myCompContent).toContain(`import { MyTooltip } from './tooltip';`);
757757
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
758758
@Component({
759759
selector: 'my-comp',
@@ -810,7 +810,7 @@ describe('standalone migration', () => {
810810

811811
const myCompContent = tree.readContent('comp.ts');
812812

813-
expect(myCompContent).toContain(`import { NgForOf, NgIf } from "@angular/common";`);
813+
expect(myCompContent).toContain(`import { NgForOf, NgIf } from '@angular/common';`);
814814
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
815815
@Component({
816816
selector: 'my-comp',
@@ -866,7 +866,7 @@ describe('standalone migration', () => {
866866

867867
const myCompContent = tree.readContent('comp.ts');
868868

869-
expect(myCompContent).toContain(`import { MyPipe } from "./pipe";`);
869+
expect(myCompContent).toContain(`import { MyPipe } from './pipe';`);
870870
expect(stripWhitespace(myCompContent)).toContain(stripWhitespace(`
871871
@Component({
872872
selector: 'my-comp',
@@ -966,7 +966,7 @@ describe('standalone migration', () => {
966966

967967
const myCompContent = tree.readContent('./should-migrate/comp.ts');
968968
expect(myCompContent)
969-
.toContain(`import { ButtonModule } from "../do-not-migrate/button.module";`);
969+
.toContain(`import { ButtonModule } from '../do-not-migrate/button.module';`);
970970
expect(myCompContent).toContain('imports: [ButtonModule]');
971971
});
972972

@@ -1850,7 +1850,7 @@ describe('standalone migration', () => {
18501850
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
18511851
import {AppModule, AppComponent} from './app/app.module';
18521852
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
1853-
import {bootstrapApplication} from "@angular/platform-browser";
1853+
import {bootstrapApplication} from '@angular/platform-browser';
18541854
18551855
bootstrapApplication(AppComponent).catch(e => console.error(e));
18561856
`));
@@ -1881,7 +1881,7 @@ describe('standalone migration', () => {
18811881
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
18821882
import {AppModule, AppComponent} from './app/app.module';
18831883
import {PlatformRef} from '@angular/core';
1884-
import {bootstrapApplication} from "@angular/platform-browser";
1884+
import {bootstrapApplication} from '@angular/platform-browser';
18851885
18861886
const foo: PlatformRef = null!;
18871887
@@ -1932,9 +1932,9 @@ describe('standalone migration', () => {
19321932
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
19331933
import {AppModule} from './app/app.module';
19341934
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
1935-
import {importProvidersFrom} from "@angular/core";
1936-
import {AppComponent} from "./app/app.component";
1937-
import {CommonModule} from "@angular/common";
1935+
import {importProvidersFrom} from '@angular/core';
1936+
import {AppComponent} from './app/app.component';
1937+
import {CommonModule} from '@angular/common';
19381938
19391939
bootstrapApplication(AppComponent, {
19401940
providers: [importProvidersFrom(CommonModule)]
@@ -1959,8 +1959,8 @@ describe('standalone migration', () => {
19591959

19601960
expect(stripWhitespace(tree.readContent('./app/app.component.ts'))).toBe(stripWhitespace(`
19611961
import {Component} from '@angular/core';
1962-
import {Dir} from "./dir";
1963-
import {NgIf} from "@angular/common";
1962+
import {Dir} from './dir';
1963+
import {NgIf} from '@angular/common';
19641964
19651965
@Component({
19661966
template: '<div *ngIf="show" dir>hello</div>',
@@ -2036,11 +2036,11 @@ describe('standalone migration', () => {
20362036
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
20372037
import { AppModule, exportedToken, exportedExtraProviders, ExportedClass, exportedFactory, AppComponent } from './app/app.module';
20382038
import { platformBrowser, bootstrapApplication } from '@angular/platform-browser';
2039-
import { ExternalInterface } from "@external/interfaces";
2040-
import { externalToken as aliasedExternalToken } from "./app/externals/other-token";
2041-
import { externalToken } from "./app/externals/token";
2042-
import { InternalInterface } from "./app/interfaces/internal-interface";
2043-
import { InjectionToken } from "@angular/core";
2039+
import { ExternalInterface } from '@external/interfaces';
2040+
import { externalToken as aliasedExternalToken } from './app/externals/other-token';
2041+
import { externalToken } from './app/externals/token';
2042+
import { InternalInterface } from './app/interfaces/internal-interface';
2043+
import { InjectionToken } from '@angular/core';
20442044
20452045
const internalToken = new InjectionToken<string>('internalToken');
20462046
const unexportedExtraProviders = [
@@ -2090,7 +2090,7 @@ describe('standalone migration', () => {
20902090
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
20912091
import {AppModule, token, AppComponent} from './app/app.module';
20922092
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2093-
import {InjectionToken} from "@angular/core";
2093+
import {InjectionToken} from '@angular/core';
20942094
20952095
bootstrapApplication(AppComponent, {
20962096
providers: [{ provide: token, useValue: 'hello' }]
@@ -2133,7 +2133,7 @@ describe('standalone migration', () => {
21332133
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
21342134
import {AppModule, AppComponent} from './app/app.module';
21352135
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2136-
import {ROUTES} from "@angular/router";
2136+
import {ROUTES} from '@angular/router';
21372137
21382138
bootstrapApplication(AppComponent, {
21392139
providers: [{
@@ -2187,9 +2187,9 @@ describe('standalone migration', () => {
21872187
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
21882188
import {AppModule, SameFileModule, AppComponent} from './app/app.module';
21892189
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2190-
import {NgModule, importProvidersFrom} from "@angular/core";
2191-
import {InternalModule} from "./modules/internal.module";
2192-
import {CommonModule} from "@angular/common";
2190+
import {NgModule, importProvidersFrom} from '@angular/core';
2191+
import {InternalModule} from './modules/internal.module';
2192+
import {CommonModule} from '@angular/common';
21932193
21942194
bootstrapApplication(AppComponent, {
21952195
providers: [importProvidersFrom(CommonModule, InternalModule, SameFileModule)]
@@ -2230,7 +2230,7 @@ describe('standalone migration', () => {
22302230
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
22312231
import {AppModule, AppComponent} from './app/app.module';
22322232
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2233-
import {provideRouter} from "@angular/router";
2233+
import {provideRouter} from '@angular/router';
22342234
22352235
bootstrapApplication(AppComponent, {
22362236
providers: [provideRouter([
@@ -2270,9 +2270,9 @@ describe('standalone migration', () => {
22702270
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
22712271
import {AppModule, AppComponent} from './app/app.module';
22722272
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2273-
import {importProvidersFrom} from "@angular/core";
2274-
import {APP_ROUTES} from "./app/routes";
2275-
import {RouterModule} from "@angular/router";
2273+
import {importProvidersFrom} from '@angular/core';
2274+
import {APP_ROUTES} from './app/routes';
2275+
import {RouterModule} from '@angular/router';
22762276
22772277
bootstrapApplication(AppComponent, {
22782278
providers: [importProvidersFrom(RouterModule.forRoot(APP_ROUTES, {}))]
@@ -2308,7 +2308,7 @@ describe('standalone migration', () => {
23082308
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
23092309
import {AppModule, AppComponent} from './app/app.module';
23102310
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2311-
import {provideAnimations} from "@angular/platform-browser/animations";
2311+
import {provideAnimations} from '@angular/platform-browser/animations';
23122312
23132313
bootstrapApplication(AppComponent, {
23142314
providers: [provideAnimations()]
@@ -2344,8 +2344,8 @@ describe('standalone migration', () => {
23442344
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
23452345
import {AppModule, AppComponent} from './app/app.module';
23462346
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2347-
import {importProvidersFrom} from "@angular/core";
2348-
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
2347+
import {importProvidersFrom} from '@angular/core';
2348+
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
23492349
23502350
bootstrapApplication(AppComponent, {
23512351
providers: [importProvidersFrom(BrowserAnimationsModule.withConfig({disableAnimations: true}))]
@@ -2381,7 +2381,7 @@ describe('standalone migration', () => {
23812381
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
23822382
import {AppModule, AppComponent} from './app/app.module';
23832383
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2384-
import {provideNoopAnimations} from "@angular/platform-browser/animations";
2384+
import {provideNoopAnimations} from '@angular/platform-browser/animations';
23852385
23862386
bootstrapApplication(AppComponent, {
23872387
providers: [provideNoopAnimations()]
@@ -2417,8 +2417,8 @@ describe('standalone migration', () => {
24172417
expect(stripWhitespace(tree.readContent('main.ts'))).toBe(stripWhitespace(`
24182418
import {AppModule, AppComponent} from './app/app.module';
24192419
import {platformBrowser, bootstrapApplication} from '@angular/platform-browser';
2420-
import {importProvidersFrom} from "@angular/core";
2421-
import {CommonModule} from "@angular/common";
2420+
import {importProvidersFrom} from '@angular/core';
2421+
import {CommonModule} from '@angular/common';
24222422
24232423
bootstrapApplication(AppComponent, {
24242424
providers: [importProvidersFrom(CommonModule)]

packages/core/schematics/utils/import_manager.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export interface ImportManagerUpdateRecorder {
1818
updateExistingImport(namedBindings: ts.NamedImports, newNamedBindings: string): void;
1919
}
2020

21+
/** Possible types of quotes for imports. */
22+
const enum QuoteStyle {
23+
Single,
24+
Double,
25+
}
26+
2127
/**
2228
* Import manager that can be used to add TypeScript imports to given source
2329
* files. The manager ensures that multiple transformations are applied properly
@@ -35,6 +41,8 @@ export class ImportManager {
3541
defaultImports: Map<string, ts.Identifier>,
3642
namedImports: Map<string, ts.ImportSpecifier[]>,
3743
}> = new Map();
44+
/** Map between a file and the implied quote style for imports. */
45+
private quoteStyles: Record<string, QuoteStyle> = {};
3846

3947
/**
4048
* Array of previously resolved symbol imports. Cache can be re-used to return
@@ -205,11 +213,12 @@ export class ImportManager {
205213

206214
this.newImports.forEach(({importStartIndex, defaultImports, namedImports}, sourceFile) => {
207215
const recorder = this.getUpdateRecorder(sourceFile);
216+
const useSingleQuotes = this._getQuoteStyle(sourceFile) === QuoteStyle.Single;
208217

209218
defaultImports.forEach((identifier, moduleName) => {
210219
const newImport = createImportDeclaration(
211220
undefined, ts.factory.createImportClause(false, identifier, undefined),
212-
ts.factory.createStringLiteral(moduleName));
221+
ts.factory.createStringLiteral(moduleName, useSingleQuotes));
213222

214223
recorder.addNewImport(
215224
importStartIndex, this._getNewImportText(importStartIndex, newImport, sourceFile));
@@ -220,7 +229,7 @@ export class ImportManager {
220229
undefined,
221230
ts.factory.createImportClause(
222231
false, undefined, ts.factory.createNamedImports(specifiers)),
223-
ts.factory.createStringLiteral(moduleName));
232+
ts.factory.createStringLiteral(moduleName, useSingleQuotes));
224233

225234
recorder.addNewImport(
226235
importStartIndex, this._getNewImportText(importStartIndex, newImport, sourceFile));
@@ -332,6 +341,30 @@ export class ImportManager {
332341

333342
return {propertyName, name};
334343
}
344+
345+
/** Gets the quote style that is used for a file's imports. */
346+
private _getQuoteStyle(sourceFile: ts.SourceFile): QuoteStyle {
347+
if (!this.quoteStyles.hasOwnProperty(sourceFile.fileName)) {
348+
let quoteStyle: QuoteStyle|undefined;
349+
350+
// Walk through the top-level imports and try to infer the quotes.
351+
for (const statement of sourceFile.statements) {
352+
if (ts.isImportDeclaration(statement) &&
353+
ts.isStringLiteralLike(statement.moduleSpecifier)) {
354+
// Use `getText` instead of the actual text since it includes the quotes.
355+
quoteStyle = statement.moduleSpecifier.getText().trim().startsWith('"') ?
356+
QuoteStyle.Double :
357+
QuoteStyle.Single;
358+
break;
359+
}
360+
}
361+
362+
// Otherwise fall back to single quotes.
363+
this.quoteStyles[sourceFile.fileName] = quoteStyle ?? QuoteStyle.Single;
364+
}
365+
366+
return this.quoteStyles[sourceFile.fileName];
367+
}
335368
}
336369

337370
/**

0 commit comments

Comments
 (0)