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(llvm::StringRef Expected, llvm::StringRef Code, 37 unsigned Offset = 0, unsigned Length = 0) { 38 std::string Result = sort(Code, Offset, Length); 39 EXPECT_EQ(Expected.str(), Result) << "Expected:\n" 40 << Expected << "\nActual:\n" 41 << Result; 42 } 43 44 FormatStyle Style = getGoogleStyle(FormatStyle::LK_JavaScript); 45 }; 46 47 TEST_F(SortImportsTestJS, AlreadySorted) { 48 verifySort("import {sym} from 'a';\n" 49 "import {sym} from 'b';\n" 50 "import {sym} from 'c';\n" 51 "\n" 52 "let x = 1;", 53 "import {sym} from 'a';\n" 54 "import {sym} from 'b';\n" 55 "import {sym} from 'c';\n" 56 "\n" 57 "let x = 1;"); 58 } 59 60 TEST_F(SortImportsTestJS, BasicSorting) { 61 verifySort("import {sym} from 'a';\n" 62 "import {sym} from 'b';\n" 63 "import {sym} from 'c';\n" 64 "\n" 65 "let x = 1;", 66 "import {sym} from 'a';\n" 67 "import {sym} from 'c';\n" 68 "import {sym} from 'b';\n" 69 "let x = 1;"); 70 } 71 72 TEST_F(SortImportsTestJS, DefaultBinding) { 73 verifySort("import A from 'a';\n" 74 "import B from 'b';\n" 75 "\n" 76 "let x = 1;", 77 "import B from 'b';\n" 78 "import A from 'a';\n" 79 "let x = 1;"); 80 } 81 82 TEST_F(SortImportsTestJS, DefaultAndNamedBinding) { 83 verifySort("import A, {a} from 'a';\n" 84 "import B, {b} from 'b';\n" 85 "\n" 86 "let x = 1;", 87 "import B, {b} from 'b';\n" 88 "import A, {a} from 'a';\n" 89 "let x = 1;"); 90 } 91 92 TEST_F(SortImportsTestJS, WrappedImportStatements) { 93 verifySort("import {sym1, sym2} from 'a';\n" 94 "import {sym} from 'b';\n" 95 "\n" 96 "1;", 97 "import\n" 98 " {sym}\n" 99 " from 'b';\n" 100 "import {\n" 101 " sym1,\n" 102 " sym2\n" 103 "} from 'a';\n" 104 "1;"); 105 } 106 107 TEST_F(SortImportsTestJS, SeparateMainCodeBody) { 108 verifySort("import {sym} from 'a';" 109 "\n" 110 "let x = 1;\n", 111 "import {sym} from 'a'; let x = 1;\n"); 112 } 113 114 TEST_F(SortImportsTestJS, Comments) { 115 verifySort("/** @fileoverview This is a great file. */\n" 116 "// A very important import follows.\n" 117 "import {sym} from 'a'; /* more comments */\n" 118 "import {sym} from 'b'; // from //foo:bar\n", 119 "/** @fileoverview This is a great file. */\n" 120 "import {sym} from 'b'; // from //foo:bar\n" 121 "// A very important import follows.\n" 122 "import {sym} from 'a'; /* more comments */\n"); 123 verifySort("import {sym} from 'a';\n" 124 "import {sym} from 'b';\n" 125 "\n" 126 "/** Comment on variable. */\n" 127 "const x = 1;\n", 128 "import {sym} from 'b';\n" 129 "import {sym} from 'a';\n" 130 "\n" 131 "/** Comment on variable. */\n" 132 "const x = 1;\n"); 133 } 134 135 TEST_F(SortImportsTestJS, SortStar) { 136 verifySort("import * as foo from 'a';\n" 137 "import {sym} from 'a';\n" 138 "import * as bar from 'b';\n", 139 "import {sym} from 'a';\n" 140 "import * as foo from 'a';\n" 141 "import * as bar from 'b';\n"); 142 } 143 144 TEST_F(SortImportsTestJS, AliasesSymbols) { 145 verifySort("import {sym1 as alias1} from 'b';\n" 146 "import {sym2 as alias2, sym3 as alias3} from 'c';\n", 147 "import {sym2 as alias2, sym3 as alias3} from 'c';\n" 148 "import {sym1 as alias1} from 'b';\n"); 149 } 150 151 TEST_F(SortImportsTestJS, SortSymbols) { 152 verifySort("import {sym1, sym2 as a, sym3} from 'b';\n", 153 "import {sym2 as a, sym1, sym3} from 'b';\n"); 154 verifySort("import {sym1 /* important! */, /*!*/ sym2 as a} from 'b';\n", 155 "import {/*!*/ sym2 as a, sym1 /* important! */} from 'b';\n"); 156 verifySort("import {sym1, sym2} from 'b';\n", "import {\n" 157 " sym2 \n" 158 ",\n" 159 " sym1 \n" 160 "} from 'b';\n"); 161 } 162 163 TEST_F(SortImportsTestJS, GroupImports) { 164 verifySort("import {a} from 'absolute';\n" 165 "\n" 166 "import {b} from '../parent';\n" 167 "import {b} from '../parent/nested';\n" 168 "\n" 169 "import {b} from './relative/path';\n" 170 "import {b} from './relative/path/nested';\n" 171 "\n" 172 "let x = 1;\n", 173 "import {b} from './relative/path/nested';\n" 174 "import {b} from './relative/path';\n" 175 "import {b} from '../parent/nested';\n" 176 "import {b} from '../parent';\n" 177 "import {a} from 'absolute';\n" 178 "let x = 1;\n"); 179 } 180 181 TEST_F(SortImportsTestJS, Exports) { 182 verifySort("import {S} from 'bpath';\n" 183 "\n" 184 "import {T} from './cpath';\n" 185 "\n" 186 "export {A, B} from 'apath';\n" 187 "export {P} from '../parent';\n" 188 "export {R} from './relative';\n" 189 "export {S};\n" 190 "\n" 191 "let x = 1;\n" 192 "export y = 1;\n", 193 "export {R} from './relative';\n" 194 "import {T} from './cpath';\n" 195 "export {S};\n" 196 "export {A, B} from 'apath';\n" 197 "import {S} from 'bpath';\n" 198 "export {P} from '../parent';\n" 199 "let x = 1;\n" 200 "export y = 1;\n"); 201 verifySort("import {S} from 'bpath';\n" 202 "\n" 203 "export {T} from 'epath';\n", 204 "export {T} from 'epath';\n" 205 "import {S} from 'bpath';\n"); 206 } 207 208 TEST_F(SortImportsTestJS, SideEffectImports) { 209 verifySort("import 'ZZside-effect';\n" 210 "import 'AAside-effect';\n" 211 "\n" 212 "import {A} from 'absolute';\n" 213 "\n" 214 "import {R} from './relative';\n", 215 "import {R} from './relative';\n" 216 "import 'ZZside-effect';\n" 217 "import {A} from 'absolute';\n" 218 "import 'AAside-effect';\n"); 219 } 220 221 TEST_F(SortImportsTestJS, AffectedRange) { 222 // Affected range inside of import statements. 223 verifySort("import {sym} from 'a';\n" 224 "import {sym} from 'b';\n" 225 "import {sym} from 'c';\n" 226 "\n" 227 "let x = 1;", 228 "import {sym} from 'c';\n" 229 "import {sym} from 'b';\n" 230 "import {sym} from 'a';\n" 231 "let x = 1;", 232 0, 30); 233 // Affected range outside of import statements. 234 verifySort("import {sym} from 'c';\n" 235 "import {sym} from 'b';\n" 236 "import {sym} from 'a';\n" 237 "\n" 238 "let x = 1;", 239 "import {sym} from 'c';\n" 240 "import {sym} from 'b';\n" 241 "import {sym} from 'a';\n" 242 "\n" 243 "let x = 1;", 244 70, 1); 245 } 246 247 TEST_F(SortImportsTestJS, SortingCanShrink) { 248 // Sort excluding a suffix. 249 verifySort("import {B} from 'a';\n" 250 "import {A} from 'b';\n" 251 "\n" 252 "1;", 253 "import {A} from 'b';\n" 254 "\n" 255 "import {B} from 'a';\n" 256 "\n" 257 "1;"); 258 } 259 260 TEST_F(SortImportsTestJS, TrailingComma) { 261 verifySort("import {A, B,} from 'aa';\n", "import {B, A,} from 'aa';\n"); 262 } 263 264 TEST_F(SortImportsTestJS, SortCaseInsensitive) { 265 verifySort("import {A} from 'aa';\n" 266 "import {A} from 'Ab';\n" 267 "import {A} from 'b';\n" 268 "import {A} from 'Bc';\n" 269 "\n" 270 "1;", 271 "import {A} from 'b';\n" 272 "import {A} from 'Bc';\n" 273 "import {A} from 'Ab';\n" 274 "import {A} from 'aa';\n" 275 "\n" 276 "1;"); 277 verifySort("import {aa, Ab, b, Bc} from 'x';\n" 278 "\n" 279 "1;", 280 "import {b, Bc, Ab, aa} from 'x';\n" 281 "\n" 282 "1;"); 283 } 284 285 TEST_F(SortImportsTestJS, SortMultiLine) { 286 // Reproduces issue where multi-line import was not parsed correctly. 287 verifySort("import {A} from 'a';\n" 288 "import {A} from 'b';\n" 289 "\n" 290 "1;", 291 "import\n" 292 "{\n" 293 "A\n" 294 "}\n" 295 "from\n" 296 "'b';\n" 297 "import {A} from 'a';\n" 298 "\n" 299 "1;"); 300 } 301 302 TEST_F(SortImportsTestJS, SortDefaultImports) { 303 // Reproduces issue where multi-line import was not parsed correctly. 304 verifySort("import {A} from 'a';\n" 305 "import {default as B} from 'b';\n", 306 "import {default as B} from 'b';\n" 307 "import {A} from 'a';\n"); 308 } 309 310 TEST_F(SortImportsTestJS, MergeImports) { 311 // basic operation 312 verifySort("import {X, Y} from 'a';\n" 313 "import {Z} from 'z';\n" 314 "\n" 315 "X + Y + Z;\n", 316 "import {X} from 'a';\n" 317 "import {Z} from 'z';\n" 318 "import {Y} from 'a';\n" 319 "\n" 320 "X + Y + Z;\n"); 321 322 // merge only, no resorting. 323 verifySort("import {A, B} from 'foo';\n", "import {A} from 'foo';\n" 324 "import {B} from 'foo';"); 325 326 // empty imports 327 verifySort("import {A} from 'foo';\n", "import {} from 'foo';\n" 328 "import {A} from 'foo';"); 329 330 // ignores import * 331 verifySort("import * as foo from 'foo';\n" 332 "import {A} from 'foo';\n", 333 "import * as foo from 'foo';\n" 334 "import {A} from 'foo';\n"); 335 336 // ignores default import 337 verifySort("import X from 'foo';\n" 338 "import {A} from 'foo';\n", 339 "import X from 'foo';\n" 340 "import {A} from 'foo';\n"); 341 342 // keeps comments 343 // known issue: loses the 'also a' comment. 344 verifySort("// a\n" 345 "import {/* x */ X, /* y */ Y} from 'a';\n" 346 "// z\n" 347 "import {Z} from 'z';\n" 348 "\n" 349 "X + Y + Z;\n", 350 "// a\n" 351 "import {/* y */ Y} from 'a';\n" 352 "// z\n" 353 "import {Z} from 'z';\n" 354 "// also a\n" 355 "import {/* x */ X} from 'a';\n" 356 "\n" 357 "X + Y + Z;\n"); 358 359 // do not merge imports and exports 360 verifySort("import {A} from 'foo';\n" 361 "\n" 362 "export {B} from 'foo';\n", 363 "import {A} from 'foo';\n" 364 "export {B} from 'foo';"); 365 // do merge exports 366 verifySort("export {A, B} from 'foo';\n", "export {A} from 'foo';\n" 367 "export {B} from 'foo';"); 368 369 // do not merge side effect imports with named ones 370 verifySort("import './a';\n" 371 "\n" 372 "import {bar} from './a';\n", 373 "import {bar} from './a';\n" 374 "import './a';\n"); 375 } 376 377 TEST_F(SortImportsTestJS, RespectsClangFormatOff) { 378 verifySort("// clang-format off\n" 379 "import {B} from './b';\n" 380 "import {A} from './a';\n" 381 "// clang-format on\n", 382 "// clang-format off\n" 383 "import {B} from './b';\n" 384 "import {A} from './a';\n" 385 "// clang-format on\n"); 386 387 verifySort("import {A} from './sorted1_a';\n" 388 "import {B} from './sorted1_b';\n" 389 "// clang-format off\n" 390 "import {B} from './unsorted_b';\n" 391 "import {A} from './unsorted_a';\n" 392 "// clang-format on\n" 393 "import {A} from './sorted2_a';\n" 394 "import {B} from './sorted2_b';\n", 395 "import {B} from './sorted1_b';\n" 396 "import {A} from './sorted1_a';\n" 397 "// clang-format off\n" 398 "import {B} from './unsorted_b';\n" 399 "import {A} from './unsorted_a';\n" 400 "// clang-format on\n" 401 "import {B} from './sorted2_b';\n" 402 "import {A} from './sorted2_a';\n"); 403 404 // Boundary cases 405 verifySort("// clang-format on\n", "// clang-format on\n"); 406 verifySort("// clang-format off\n", "// clang-format off\n"); 407 verifySort("// clang-format on\n" 408 "// clang-format off\n", 409 "// clang-format on\n" 410 "// clang-format off\n"); 411 verifySort("// clang-format off\n" 412 "// clang-format on\n" 413 "import {A} from './a';\n" 414 "import {B} from './b';\n", 415 "// clang-format off\n" 416 "// clang-format on\n" 417 "import {B} from './b';\n" 418 "import {A} from './a';\n"); 419 // section ends with comment 420 verifySort("// clang-format on\n" 421 "import {A} from './a';\n" 422 "import {B} from './b';\n" 423 "import {C} from './c';\n" 424 "\n" // inserted empty line is working as intended: splits imports 425 // section from main code body 426 "// clang-format off\n", 427 "// clang-format on\n" 428 "import {C} from './c';\n" 429 "import {B} from './b';\n" 430 "import {A} from './a';\n" 431 "// clang-format off\n"); 432 } 433 434 } // end namespace 435 } // end namespace format 436 } // end namespace clang 437