xref: /llvm-project/clang/unittests/Format/SortImportsTestJS.cpp (revision e1f34b735b669c5978bfdead874ee59d33f99eb0)
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