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