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