1namespace ts { 2 describe("unittests:: services:: organizeImports", () => { 3 describe("Sort imports", () => { 4 it("Sort - non-relative vs non-relative", () => { 5 assertSortsBefore( 6 `import y from "lib1";`, 7 `import x from "lib2";`); 8 }); 9 10 it("Sort - relative vs relative", () => { 11 assertSortsBefore( 12 `import y from "./lib1";`, 13 `import x from "./lib2";`); 14 }); 15 16 it("Sort - relative vs non-relative", () => { 17 assertSortsBefore( 18 `import y from "lib";`, 19 `import x from "./lib";`); 20 }); 21 22 it("Sort - case-insensitive", () => { 23 assertSortsBefore( 24 `import y from "a";`, 25 `import x from "Z";`); 26 assertSortsBefore( 27 `import y from "A";`, 28 `import x from "z";`); 29 }); 30 31 function assertSortsBefore(importString1: string, importString2: string) { 32 const [{moduleSpecifier: moduleSpecifier1}, {moduleSpecifier: moduleSpecifier2}] = parseImports(importString1, importString2); 33 assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier1, moduleSpecifier2), Comparison.LessThan); 34 assert.equal(OrganizeImports.compareModuleSpecifiers(moduleSpecifier2, moduleSpecifier1), Comparison.GreaterThan); 35 } 36 }); 37 38 describe("Coalesce imports", () => { 39 it("No imports", () => { 40 assert.isEmpty(OrganizeImports.coalesceImports([])); 41 }); 42 43 it("Sort specifiers - case-insensitive", () => { 44 const sortedImports = parseImports(`import { default as M, a as n, B, y, Z as O } from "lib";`); 45 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 46 const expectedCoalescedImports = parseImports(`import { a as n, B, default as M, y, Z as O } from "lib";`); 47 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 48 }); 49 50 it("Combine side-effect-only imports", () => { 51 const sortedImports = parseImports( 52 `import "lib";`, 53 `import "lib";`); 54 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 55 const expectedCoalescedImports = parseImports(`import "lib";`); 56 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 57 }); 58 59 it("Combine namespace imports", () => { 60 const sortedImports = parseImports( 61 `import * as x from "lib";`, 62 `import * as y from "lib";`); 63 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 64 const expectedCoalescedImports = sortedImports; 65 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 66 }); 67 68 it("Combine default imports", () => { 69 const sortedImports = parseImports( 70 `import x from "lib";`, 71 `import y from "lib";`); 72 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 73 const expectedCoalescedImports = parseImports(`import { default as x, default as y } from "lib";`); 74 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 75 }); 76 77 it("Combine property imports", () => { 78 const sortedImports = parseImports( 79 `import { x } from "lib";`, 80 `import { y as z } from "lib";`); 81 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 82 const expectedCoalescedImports = parseImports(`import { x, y as z } from "lib";`); 83 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 84 }); 85 86 it("Combine side-effect-only import with namespace import", () => { 87 const sortedImports = parseImports( 88 `import "lib";`, 89 `import * as x from "lib";`); 90 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 91 const expectedCoalescedImports = sortedImports; 92 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 93 }); 94 95 it("Combine side-effect-only import with default import", () => { 96 const sortedImports = parseImports( 97 `import "lib";`, 98 `import x from "lib";`); 99 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 100 const expectedCoalescedImports = sortedImports; 101 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 102 }); 103 104 it("Combine side-effect-only import with property import", () => { 105 const sortedImports = parseImports( 106 `import "lib";`, 107 `import { x } from "lib";`); 108 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 109 const expectedCoalescedImports = sortedImports; 110 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 111 }); 112 113 it("Combine namespace import with default import", () => { 114 const sortedImports = parseImports( 115 `import * as x from "lib";`, 116 `import y from "lib";`); 117 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 118 const expectedCoalescedImports = parseImports( 119 `import y, * as x from "lib";`); 120 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 121 }); 122 123 it("Combine namespace import with property import", () => { 124 const sortedImports = parseImports( 125 `import * as x from "lib";`, 126 `import { y } from "lib";`); 127 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 128 const expectedCoalescedImports = sortedImports; 129 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 130 }); 131 132 it("Combine default import with property import", () => { 133 const sortedImports = parseImports( 134 `import x from "lib";`, 135 `import { y } from "lib";`); 136 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 137 const expectedCoalescedImports = parseImports( 138 `import x, { y } from "lib";`); 139 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 140 }); 141 142 it("Combine many imports", () => { 143 const sortedImports = parseImports( 144 `import "lib";`, 145 `import * as y from "lib";`, 146 `import w from "lib";`, 147 `import { b } from "lib";`, 148 `import "lib";`, 149 `import * as x from "lib";`, 150 `import z from "lib";`, 151 `import { a } from "lib";`); 152 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 153 const expectedCoalescedImports = parseImports( 154 `import "lib";`, 155 `import * as x from "lib";`, 156 `import * as y from "lib";`, 157 `import { a, b, default as w, default as z } from "lib";`); 158 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 159 }); 160 161 // This is descriptive, rather than normative 162 it("Combine two namespace imports with one default import", () => { 163 const sortedImports = parseImports( 164 `import * as x from "lib";`, 165 `import * as y from "lib";`, 166 `import z from "lib";`); 167 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 168 const expectedCoalescedImports = sortedImports; 169 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 170 }); 171 172 it("Combine type-only imports separately from other imports", () => { 173 const sortedImports = parseImports( 174 `import type { x } from "lib";`, 175 `import type { y } from "lib";`, 176 `import { z } from "lib";`); 177 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 178 const expectedCoalescedImports = parseImports( 179 `import { z } from "lib";`, 180 `import type { x, y } from "lib";`); 181 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 182 }); 183 184 it("Do not combine type-only default, namespace, or named imports with each other", () => { 185 const sortedImports = parseImports( 186 `import type { x } from "lib";`, 187 `import type * as y from "lib";`, 188 `import type z from "lib";`); 189 // Default import could be rewritten as a named import to combine with `x`, 190 // but seems of debatable merit. 191 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 192 const expectedCoalescedImports = actualCoalescedImports; 193 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 194 }); 195 }); 196 197 describe("Coalesce exports", () => { 198 it("No exports", () => { 199 assert.isEmpty(OrganizeImports.coalesceExports([])); 200 }); 201 202 it("Sort specifiers - case-insensitive", () => { 203 const sortedExports = parseExports(`export { default as M, a as n, B, y, Z as O } from "lib";`); 204 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 205 const expectedCoalescedExports = parseExports(`export { a as n, B, default as M, y, Z as O } from "lib";`); 206 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 207 }); 208 209 it("Sort specifiers - type-only", () => { 210 const sortedImports = parseImports(`import { type z, y, type x, c, type b, a } from "lib";`); 211 const actualCoalescedImports = OrganizeImports.coalesceImports(sortedImports); 212 const expectedCoalescedImports = parseImports(`import { a, c, y, type b, type x, type z } from "lib";`); 213 assertListEqual(actualCoalescedImports, expectedCoalescedImports); 214 }); 215 216 it("Combine namespace re-exports", () => { 217 const sortedExports = parseExports( 218 `export * from "lib";`, 219 `export * from "lib";`); 220 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 221 const expectedCoalescedExports = parseExports(`export * from "lib";`); 222 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 223 }); 224 225 it("Combine property exports", () => { 226 const sortedExports = parseExports( 227 `export { x };`, 228 `export { y as z };`); 229 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 230 const expectedCoalescedExports = parseExports(`export { x, y as z };`); 231 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 232 }); 233 234 it("Combine property re-exports", () => { 235 const sortedExports = parseExports( 236 `export { x } from "lib";`, 237 `export { y as z } from "lib";`); 238 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 239 const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); 240 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 241 }); 242 243 it("Combine namespace re-export with property re-export", () => { 244 const sortedExports = parseExports( 245 `export * from "lib";`, 246 `export { y } from "lib";`); 247 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 248 const expectedCoalescedExports = sortedExports; 249 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 250 }); 251 252 it("Combine many exports", () => { 253 const sortedExports = parseExports( 254 `export { x };`, 255 `export { y as w, z as default };`, 256 `export { w as q };`); 257 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 258 const expectedCoalescedExports = parseExports( 259 `export { w as q, x, y as w, z as default };`); 260 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 261 }); 262 263 it("Combine many re-exports", () => { 264 const sortedExports = parseExports( 265 `export { x as a, y } from "lib";`, 266 `export * from "lib";`, 267 `export { z as b } from "lib";`); 268 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 269 const expectedCoalescedExports = parseExports( 270 `export * from "lib";`, 271 `export { x as a, y, z as b } from "lib";`); 272 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 273 }); 274 275 it("Keep type-only exports separate", () => { 276 const sortedExports = parseExports( 277 `export { x };`, 278 `export type { y };`); 279 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 280 const expectedCoalescedExports = sortedExports; 281 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 282 }); 283 284 it("Combine type-only exports", () => { 285 const sortedExports = parseExports( 286 `export type { x };`, 287 `export type { y };`); 288 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 289 const expectedCoalescedExports = parseExports( 290 `export type { x, y };`); 291 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 292 }); 293 }); 294 295 296 describe("Baselines", () => { 297 298 const libFile = { 299 path: "/lib.ts", 300 content: ` 301export function F1(); 302export default function F2(); 303`, 304 }; 305 306 const reactLibFile = { 307 path: "/react.ts", 308 content: ` 309export const React = { 310createElement: (_type, _props, _children) => {}, 311}; 312 313export const Other = 1; 314`, 315 }; 316 317 // Don't bother to actually emit a baseline for this. 318 it("NoImports", () => { 319 const testFile = { 320 path: "/a.ts", 321 content: "function F() { }", 322 }; 323 const languageService = makeLanguageService(testFile); 324 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 325 assert.isEmpty(changes); 326 }); 327 328 it("doesn't crash on shorthand ambient module", () => { 329 const testFile = { 330 path: "/a.ts", 331 content: "declare module '*';", 332 }; 333 const languageService = makeLanguageService(testFile); 334 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 335 assert.isEmpty(changes); 336 }); 337 338 it("doesn't return any changes when the text would be identical", () => { 339 const testFile = { 340 path: "/a.ts", 341 content: `import { f } from 'foo';\nf();` 342 }; 343 const languageService = makeLanguageService(testFile); 344 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 345 assert.isEmpty(changes); 346 }); 347 348 testOrganizeImports("Renamed_used", 349 /*skipDestructiveCodeActions*/ false, 350 { 351 path: "/test.ts", 352 content: ` 353import { F1 as EffOne, F2 as EffTwo } from "lib"; 354EffOne(); 355`, 356 }, 357 libFile); 358 359 testOrganizeImports("Simple", 360 /*skipDestructiveCodeActions*/ false, 361 { 362 path: "/test.ts", 363 content: ` 364import { F1, F2 } from "lib"; 365import * as NS from "lib"; 366import D from "lib"; 367 368NS.F1(); 369D(); 370F1(); 371F2(); 372`, 373 }, 374 libFile); 375 376 testOrganizeImports("Unused_Some", 377 /*skipDestructiveCodeActions*/ false, 378 { 379 path: "/test.ts", 380 content: ` 381import { F1, F2 } from "lib"; 382import * as NS from "lib"; 383import D from "lib"; 384 385D(); 386`, 387 }, 388 libFile); 389 390 describe("skipDestructiveCodeActions=true", () => { 391 testOrganizeImports("Syntax_Error_Body_skipDestructiveCodeActions", 392 /*skipDestructiveCodeActions*/ true, 393 { 394 path: "/test.ts", 395 content: ` 396import { F1, F2 } from "lib"; 397import * as NS from "lib"; 398import D from "lib"; 399 400class class class; 401D; 402`, 403 }, 404 libFile); 405 }); 406 407 testOrganizeImports("Syntax_Error_Imports_skipDestructiveCodeActions", 408 /*skipDestructiveCodeActions*/ true, 409 { 410 path: "/test.ts", 411 content: ` 412import { F1, F2 class class class; } from "lib"; 413import * as NS from "lib"; 414class class class; 415import D from "lib"; 416 417D; 418`, 419 }, 420 libFile); 421 422 describe("skipDestructiveCodeActions=false", () => { 423 testOrganizeImports("Syntax_Error_Body", 424 /*skipDestructiveCodeActions*/ false, 425 { 426 path: "/test.ts", 427 content: ` 428import { F1, F2 } from "lib"; 429import * as NS from "lib"; 430import D from "lib"; 431 432class class class; 433D; 434`, 435 }, 436 libFile); 437 438 testOrganizeImports("Syntax_Error_Imports", 439 /*skipDestructiveCodeActions*/ false, 440 { 441 path: "/test.ts", 442 content: ` 443import { F1, F2 class class class; } from "lib"; 444import * as NS from "lib"; 445class class class; 446import D from "lib"; 447 448D; 449`, 450 }, 451 libFile); 452 }); 453 454 it("doesn't return any changes when the text would be identical", () => { 455 const testFile = { 456 path: "/a.ts", 457 content: `import { f } from 'foo';\nf();` 458 }; 459 const languageService = makeLanguageService(testFile); 460 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 461 assert.isEmpty(changes); 462 }); 463 464 testOrganizeImports("Unused_All", 465 /*skipDestructiveCodeActions*/ false, 466 { 467 path: "/test.ts", 468 content: ` 469import { F1, F2 } from "lib"; 470import * as NS from "lib"; 471import D from "lib"; 472`, 473 }, 474 libFile); 475 476 it("Unused_Empty", () => { 477 const testFile = { 478 path: "/test.ts", 479 content: ` 480import { } from "lib"; 481`, 482 }; 483 const languageService = makeLanguageService(testFile); 484 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 485 assert.isEmpty(changes); 486 }); 487 488 testOrganizeImports("Unused_false_positive_module_augmentation", 489 /*skipDestructiveCodeActions*/ false, 490 { 491 path: "/test.d.ts", 492 content: ` 493import foo from 'foo'; 494import { Caseless } from 'caseless'; 495 496declare module 'foo' {} 497declare module 'caseless' { 498 interface Caseless { 499 test(name: KeyType): boolean; 500 } 501}` 502 }); 503 504 testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", 505 /*skipDestructiveCodeActions*/ false, 506 { 507 path: "/test.ts", 508 content: ` 509import foo from 'foo'; 510import { Caseless } from 'caseless'; 511 512declare module 'foo' {} 513declare module 'caseless' { 514 interface Caseless { 515 test(name: KeyType): boolean; 516 } 517}` 518 }); 519 520 it("Unused_false_positive_shorthand_assignment", () => { 521 const testFile = { 522 path: "/test.ts", 523 content: ` 524import { x } from "a"; 525const o = { x }; 526` 527 }; 528 const languageService = makeLanguageService(testFile); 529 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 530 assert.isEmpty(changes); 531 }); 532 533 it("Unused_false_positive_export_shorthand", () => { 534 const testFile = { 535 path: "/test.ts", 536 content: ` 537import { x } from "a"; 538export { x }; 539` 540 }; 541 const languageService = makeLanguageService(testFile); 542 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 543 assert.isEmpty(changes); 544 }); 545 546 testOrganizeImports("MoveToTop", 547 /*skipDestructiveCodeActions*/ false, 548 { 549 path: "/test.ts", 550 content: ` 551import { F1, F2 } from "lib"; 552F1(); 553F2(); 554import * as NS from "lib"; 555NS.F1(); 556import D from "lib"; 557D(); 558`, 559 }, 560 libFile); 561 562 /* eslint-disable no-template-curly-in-string */ 563 testOrganizeImports("MoveToTop_Invalid", 564 /*skipDestructiveCodeActions*/ false, 565 { 566 path: "/test.ts", 567 content: ` 568import { F1, F2 } from "lib"; 569F1(); 570F2(); 571import * as NS from "lib"; 572NS.F1(); 573import b from ${"`${'lib'}`"}; 574import a from ${"`${'lib'}`"}; 575import D from "lib"; 576D(); 577`, 578 }, 579 libFile); 580 /* eslint-enable no-template-curly-in-string */ 581 582 testOrganizeImports("TypeOnly", 583 /*skipDestructiveCodeActions*/ false, 584 { 585 path: "/test.ts", 586 content: ` 587import { X } from "lib"; 588import type Y from "lib"; 589import { Z } from "lib"; 590import type { A, B } from "lib"; 591 592export { A, B, X, Y, Z };` 593 }); 594 595 testOrganizeImports("CoalesceMultipleModules", 596 /*skipDestructiveCodeActions*/ false, 597 { 598 path: "/test.ts", 599 content: ` 600import { d } from "lib1"; 601import { b } from "lib1"; 602import { c } from "lib2"; 603import { a } from "lib2"; 604a + b + c + d; 605`, 606 }, 607 { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, 608 { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); 609 610 testOrganizeImports("CoalesceTrivia", 611 /*skipDestructiveCodeActions*/ false, 612 { 613 path: "/test.ts", 614 content: ` 615/*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 616/*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R 617 618F1(); 619F2(); 620`, 621 }, 622 libFile); 623 624 testOrganizeImports("SortTrivia", 625 /*skipDestructiveCodeActions*/ false, 626 { 627 path: "/test.ts", 628 content: ` 629/*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E 630/*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J 631`, 632 }, 633 { path: "/lib1.ts", content: "" }, 634 { path: "/lib2.ts", content: "" }); 635 636 testOrganizeImports("UnusedTrivia1", 637 /*skipDestructiveCodeActions*/ false, 638 { 639 path: "/test.ts", 640 content: ` 641/*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 642`, 643 }, 644 libFile); 645 646 testOrganizeImports("UnusedTrivia2", 647 /*skipDestructiveCodeActions*/ false, 648 { 649 path: "/test.ts", 650 content: ` 651/*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K 652 653F1(); 654`, 655 }, 656 libFile); 657 658 testOrganizeImports("UnusedHeaderComment", 659 /*skipDestructiveCodeActions*/ false, 660 { 661 path: "/test.ts", 662 content: ` 663// Header 664import { F1 } from "lib"; 665`, 666 }, 667 libFile); 668 669 testOrganizeImports("SortHeaderComment", 670 /*skipDestructiveCodeActions*/ false, 671 { 672 path: "/test.ts", 673 content: ` 674// Header 675import "lib2"; 676import "lib1"; 677`, 678 }, 679 { path: "/lib1.ts", content: "" }, 680 { path: "/lib2.ts", content: "" }); 681 682 testOrganizeImports("AmbientModule", 683 /*skipDestructiveCodeActions*/ false, 684 { 685 path: "/test.ts", 686 content: ` 687declare module "mod" { 688 import { F1 } from "lib"; 689 import * as NS from "lib"; 690 import { F2 } from "lib"; 691 692 function F(f1: {} = F1, f2: {} = F2) {} 693} 694`, 695 }, 696 libFile); 697 698 testOrganizeImports("TopLevelAndAmbientModule", 699 /*skipDestructiveCodeActions*/ false, 700 { 701 path: "/test.ts", 702 content: ` 703import D from "lib"; 704 705declare module "mod" { 706 import { F1 } from "lib"; 707 import * as NS from "lib"; 708 import { F2 } from "lib"; 709 710 function F(f1: {} = F1, f2: {} = F2) {} 711} 712 713import E from "lib"; 714import "lib"; 715 716D(); 717`, 718 }, 719 libFile); 720 721 testOrganizeImports("JsxFactoryUsedJsx", 722 /*skipDestructiveCodeActions*/ false, 723 { 724 path: "/test.jsx", 725 content: ` 726import { React, Other } from "react"; 727 728<div/>; 729`, 730 }, 731 reactLibFile); 732 733 testOrganizeImports("JsxFactoryUsedJs", 734 /*skipDestructiveCodeActions*/ false, 735 { 736 path: "/test.js", 737 content: ` 738import { React, Other } from "react"; 739 740<div/>; 741`, 742 }, 743 reactLibFile); 744 745 testOrganizeImports("JsxFactoryUsedTsx", 746 /*skipDestructiveCodeActions*/ false, 747 { 748 path: "/test.tsx", 749 content: ` 750import { React, Other } from "react"; 751 752<div/>; 753`, 754 }, 755 reactLibFile); 756 757 // TS files are not JSX contexts, so the parser does not treat 758 // `<div/>` as a JSX element. 759 testOrganizeImports("JsxFactoryUsedTs", 760 /*skipDestructiveCodeActions*/ false, 761 { 762 path: "/test.ts", 763 content: ` 764import { React, Other } from "react"; 765 766<div/>; 767`, 768 }, 769 reactLibFile); 770 771 testOrganizeImports("JsxFactoryUnusedJsx", 772 /*skipDestructiveCodeActions*/ false, 773 { 774 path: "/test.jsx", 775 content: ` 776import { React, Other } from "react"; 777`, 778 }, 779 reactLibFile); 780 781 // Note: Since the file extension does not end with "x", the jsx compiler option 782 // will not be enabled. The import should be retained regardless. 783 testOrganizeImports("JsxFactoryUnusedJs", 784 /*skipDestructiveCodeActions*/ false, 785 { 786 path: "/test.js", 787 content: ` 788import { React, Other } from "react"; 789`, 790 }, 791 reactLibFile); 792 793 testOrganizeImports("JsxFactoryUnusedTsx", 794 /*skipDestructiveCodeActions*/ false, 795 { 796 path: "/test.tsx", 797 content: ` 798import { React, Other } from "react"; 799`, 800 }, 801 reactLibFile); 802 803 testOrganizeImports("JsxFactoryUnusedTs", 804 /*skipDestructiveCodeActions*/ false, 805 { 806 path: "/test.ts", 807 content: ` 808import { React, Other } from "react"; 809`, 810 }, 811 reactLibFile); 812 813 testOrganizeImports("JsxPragmaTsx", 814 /*skipDestructiveCodeActions*/ false, 815 { 816 path: "/test.tsx", 817 content: `/** @jsx jsx */ 818 819import { Global, jsx } from '@emotion/core'; 820import * as React from 'react'; 821 822export const App: React.FunctionComponent = _ => <Global><h1>Hello!</h1></Global> 823`, 824 }, 825 { 826 path: "/@emotion/core/index.d.ts", 827 content: `import { createElement } from 'react' 828export const jsx: typeof createElement; 829export function Global(props: any): ReactElement<any>;` 830 }, 831 { 832 path: reactLibFile.path, 833 content: `${reactLibFile.content} 834export namespace React { 835 interface FunctionComponent { 836 } 837} 838` 839 } 840 ); 841 842 testOrganizeImports("JsxFragmentPragmaTsx", 843 /*skipDestructiveCodeActions*/ false, 844 { 845 path: "/test.tsx", 846 content: `/** @jsx h */ 847/** @jsxFrag frag */ 848import { h, frag } from "@foo/core"; 849 850const elem = <><div>Foo</div></>; 851`, 852 }, 853 { 854 path: "/@foo/core/index.d.ts", 855 content: `export function h(): void; 856export function frag(): void; 857` 858 } 859 ); 860 861 describe("Exports", () => { 862 863 testOrganizeExports("MoveToTop", 864 { 865 path: "/test.ts", 866 content: ` 867export { F1, F2 } from "lib"; 8681; 869export * from "lib"; 8702; 871`, 872 }, 873 libFile); 874 875 /* eslint-disable no-template-curly-in-string */ 876 testOrganizeExports("MoveToTop_Invalid", 877 { 878 path: "/test.ts", 879 content: ` 880export { F1, F2 } from "lib"; 8811; 882export * from "lib"; 8832; 884export { b } from ${"`${'lib'}`"}; 885export { a } from ${"`${'lib'}`"}; 886export { D } from "lib"; 8873; 888`, 889 }, 890 libFile); 891 /* eslint-enable no-template-curly-in-string */ 892 893 testOrganizeExports("MoveToTop_WithImportsFirst", 894 { 895 path: "/test.ts", 896 content: ` 897import { F1, F2 } from "lib"; 8981; 899export { F1, F2 } from "lib"; 9002; 901import * as NS from "lib"; 9023; 903export * from "lib"; 9044; 905F1(); F2(); NS.F1(); 906`, 907 }, 908 libFile); 909 910 testOrganizeExports("MoveToTop_WithExportsFirst", 911 { 912 path: "/test.ts", 913 content: ` 914export { F1, F2 } from "lib"; 9151; 916import { F1, F2 } from "lib"; 9172; 918export * from "lib"; 9193; 920import * as NS from "lib"; 9214; 922F1(); F2(); NS.F1(); 923`, 924 }, 925 libFile); 926 927 testOrganizeExports("CoalesceMultipleModules", 928 { 929 path: "/test.ts", 930 content: ` 931export { d } from "lib1"; 932export { b } from "lib1"; 933export { c } from "lib2"; 934export { a } from "lib2"; 935`, 936 }, 937 { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, 938 { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); 939 940 testOrganizeExports("CoalesceTrivia", 941 { 942 path: "/test.ts", 943 content: ` 944/*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 945/*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R 946`, 947 }, 948 libFile); 949 950 testOrganizeExports("SortTrivia", 951 { 952 path: "/test.ts", 953 content: ` 954/*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G 955/*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N 956`, 957 }, 958 { path: "/lib1.ts", content: "" }, 959 { path: "/lib2.ts", content: "" }); 960 961 testOrganizeExports("SortHeaderComment", 962 { 963 path: "/test.ts", 964 content: ` 965// Header 966export * from "lib2"; 967export * from "lib1"; 968`, 969 }, 970 { path: "/lib1.ts", content: "" }, 971 { path: "/lib2.ts", content: "" }); 972 973 testOrganizeExports("AmbientModule", 974 { 975 path: "/test.ts", 976 content: ` 977declare module "mod" { 978 export { F1 } from "lib"; 979 export * from "lib"; 980 export { F2 } from "lib"; 981} 982 `, 983 }, 984 libFile); 985 986 testOrganizeExports("TopLevelAndAmbientModule", 987 { 988 path: "/test.ts", 989 content: ` 990export { D } from "lib"; 991 992declare module "mod" { 993 export { F1 } from "lib"; 994 export * from "lib"; 995 export { F2 } from "lib"; 996} 997 998export { E } from "lib"; 999export * from "lib"; 1000`, 1001 }, 1002 libFile); 1003 }); 1004 1005 function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 1006 testOrganizeImports(`${testName}.exports`, /*skipDestructiveCodeActions*/ true, testFile, ...otherFiles); 1007 } 1008 1009 function testOrganizeImports(testName: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 1010 it(testName, () => runBaseline(`organizeImports/${testName}.ts`, skipDestructiveCodeActions, testFile, ...otherFiles)); 1011 } 1012 1013 function runBaseline(baselinePath: string, skipDestructiveCodeActions: boolean, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 1014 const { path: testPath, content: testContent } = testFile; 1015 const languageService = makeLanguageService(testFile, ...otherFiles); 1016 const changes = languageService.organizeImports({ skipDestructiveCodeActions, type: "file", fileName: testPath }, testFormatSettings, emptyOptions); 1017 assert.equal(changes.length, 1); 1018 assert.equal(changes[0].fileName, testPath); 1019 1020 const newText = textChanges.applyChanges(testContent, changes[0].textChanges); 1021 Harness.Baseline.runBaseline(baselinePath, [ 1022 "// ==ORIGINAL==", 1023 testContent, 1024 "// ==ORGANIZED==", 1025 newText, 1026 ].join(newLineCharacter)); 1027 } 1028 1029 function makeLanguageService(...files: TestFSWithWatch.File[]) { 1030 const host = projectSystem.createServerHost(files); 1031 const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true }); 1032 projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None }); 1033 files.forEach(f => projectService.openClientFile(f.path)); 1034 return projectService.inferredProjects[0].getLanguageService(); 1035 } 1036 }); 1037 1038 function parseImports(...importStrings: string[]): readonly ImportDeclaration[] { 1039 const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); 1040 const imports = filter(sourceFile.statements, isImportDeclaration); 1041 assert.equal(imports.length, importStrings.length); 1042 return imports; 1043 } 1044 1045 function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] { 1046 const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); 1047 const exports = filter(sourceFile.statements, isExportDeclaration); 1048 assert.equal(exports.length, exportStrings.length); 1049 return exports; 1050 } 1051 1052 function assertEqual(node1?: Node, node2?: Node) { 1053 if (node1 === undefined) { 1054 assert.isUndefined(node2); 1055 return; 1056 } 1057 else if (node2 === undefined) { 1058 assert.isUndefined(node1); // Guaranteed to fail 1059 return; 1060 } 1061 1062 assert.equal(node1.kind, node2.kind); 1063 1064 switch (node1.kind) { 1065 case SyntaxKind.ImportDeclaration: 1066 const decl1 = node1 as ImportDeclaration; 1067 const decl2 = node2 as ImportDeclaration; 1068 assertEqual(decl1.importClause, decl2.importClause); 1069 assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); 1070 break; 1071 case SyntaxKind.ImportClause: 1072 const clause1 = node1 as ImportClause; 1073 const clause2 = node2 as ImportClause; 1074 assertEqual(clause1.name, clause2.name); 1075 assertEqual(clause1.namedBindings, clause2.namedBindings); 1076 break; 1077 case SyntaxKind.NamespaceImport: 1078 const nsi1 = node1 as NamespaceImport; 1079 const nsi2 = node2 as NamespaceImport; 1080 assertEqual(nsi1.name, nsi2.name); 1081 break; 1082 case SyntaxKind.NamedImports: 1083 const ni1 = node1 as NamedImports; 1084 const ni2 = node2 as NamedImports; 1085 assertListEqual(ni1.elements, ni2.elements); 1086 break; 1087 case SyntaxKind.ImportSpecifier: 1088 const is1 = node1 as ImportSpecifier; 1089 const is2 = node2 as ImportSpecifier; 1090 assertEqual(is1.name, is2.name); 1091 assertEqual(is1.propertyName, is2.propertyName); 1092 break; 1093 case SyntaxKind.ExportDeclaration: 1094 const ed1 = node1 as ExportDeclaration; 1095 const ed2 = node2 as ExportDeclaration; 1096 assertEqual(ed1.exportClause, ed2.exportClause); 1097 assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); 1098 break; 1099 case SyntaxKind.NamedExports: 1100 const ne1 = node1 as NamedExports; 1101 const ne2 = node2 as NamedExports; 1102 assertListEqual(ne1.elements, ne2.elements); 1103 break; 1104 case SyntaxKind.ExportSpecifier: 1105 const es1 = node1 as ExportSpecifier; 1106 const es2 = node2 as ExportSpecifier; 1107 assertEqual(es1.name, es2.name); 1108 assertEqual(es1.propertyName, es2.propertyName); 1109 break; 1110 case SyntaxKind.Identifier: 1111 const id1 = node1 as Identifier; 1112 const id2 = node2 as Identifier; 1113 assert.equal(id1.text, id2.text); 1114 break; 1115 case SyntaxKind.StringLiteral: 1116 case SyntaxKind.NoSubstitutionTemplateLiteral: 1117 const sl1 = node1 as LiteralLikeNode; 1118 const sl2 = node2 as LiteralLikeNode; 1119 assert.equal(sl1.text, sl2.text); 1120 break; 1121 default: 1122 assert.equal(node1.getText(), node2.getText()); 1123 break; 1124 } 1125 } 1126 1127 function assertListEqual(list1: readonly Node[], list2: readonly Node[]) { 1128 if (list1 === undefined || list2 === undefined) { 1129 assert.isUndefined(list1); 1130 assert.isUndefined(list2); 1131 return; 1132 } 1133 1134 assert.equal(list1.length, list2.length); 1135 for (let i = 0; i < list1.length; i++) { 1136 assertEqual(list1[i], list2[i]); 1137 } 1138 } 1139 }); 1140} 1141