1 //===- unittest/Format/SortImportsTestJS.cpp - JS import sort unit tests --===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "FormatTestUtils.h" 10 #include "clang/Format/Format.h" 11 #include "llvm/Support/Debug.h" 12 #include "gtest/gtest.h" 13 14 #define DEBUG_TYPE "format-test" 15 16 namespace clang { 17 namespace format { 18 namespace { 19 20 class SortImportsTestJS : public ::testing::Test { 21 protected: 22 std::string sort(StringRef Code, unsigned Offset = 0, unsigned Length = 0) { 23 StringRef FileName = "input.js"; 24 if (Length == 0U) 25 Length = Code.size() - Offset; 26 std::vector<tooling::Range> Ranges(1, tooling::Range(Offset, Length)); 27 auto Sorted = 28 applyAllReplacements(Code, sortIncludes(Style, Code, Ranges, FileName)); 29 EXPECT_TRUE(static_cast<bool>(Sorted)); 30 auto Formatted = applyAllReplacements( 31 *Sorted, reformat(Style, *Sorted, Ranges, FileName)); 32 EXPECT_TRUE(static_cast<bool>(Formatted)); 33 return *Formatted; 34 } 35 36 void _verifySort(const char *File, int Line, llvm::StringRef Expected, 37 llvm::StringRef Code, unsigned Offset = 0, 38 unsigned Length = 0) { 39 ::testing::ScopedTrace t(File, Line, ::testing::Message() << Code.str()); 40 std::string Result = sort(Code, Offset, Length); 41 EXPECT_EQ(Expected.str(), Result) << "Expected:\n" 42 << Expected << "\nActual:\n" 43 << Result; 44 } 45 46 FormatStyle Style = getGoogleStyle(FormatStyle::LK_JavaScript); 47 }; 48 49 #define verifySort(...) _verifySort(__FILE__, __LINE__, __VA_ARGS__) 50 51 TEST_F(SortImportsTestJS, AlreadySorted) { 52 verifySort("import {sym} from 'a';\n" 53 "import {sym} from 'b';\n" 54 "import {sym} from 'c';\n" 55 "\n" 56 "let x = 1;", 57 "import {sym} from 'a';\n" 58 "import {sym} from 'b';\n" 59 "import {sym} from 'c';\n" 60 "\n" 61 "let x = 1;"); 62 } 63 64 TEST_F(SortImportsTestJS, BasicSorting) { 65 verifySort("import {sym} from 'a';\n" 66 "import {sym} from 'b';\n" 67 "import {sym} from 'c';\n" 68 "\n" 69 "let x = 1;", 70 "import {sym} from 'a';\n" 71 "import {sym} from 'c';\n" 72 "import {sym} from 'b';\n" 73 "let x = 1;"); 74 } 75 76 TEST_F(SortImportsTestJS, DefaultBinding) { 77 verifySort("import A from 'a';\n" 78 "import B from 'b';\n" 79 "\n" 80 "let x = 1;", 81 "import B from 'b';\n" 82 "import A from 'a';\n" 83 "let x = 1;"); 84 } 85 86 TEST_F(SortImportsTestJS, DefaultAndNamedBinding) { 87 verifySort("import A, {a} from 'a';\n" 88 "import B, {b} from 'b';\n" 89 "\n" 90 "let x = 1;", 91 "import B, {b} from 'b';\n" 92 "import A, {a} from 'a';\n" 93 "let x = 1;"); 94 } 95 96 TEST_F(SortImportsTestJS, WrappedImportStatements) { 97 verifySort("import {sym1, sym2} from 'a';\n" 98 "import {sym} from 'b';\n" 99 "\n" 100 "1;", 101 "import\n" 102 " {sym}\n" 103 " from 'b';\n" 104 "import {\n" 105 " sym1,\n" 106 " sym2\n" 107 "} from 'a';\n" 108 "1;"); 109 } 110 111 TEST_F(SortImportsTestJS, SeparateMainCodeBody) { 112 verifySort("import {sym} from 'a';" 113 "\n" 114 "let x = 1;\n", 115 "import {sym} from 'a'; let x = 1;\n"); 116 } 117 118 TEST_F(SortImportsTestJS, Comments) { 119 verifySort("/** @fileoverview This is a great file. */\n" 120 "// A very important import follows.\n" 121 "import {sym} from 'a'; /* more comments */\n" 122 "import {sym} from 'b'; // from //foo:bar\n", 123 "/** @fileoverview This is a great file. */\n" 124 "import {sym} from 'b'; // from //foo:bar\n" 125 "// A very important import follows.\n" 126 "import {sym} from 'a'; /* more comments */\n"); 127 verifySort("import {sym} from 'a';\n" 128 "import {sym} from 'b';\n" 129 "\n" 130 "/** Comment on variable. */\n" 131 "const x = 1;\n", 132 "import {sym} from 'b';\n" 133 "import {sym} from 'a';\n" 134 "\n" 135 "/** Comment on variable. */\n" 136 "const x = 1;\n"); 137 } 138 139 TEST_F(SortImportsTestJS, SortStar) { 140 verifySort("import * as foo from 'a';\n" 141 "import {sym} from 'a';\n" 142 "import * as bar from 'b';\n", 143 "import {sym} from 'a';\n" 144 "import * as foo from 'a';\n" 145 "import * as bar from 'b';\n"); 146 } 147 148 TEST_F(SortImportsTestJS, AliasesSymbols) { 149 verifySort("import {sym1 as alias1} from 'b';\n" 150 "import {sym2 as alias2, sym3 as alias3} from 'c';\n", 151 "import {sym2 as alias2, sym3 as alias3} from 'c';\n" 152 "import {sym1 as alias1} from 'b';\n"); 153 } 154 155 TEST_F(SortImportsTestJS, SortSymbols) { 156 verifySort("import {sym1, sym2 as a, sym3} from 'b';\n", 157 "import {sym2 as a, sym1, sym3} from 'b';\n"); 158 verifySort("import {sym1 /* important! */, /*!*/ sym2 as a} from 'b';\n", 159 "import {/*!*/ sym2 as a, sym1 /* important! */} from 'b';\n"); 160 verifySort("import {sym1, sym2} from 'b';\n", "import {\n" 161 " sym2 \n" 162 ",\n" 163 " sym1 \n" 164 "} from 'b';\n"); 165 } 166 167 TEST_F(SortImportsTestJS, GroupImports) { 168 verifySort("import {a} from 'absolute';\n" 169 "\n" 170 "import {b} from '../parent';\n" 171 "import {b} from '../parent/nested';\n" 172 "\n" 173 "import {b} from './relative/path';\n" 174 "import {b} from './relative/path/nested';\n" 175 "\n" 176 "let x = 1;\n", 177 "import {b} from './relative/path/nested';\n" 178 "import {b} from './relative/path';\n" 179 "import {b} from '../parent/nested';\n" 180 "import {b} from '../parent';\n" 181 "import {a} from 'absolute';\n" 182 "let x = 1;\n"); 183 } 184 185 TEST_F(SortImportsTestJS, Exports) { 186 verifySort("import {S} from 'bpath';\n" 187 "\n" 188 "import {T} from './cpath';\n" 189 "\n" 190 "export {A, B} from 'apath';\n" 191 "export {P} from '../parent';\n" 192 "export {R} from './relative';\n" 193 "export {S};\n" 194 "\n" 195 "let x = 1;\n" 196 "export y = 1;\n", 197 "export {R} from './relative';\n" 198 "import {T} from './cpath';\n" 199 "export {S};\n" 200 "export {A, B} from 'apath';\n" 201 "import {S} from 'bpath';\n" 202 "export {P} from '../parent';\n" 203 "let x = 1;\n" 204 "export y = 1;\n"); 205 verifySort("import {S} from 'bpath';\n" 206 "\n" 207 "export {T} from 'epath';\n", 208 "export {T} from 'epath';\n" 209 "import {S} from 'bpath';\n"); 210 } 211 212 TEST_F(SortImportsTestJS, SideEffectImports) { 213 verifySort("import 'ZZside-effect';\n" 214 "import 'AAside-effect';\n" 215 "\n" 216 "import {A} from 'absolute';\n" 217 "\n" 218 "import {R} from './relative';\n", 219 "import {R} from './relative';\n" 220 "import 'ZZside-effect';\n" 221 "import {A} from 'absolute';\n" 222 "import 'AAside-effect';\n"); 223 } 224 225 TEST_F(SortImportsTestJS, AffectedRange) { 226 // Affected range inside of import statements. 227 verifySort("import {sym} from 'a';\n" 228 "import {sym} from 'b';\n" 229 "import {sym} from 'c';\n" 230 "\n" 231 "let x = 1;", 232 "import {sym} from 'c';\n" 233 "import {sym} from 'b';\n" 234 "import {sym} from 'a';\n" 235 "let x = 1;", 236 0, 30); 237 // Affected range outside of import statements. 238 verifySort("import {sym} from 'c';\n" 239 "import {sym} from 'b';\n" 240 "import {sym} from 'a';\n" 241 "\n" 242 "let x = 1;", 243 "import {sym} from 'c';\n" 244 "import {sym} from 'b';\n" 245 "import {sym} from 'a';\n" 246 "\n" 247 "let x = 1;", 248 70, 1); 249 } 250 251 TEST_F(SortImportsTestJS, SortingCanShrink) { 252 // Sort excluding a suffix. 253 verifySort("import {B} from 'a';\n" 254 "import {A} from 'b';\n" 255 "\n" 256 "1;", 257 "import {A} from 'b';\n" 258 "\n" 259 "import {B} from 'a';\n" 260 "\n" 261 "1;"); 262 } 263 264 TEST_F(SortImportsTestJS, TrailingComma) { 265 verifySort("import {A, B,} from 'aa';\n", "import {B, A,} from 'aa';\n"); 266 } 267 268 TEST_F(SortImportsTestJS, SortCaseInsensitive) { 269 verifySort("import {A} from 'aa';\n" 270 "import {A} from 'Ab';\n" 271 "import {A} from 'b';\n" 272 "import {A} from 'Bc';\n" 273 "\n" 274 "1;", 275 "import {A} from 'b';\n" 276 "import {A} from 'Bc';\n" 277 "import {A} from 'Ab';\n" 278 "import {A} from 'aa';\n" 279 "\n" 280 "1;"); 281 verifySort("import {aa, Ab, b, Bc} from 'x';\n" 282 "\n" 283 "1;", 284 "import {b, Bc, Ab, aa} from 'x';\n" 285 "\n" 286 "1;"); 287 } 288 289 TEST_F(SortImportsTestJS, SortMultiLine) { 290 // Reproduces issue where multi-line import was not parsed correctly. 291 verifySort("import {A} from 'a';\n" 292 "import {A} from 'b';\n" 293 "\n" 294 "1;", 295 "import\n" 296 "{\n" 297 "A\n" 298 "}\n" 299 "from\n" 300 "'b';\n" 301 "import {A} from 'a';\n" 302 "\n" 303 "1;"); 304 } 305 306 TEST_F(SortImportsTestJS, SortDefaultImports) { 307 // Reproduces issue where multi-line import was not parsed correctly. 308 verifySort("import {A} from 'a';\n" 309 "import {default as B} from 'b';\n", 310 "import {default as B} from 'b';\n" 311 "import {A} from 'a';\n"); 312 } 313 314 TEST_F(SortImportsTestJS, MergeImports) { 315 // basic operation 316 verifySort("import {X, Y} from 'a';\n" 317 "import {Z} from 'z';\n" 318 "\n" 319 "X + Y + Z;\n", 320 "import {X} from 'a';\n" 321 "import {Z} from 'z';\n" 322 "import {Y} from 'a';\n" 323 "\n" 324 "X + Y + Z;\n"); 325 326 // merge only, no resorting. 327 verifySort("import {A, B} from 'foo';\n", "import {A} from 'foo';\n" 328 "import {B} from 'foo';"); 329 330 // empty imports 331 verifySort("import {A} from 'foo';\n", "import {} from 'foo';\n" 332 "import {A} from 'foo';"); 333 334 // ignores import * 335 verifySort("import * as foo from 'foo';\n" 336 "import {A} from 'foo';\n", 337 "import * as foo from 'foo';\n" 338 "import {A} from 'foo';\n"); 339 340 // ignores default import 341 verifySort("import X from 'foo';\n" 342 "import {A} from 'foo';\n", 343 "import X from 'foo';\n" 344 "import {A} from 'foo';\n"); 345 346 // keeps comments 347 // known issue: loses the 'also a' comment. 348 verifySort("// a\n" 349 "import {/* x */ X, /* y */ Y} from 'a';\n" 350 "// z\n" 351 "import {Z} from 'z';\n" 352 "\n" 353 "X + Y + Z;\n", 354 "// a\n" 355 "import {/* y */ Y} from 'a';\n" 356 "// z\n" 357 "import {Z} from 'z';\n" 358 "// also a\n" 359 "import {/* x */ X} from 'a';\n" 360 "\n" 361 "X + Y + Z;\n"); 362 363 // do not merge imports and exports 364 verifySort("import {A} from 'foo';\n" 365 "\n" 366 "export {B} from 'foo';\n", 367 "import {A} from 'foo';\n" 368 "export {B} from 'foo';"); 369 // do merge exports 370 verifySort("export {A, B} from 'foo';\n", "export {A} from 'foo';\n" 371 "export {B} from 'foo';"); 372 373 // do not merge side effect imports with named ones 374 verifySort("import './a';\n" 375 "\n" 376 "import {bar} from './a';\n", 377 "import {bar} from './a';\n" 378 "import './a';\n"); 379 } 380 381 TEST_F(SortImportsTestJS, RespectsClangFormatOff) { 382 verifySort("// clang-format off\n" 383 "import {B} from './b';\n" 384 "import {A} from './a';\n" 385 "// clang-format on\n", 386 "// clang-format off\n" 387 "import {B} from './b';\n" 388 "import {A} from './a';\n" 389 "// clang-format on\n"); 390 391 verifySort("import {A} from './sorted1_a';\n" 392 "import {B} from './sorted1_b';\n" 393 "// clang-format off\n" 394 "import {B} from './unsorted_b';\n" 395 "import {A} from './unsorted_a';\n" 396 "// clang-format on\n" 397 "import {A} from './sorted2_a';\n" 398 "import {B} from './sorted2_b';\n", 399 "import {B} from './sorted1_b';\n" 400 "import {A} from './sorted1_a';\n" 401 "// clang-format off\n" 402 "import {B} from './unsorted_b';\n" 403 "import {A} from './unsorted_a';\n" 404 "// clang-format on\n" 405 "import {B} from './sorted2_b';\n" 406 "import {A} from './sorted2_a';\n"); 407 408 // Boundary cases 409 verifySort("// clang-format on\n", "// clang-format on\n"); 410 verifySort("// clang-format off\n", "// clang-format off\n"); 411 verifySort("// clang-format on\n" 412 "// clang-format off\n", 413 "// clang-format on\n" 414 "// clang-format off\n"); 415 verifySort("// clang-format off\n" 416 "// clang-format on\n" 417 "import {A} from './a';\n" 418 "import {B} from './b';\n", 419 "// clang-format off\n" 420 "// clang-format on\n" 421 "import {B} from './b';\n" 422 "import {A} from './a';\n"); 423 // section ends with comment 424 verifySort("// clang-format on\n" 425 "import {A} from './a';\n" 426 "import {B} from './b';\n" 427 "import {C} from './c';\n" 428 "\n" // inserted empty line is working as intended: splits imports 429 // section from main code body 430 "// clang-format off\n", 431 "// clang-format on\n" 432 "import {C} from './c';\n" 433 "import {B} from './b';\n" 434 "import {A} from './a';\n" 435 "// clang-format off\n"); 436 } 437 438 TEST_F(SortImportsTestJS, RespectsClangFormatOffInNamedImports) { 439 verifySort("// clang-format off\n" 440 "import {B, A} from './b';\n" 441 "// clang-format on\n" 442 "const x = 1;", 443 "// clang-format off\n" 444 "import {B, A} from './b';\n" 445 "// clang-format on\n" 446 "const x = 1;"); 447 } 448 449 TEST_F(SortImportsTestJS, ImportEqAliases) { 450 verifySort("import {B} from 'bar';\n" 451 "import {A} from 'foo';\n" 452 "\n" 453 "import Z = A.C;\n" 454 "import Y = B.C.Z;\n" 455 "\n" 456 "export {Z};\n" 457 "\n" 458 "console.log(Z);\n", 459 "import {A} from 'foo';\n" 460 "import Z = A.C;\n" 461 "export {Z};\n" 462 "import {B} from 'bar';\n" 463 "import Y = B.C.Z;\n" 464 "\n" 465 "console.log(Z);\n"); 466 } 467 468 TEST_F(SortImportsTestJS, ImportExportType) { 469 verifySort("import type {sym} from 'a';\n" 470 "import {type sym} from 'b';\n" 471 "import {sym} from 'c';\n" 472 "import type sym from 'd';\n" 473 "import type * as sym from 'e';\n" 474 "\n" 475 "let x = 1;", 476 "import {sym} from 'c';\n" 477 "import type {sym} from 'a';\n" 478 "import type * as sym from 'e';\n" 479 "import type sym from 'd';\n" 480 "import {type sym} from 'b';\n" 481 "let x = 1;"); 482 483 // Symbols within import statement 484 verifySort("import {type sym1, type sym2 as a, sym3} from 'b';\n", 485 "import {type sym2 as a, type sym1, sym3} from 'b';\n"); 486 487 // Merging 488 verifySort("import {X, type Z} from 'a';\n" 489 "import type {Y} from 'a';\n" 490 "\n" 491 "X + Y + Z;\n", 492 "import {X} from 'a';\n" 493 "import {type Z} from 'a';\n" 494 "import type {Y} from 'a';\n" 495 "\n" 496 "X + Y + Z;\n"); 497 498 // Merging: empty imports 499 verifySort("import type {A} from 'foo';\n", "import type {} from 'foo';\n" 500 "import type {A} from 'foo';"); 501 502 // Merging: exports 503 verifySort("export {A, type B} from 'foo';\n", 504 "export {A} from 'foo';\n" 505 "export {type B} from 'foo';"); 506 507 // `export type X = Y;` should terminate import sorting. The following export 508 // statements should therefore not merge. 509 verifySort("export type A = B;\n" 510 "export {X};\n" 511 "export {Y};\n", 512 "export type A = B;\n" 513 "export {X};\n" 514 "export {Y};\n"); 515 } 516 517 } // end namespace 518 } // end namespace format 519 } // end namespace clang 520