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("Combine namespace re-exports", () => { 210 const sortedExports = parseExports( 211 `export * from "lib";`, 212 `export * from "lib";`); 213 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 214 const expectedCoalescedExports = parseExports(`export * from "lib";`); 215 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 216 }); 217 218 it("Combine property exports", () => { 219 const sortedExports = parseExports( 220 `export { x };`, 221 `export { y as z };`); 222 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 223 const expectedCoalescedExports = parseExports(`export { x, y as z };`); 224 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 225 }); 226 227 it("Combine property re-exports", () => { 228 const sortedExports = parseExports( 229 `export { x } from "lib";`, 230 `export { y as z } from "lib";`); 231 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 232 const expectedCoalescedExports = parseExports(`export { x, y as z } from "lib";`); 233 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 234 }); 235 236 it("Combine namespace re-export with property re-export", () => { 237 const sortedExports = parseExports( 238 `export * from "lib";`, 239 `export { y } from "lib";`); 240 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 241 const expectedCoalescedExports = sortedExports; 242 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 243 }); 244 245 it("Combine many exports", () => { 246 const sortedExports = parseExports( 247 `export { x };`, 248 `export { y as w, z as default };`, 249 `export { w as q };`); 250 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 251 const expectedCoalescedExports = parseExports( 252 `export { w as q, x, y as w, z as default };`); 253 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 254 }); 255 256 it("Combine many re-exports", () => { 257 const sortedExports = parseExports( 258 `export { x as a, y } from "lib";`, 259 `export * from "lib";`, 260 `export { z as b } from "lib";`); 261 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 262 const expectedCoalescedExports = parseExports( 263 `export * from "lib";`, 264 `export { x as a, y, z as b } from "lib";`); 265 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 266 }); 267 268 it("Keep type-only exports separate", () => { 269 const sortedExports = parseExports( 270 `export { x };`, 271 `export type { y };`); 272 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 273 const expectedCoalescedExports = sortedExports; 274 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 275 }); 276 277 it("Combine type-only exports", () => { 278 const sortedExports = parseExports( 279 `export type { x };`, 280 `export type { y };`); 281 const actualCoalescedExports = OrganizeImports.coalesceExports(sortedExports); 282 const expectedCoalescedExports = parseExports( 283 `export type { x, y };`); 284 assertListEqual(actualCoalescedExports, expectedCoalescedExports); 285 }); 286 }); 287 288 289 describe("Baselines", () => { 290 291 const libFile = { 292 path: "/lib.ts", 293 content: ` 294export function F1(); 295export default function F2(); 296`, 297 }; 298 299 const reactLibFile = { 300 path: "/react.ts", 301 content: ` 302export const React = { 303createElement: (_type, _props, _children) => {}, 304}; 305 306export const Other = 1; 307`, 308 }; 309 310 // Don't bother to actually emit a baseline for this. 311 it("NoImports", () => { 312 const testFile = { 313 path: "/a.ts", 314 content: "function F() { }", 315 }; 316 const languageService = makeLanguageService(testFile); 317 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 318 assert.isEmpty(changes); 319 }); 320 321 it("doesn't crash on shorthand ambient module", () => { 322 const testFile = { 323 path: "/a.ts", 324 content: "declare module '*';", 325 }; 326 const languageService = makeLanguageService(testFile); 327 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 328 assert.isEmpty(changes); 329 }); 330 331 it("doesn't return any changes when the text would be identical", () => { 332 const testFile = { 333 path: "/a.ts", 334 content: `import { f } from 'foo';\nf();` 335 }; 336 const languageService = makeLanguageService(testFile); 337 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 338 assert.isEmpty(changes); 339 }); 340 341 testOrganizeImports("Renamed_used", 342 { 343 path: "/test.ts", 344 content: ` 345import { F1 as EffOne, F2 as EffTwo } from "lib"; 346EffOne(); 347`, 348 }, 349 libFile); 350 351 testOrganizeImports("Simple", 352 { 353 path: "/test.ts", 354 content: ` 355import { F1, F2 } from "lib"; 356import * as NS from "lib"; 357import D from "lib"; 358 359NS.F1(); 360D(); 361F1(); 362F2(); 363`, 364 }, 365 libFile); 366 367 testOrganizeImports("Unused_Some", 368 { 369 path: "/test.ts", 370 content: ` 371import { F1, F2 } from "lib"; 372import * as NS from "lib"; 373import D from "lib"; 374 375D(); 376`, 377 }, 378 libFile); 379 380 it("doesn't return any changes when the text would be identical", () => { 381 const testFile = { 382 path: "/a.ts", 383 content: `import { f } from 'foo';\nf();` 384 }; 385 const languageService = makeLanguageService(testFile); 386 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 387 assert.isEmpty(changes); 388 }); 389 390 testOrganizeImports("Unused_All", 391 { 392 path: "/test.ts", 393 content: ` 394import { F1, F2 } from "lib"; 395import * as NS from "lib"; 396import D from "lib"; 397`, 398 }, 399 libFile); 400 401 it("Unused_Empty", () => { 402 const testFile = { 403 path: "/test.ts", 404 content: ` 405import { } from "lib"; 406`, 407 }; 408 const languageService = makeLanguageService(testFile); 409 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 410 assert.isEmpty(changes); 411 }); 412 413 testOrganizeImports("Unused_false_positive_module_augmentation", 414 { 415 path: "/test.d.ts", 416 content: ` 417import foo from 'foo'; 418import { Caseless } from 'caseless'; 419 420declare module 'foo' {} 421declare module 'caseless' { 422 interface Caseless { 423 test(name: KeyType): boolean; 424 } 425}` 426 }); 427 428 testOrganizeImports("Unused_preserve_imports_for_module_augmentation_in_non_declaration_file", 429 { 430 path: "/test.ts", 431 content: ` 432import foo from 'foo'; 433import { Caseless } from 'caseless'; 434 435declare module 'foo' {} 436declare module 'caseless' { 437 interface Caseless { 438 test(name: KeyType): boolean; 439 } 440}` 441 }); 442 443 it("Unused_false_positive_shorthand_assignment", () => { 444 const testFile = { 445 path: "/test.ts", 446 content: ` 447import { x } from "a"; 448const o = { x }; 449` 450 }; 451 const languageService = makeLanguageService(testFile); 452 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 453 assert.isEmpty(changes); 454 }); 455 456 it("Unused_false_positive_export_shorthand", () => { 457 const testFile = { 458 path: "/test.ts", 459 content: ` 460import { x } from "a"; 461export { x }; 462` 463 }; 464 const languageService = makeLanguageService(testFile); 465 const changes = languageService.organizeImports({ type: "file", fileName: testFile.path }, testFormatSettings, emptyOptions); 466 assert.isEmpty(changes); 467 }); 468 469 testOrganizeImports("MoveToTop", 470 { 471 path: "/test.ts", 472 content: ` 473import { F1, F2 } from "lib"; 474F1(); 475F2(); 476import * as NS from "lib"; 477NS.F1(); 478import D from "lib"; 479D(); 480`, 481 }, 482 libFile); 483 484 /* eslint-disable no-template-curly-in-string */ 485 testOrganizeImports("MoveToTop_Invalid", 486 { 487 path: "/test.ts", 488 content: ` 489import { F1, F2 } from "lib"; 490F1(); 491F2(); 492import * as NS from "lib"; 493NS.F1(); 494import b from ${"`${'lib'}`"}; 495import a from ${"`${'lib'}`"}; 496import D from "lib"; 497D(); 498`, 499 }, 500 libFile); 501 /* eslint-enable no-template-curly-in-string */ 502 503 testOrganizeImports("TypeOnly", 504 { 505 path: "/test.ts", 506 content: ` 507import { X } from "lib"; 508import type Y from "lib"; 509import { Z } from "lib"; 510import type { A, B } from "lib"; 511 512export { A, B, X, Y, Z };` 513 }); 514 515 testOrganizeImports("CoalesceMultipleModules", 516 { 517 path: "/test.ts", 518 content: ` 519import { d } from "lib1"; 520import { b } from "lib1"; 521import { c } from "lib2"; 522import { a } from "lib2"; 523a + b + c + d; 524`, 525 }, 526 { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, 527 { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); 528 529 testOrganizeImports("CoalesceTrivia", 530 { 531 path: "/test.ts", 532 content: ` 533/*A*/import /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 534/*J*/import /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R 535 536F1(); 537F2(); 538`, 539 }, 540 libFile); 541 542 testOrganizeImports("SortTrivia", 543 { 544 path: "/test.ts", 545 content: ` 546/*A*/import /*B*/ "lib2" /*C*/;/*D*/ //E 547/*F*/import /*G*/ "lib1" /*H*/;/*I*/ //J 548`, 549 }, 550 { path: "/lib1.ts", content: "" }, 551 { path: "/lib2.ts", content: "" }); 552 553 testOrganizeImports("UnusedTrivia1", 554 { 555 path: "/test.ts", 556 content: ` 557/*A*/import /*B*/ { /*C*/ F1 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 558`, 559 }, 560 libFile); 561 562 testOrganizeImports("UnusedTrivia2", 563 { 564 path: "/test.ts", 565 content: ` 566/*A*/import /*B*/ { /*C*/ F1 /*D*/, /*E*/ F2 /*F*/ } /*G*/ from /*H*/ "lib" /*I*/;/*J*/ //K 567 568F1(); 569`, 570 }, 571 libFile); 572 573 testOrganizeImports("UnusedHeaderComment", 574 { 575 path: "/test.ts", 576 content: ` 577// Header 578import { F1 } from "lib"; 579`, 580 }, 581 libFile); 582 583 testOrganizeImports("SortHeaderComment", 584 { 585 path: "/test.ts", 586 content: ` 587// Header 588import "lib2"; 589import "lib1"; 590`, 591 }, 592 { path: "/lib1.ts", content: "" }, 593 { path: "/lib2.ts", content: "" }); 594 595 testOrganizeImports("SortComments", 596 { 597 path: "/test.ts", 598 content: ` 599// Header 600import "lib3"; 601// Comment2 602import "lib2"; 603// Comment1 604import "lib1"; 605`, 606 }, 607 { path: "/lib1.ts", content: "" }, 608 { path: "/lib2.ts", content: "" }, 609 { path: "/lib3.ts", content: "" }); 610 611 testOrganizeImports("AmbientModule", 612 { 613 path: "/test.ts", 614 content: ` 615declare module "mod" { 616 import { F1 } from "lib"; 617 import * as NS from "lib"; 618 import { F2 } from "lib"; 619 620 function F(f1: {} = F1, f2: {} = F2) {} 621} 622`, 623 }, 624 libFile); 625 626 testOrganizeImports("TopLevelAndAmbientModule", 627 { 628 path: "/test.ts", 629 content: ` 630import D from "lib"; 631 632declare module "mod" { 633 import { F1 } from "lib"; 634 import * as NS from "lib"; 635 import { F2 } from "lib"; 636 637 function F(f1: {} = F1, f2: {} = F2) {} 638} 639 640import E from "lib"; 641import "lib"; 642 643D(); 644`, 645 }, 646 libFile); 647 648 testOrganizeImports("JsxFactoryUsedJsx", 649 { 650 path: "/test.jsx", 651 content: ` 652import { React, Other } from "react"; 653 654<div/>; 655`, 656 }, 657 reactLibFile); 658 659 testOrganizeImports("JsxFactoryUsedJs", 660 { 661 path: "/test.js", 662 content: ` 663import { React, Other } from "react"; 664 665<div/>; 666`, 667 }, 668 reactLibFile); 669 670 testOrganizeImports("JsxFactoryUsedTsx", 671 { 672 path: "/test.tsx", 673 content: ` 674import { React, Other } from "react"; 675 676<div/>; 677`, 678 }, 679 reactLibFile); 680 681 // TS files are not JSX contexts, so the parser does not treat 682 // `<div/>` as a JSX element. 683 testOrganizeImports("JsxFactoryUsedTs", 684 { 685 path: "/test.ts", 686 content: ` 687import { React, Other } from "react"; 688 689<div/>; 690`, 691 }, 692 reactLibFile); 693 694 testOrganizeImports("JsxFactoryUnusedJsx", 695 { 696 path: "/test.jsx", 697 content: ` 698import { React, Other } from "react"; 699`, 700 }, 701 reactLibFile); 702 703 // Note: Since the file extension does not end with "x", the jsx compiler option 704 // will not be enabled. The import should be retained regardless. 705 testOrganizeImports("JsxFactoryUnusedJs", 706 { 707 path: "/test.js", 708 content: ` 709import { React, Other } from "react"; 710`, 711 }, 712 reactLibFile); 713 714 testOrganizeImports("JsxFactoryUnusedTsx", 715 { 716 path: "/test.tsx", 717 content: ` 718import { React, Other } from "react"; 719`, 720 }, 721 reactLibFile); 722 723 testOrganizeImports("JsxFactoryUnusedTs", 724 { 725 path: "/test.ts", 726 content: ` 727import { React, Other } from "react"; 728`, 729 }, 730 reactLibFile); 731 732 testOrganizeImports("JsxPragmaTsx", 733 { 734 path: "/test.tsx", 735 content: `/** @jsx jsx */ 736 737import { Global, jsx } from '@emotion/core'; 738import * as React from 'react'; 739 740export const App: React.FunctionComponent = _ => <Global><h1>Hello!</h1></Global> 741`, 742 }, 743 { 744 path: "/@emotion/core/index.d.ts", 745 content: `import { createElement } from 'react' 746export const jsx: typeof createElement; 747export function Global(props: any): ReactElement<any>;` 748 }, 749 { 750 path: reactLibFile.path, 751 content: `${reactLibFile.content} 752export namespace React { 753 interface FunctionComponent { 754 } 755} 756` 757 } 758 ); 759 760 testOrganizeImports("JsxFragmentPragmaTsx", 761 { 762 path: "/test.tsx", 763 content: `/** @jsx h */ 764/** @jsxFrag frag */ 765import { h, frag } from "@foo/core"; 766 767const elem = <><div>Foo</div></>; 768`, 769 }, 770 { 771 path: "/@foo/core/index.d.ts", 772 content: `export function h(): void; 773export function frag(): void; 774` 775 } 776 ); 777 778 describe("Exports", () => { 779 780 testOrganizeExports("MoveToTop", 781 { 782 path: "/test.ts", 783 content: ` 784export { F1, F2 } from "lib"; 7851; 786export * from "lib"; 7872; 788`, 789 }, 790 libFile); 791 792 /* eslint-disable no-template-curly-in-string */ 793 testOrganizeExports("MoveToTop_Invalid", 794 { 795 path: "/test.ts", 796 content: ` 797export { F1, F2 } from "lib"; 7981; 799export * from "lib"; 8002; 801export { b } from ${"`${'lib'}`"}; 802export { a } from ${"`${'lib'}`"}; 803export { D } from "lib"; 8043; 805`, 806 }, 807 libFile); 808 /* eslint-enable no-template-curly-in-string */ 809 810 testOrganizeExports("MoveToTop_WithImportsFirst", 811 { 812 path: "/test.ts", 813 content: ` 814import { F1, F2 } from "lib"; 8151; 816export { F1, F2 } from "lib"; 8172; 818import * as NS from "lib"; 8193; 820export * from "lib"; 8214; 822F1(); F2(); NS.F1(); 823`, 824 }, 825 libFile); 826 827 testOrganizeExports("MoveToTop_WithExportsFirst", 828 { 829 path: "/test.ts", 830 content: ` 831export { F1, F2 } from "lib"; 8321; 833import { F1, F2 } from "lib"; 8342; 835export * from "lib"; 8363; 837import * as NS from "lib"; 8384; 839F1(); F2(); NS.F1(); 840`, 841 }, 842 libFile); 843 844 testOrganizeExports("CoalesceMultipleModules", 845 { 846 path: "/test.ts", 847 content: ` 848export { d } from "lib1"; 849export { b } from "lib1"; 850export { c } from "lib2"; 851export { a } from "lib2"; 852`, 853 }, 854 { path: "/lib1.ts", content: "export const b = 1, d = 2;" }, 855 { path: "/lib2.ts", content: "export const a = 3, c = 4;" }); 856 857 testOrganizeExports("CoalesceTrivia", 858 { 859 path: "/test.ts", 860 content: ` 861/*A*/export /*B*/ { /*C*/ F2 /*D*/ } /*E*/ from /*F*/ "lib" /*G*/;/*H*/ //I 862/*J*/export /*K*/ { /*L*/ F1 /*M*/ } /*N*/ from /*O*/ "lib" /*P*/;/*Q*/ //R 863`, 864 }, 865 libFile); 866 867 testOrganizeExports("SortTrivia", 868 { 869 path: "/test.ts", 870 content: ` 871/*A*/export /*B*/ * /*C*/ from /*D*/ "lib2" /*E*/;/*F*/ //G 872/*H*/export /*I*/ * /*J*/ from /*K*/ "lib1" /*L*/;/*M*/ //N 873`, 874 }, 875 { path: "/lib1.ts", content: "" }, 876 { path: "/lib2.ts", content: "" }); 877 878 testOrganizeExports("SortHeaderComment", 879 { 880 path: "/test.ts", 881 content: ` 882// Header 883export * from "lib2"; 884export * from "lib1"; 885`, 886 }, 887 { path: "/lib1.ts", content: "" }, 888 { path: "/lib2.ts", content: "" }); 889 890 testOrganizeExports("AmbientModule", 891 { 892 path: "/test.ts", 893 content: ` 894declare module "mod" { 895 export { F1 } from "lib"; 896 export * from "lib"; 897 export { F2 } from "lib"; 898} 899 `, 900 }, 901 libFile); 902 903 testOrganizeExports("TopLevelAndAmbientModule", 904 { 905 path: "/test.ts", 906 content: ` 907export { D } from "lib"; 908 909declare module "mod" { 910 export { F1 } from "lib"; 911 export * from "lib"; 912 export { F2 } from "lib"; 913} 914 915export { E } from "lib"; 916export * from "lib"; 917`, 918 }, 919 libFile); 920 }); 921 922 function testOrganizeExports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 923 testOrganizeImports(`${testName}.exports`, testFile, ...otherFiles); 924 } 925 926 function testOrganizeImports(testName: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 927 it(testName, () => runBaseline(`organizeImports/${testName}.ts`, testFile, ...otherFiles)); 928 } 929 930 function runBaseline(baselinePath: string, testFile: TestFSWithWatch.File, ...otherFiles: TestFSWithWatch.File[]) { 931 const { path: testPath, content: testContent } = testFile; 932 const languageService = makeLanguageService(testFile, ...otherFiles); 933 const changes = languageService.organizeImports({ type: "file", fileName: testPath }, testFormatSettings, emptyOptions); 934 assert.equal(changes.length, 1); 935 assert.equal(changes[0].fileName, testPath); 936 937 const newText = textChanges.applyChanges(testContent, changes[0].textChanges); 938 Harness.Baseline.runBaseline(baselinePath, [ 939 "// ==ORIGINAL==", 940 testContent, 941 "// ==ORGANIZED==", 942 newText, 943 ].join(newLineCharacter)); 944 } 945 946 function makeLanguageService(...files: TestFSWithWatch.File[]) { 947 const host = projectSystem.createServerHost(files); 948 const projectService = projectSystem.createProjectService(host, { useSingleInferredProject: true }); 949 projectService.setCompilerOptionsForInferredProjects({ jsx: files.some(f => f.path.endsWith("x")) ? JsxEmit.React : JsxEmit.None }); 950 files.forEach(f => projectService.openClientFile(f.path)); 951 return projectService.inferredProjects[0].getLanguageService(); 952 } 953 }); 954 955 function parseImports(...importStrings: string[]): readonly ImportDeclaration[] { 956 const sourceFile = createSourceFile("a.ts", importStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); 957 const imports = filter(sourceFile.statements, isImportDeclaration); 958 assert.equal(imports.length, importStrings.length); 959 return imports; 960 } 961 962 function parseExports(...exportStrings: string[]): readonly ExportDeclaration[] { 963 const sourceFile = createSourceFile("a.ts", exportStrings.join("\n"), ScriptTarget.ES2015, /*setParentNodes*/ true, ScriptKind.TS); 964 const exports = filter(sourceFile.statements, isExportDeclaration); 965 assert.equal(exports.length, exportStrings.length); 966 return exports; 967 } 968 969 function assertEqual(node1?: Node, node2?: Node) { 970 if (node1 === undefined) { 971 assert.isUndefined(node2); 972 return; 973 } 974 else if (node2 === undefined) { 975 assert.isUndefined(node1); // Guaranteed to fail 976 return; 977 } 978 979 assert.equal(node1.kind, node2.kind); 980 981 switch (node1.kind) { 982 case SyntaxKind.ImportDeclaration: 983 const decl1 = node1 as ImportDeclaration; 984 const decl2 = node2 as ImportDeclaration; 985 assertEqual(decl1.importClause, decl2.importClause); 986 assertEqual(decl1.moduleSpecifier, decl2.moduleSpecifier); 987 break; 988 case SyntaxKind.ImportClause: 989 const clause1 = node1 as ImportClause; 990 const clause2 = node2 as ImportClause; 991 assertEqual(clause1.name, clause2.name); 992 assertEqual(clause1.namedBindings, clause2.namedBindings); 993 break; 994 case SyntaxKind.NamespaceImport: 995 const nsi1 = node1 as NamespaceImport; 996 const nsi2 = node2 as NamespaceImport; 997 assertEqual(nsi1.name, nsi2.name); 998 break; 999 case SyntaxKind.NamedImports: 1000 const ni1 = node1 as NamedImports; 1001 const ni2 = node2 as NamedImports; 1002 assertListEqual(ni1.elements, ni2.elements); 1003 break; 1004 case SyntaxKind.ImportSpecifier: 1005 const is1 = node1 as ImportSpecifier; 1006 const is2 = node2 as ImportSpecifier; 1007 assertEqual(is1.name, is2.name); 1008 assertEqual(is1.propertyName, is2.propertyName); 1009 break; 1010 case SyntaxKind.ExportDeclaration: 1011 const ed1 = node1 as ExportDeclaration; 1012 const ed2 = node2 as ExportDeclaration; 1013 assertEqual(ed1.exportClause, ed2.exportClause); 1014 assertEqual(ed1.moduleSpecifier, ed2.moduleSpecifier); 1015 break; 1016 case SyntaxKind.NamedExports: 1017 const ne1 = node1 as NamedExports; 1018 const ne2 = node2 as NamedExports; 1019 assertListEqual(ne1.elements, ne2.elements); 1020 break; 1021 case SyntaxKind.ExportSpecifier: 1022 const es1 = node1 as ExportSpecifier; 1023 const es2 = node2 as ExportSpecifier; 1024 assertEqual(es1.name, es2.name); 1025 assertEqual(es1.propertyName, es2.propertyName); 1026 break; 1027 case SyntaxKind.Identifier: 1028 const id1 = node1 as Identifier; 1029 const id2 = node2 as Identifier; 1030 assert.equal(id1.text, id2.text); 1031 break; 1032 case SyntaxKind.StringLiteral: 1033 case SyntaxKind.NoSubstitutionTemplateLiteral: 1034 const sl1 = node1 as LiteralLikeNode; 1035 const sl2 = node2 as LiteralLikeNode; 1036 assert.equal(sl1.text, sl2.text); 1037 break; 1038 default: 1039 assert.equal(node1.getText(), node2.getText()); 1040 break; 1041 } 1042 } 1043 1044 function assertListEqual(list1: readonly Node[], list2: readonly Node[]) { 1045 if (list1 === undefined || list2 === undefined) { 1046 assert.isUndefined(list1); 1047 assert.isUndefined(list2); 1048 return; 1049 } 1050 1051 assert.equal(list1.length, list2.length); 1052 for (let i = 0; i < list1.length; i++) { 1053 assertEqual(list1[i], list2[i]); 1054 } 1055 } 1056 }); 1057} 1058