xref: /llvm-project/clang/unittests/Format/SortImportsTestJS.cpp (revision 1c58208d899285318c89e069268145c85ec33368)
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:
sort(StringRef Code,unsigned Offset=0,unsigned Length=0)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 
_verifySort(const char * File,int Line,StringRef Expected,StringRef Code,unsigned Offset=0,unsigned Length=0)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 
TEST_F(SortImportsTestJS,AlreadySorted)50 TEST_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 
TEST_F(SortImportsTestJS,BasicSorting)63 TEST_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 
TEST_F(SortImportsTestJS,DefaultBinding)75 TEST_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 
TEST_F(SortImportsTestJS,DefaultAndNamedBinding)85 TEST_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 
TEST_F(SortImportsTestJS,WrappedImportStatements)95 TEST_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 
TEST_F(SortImportsTestJS,SeparateMainCodeBody)110 TEST_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 
TEST_F(SortImportsTestJS,Comments)117 TEST_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 
TEST_F(SortImportsTestJS,SortStar)138 TEST_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 
TEST_F(SortImportsTestJS,AliasesSymbols)147 TEST_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 
TEST_F(SortImportsTestJS,SortSymbols)154 TEST_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 
TEST_F(SortImportsTestJS,GroupImports)166 TEST_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 
TEST_F(SortImportsTestJS,Exports)184 TEST_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 
TEST_F(SortImportsTestJS,SideEffectImports)211 TEST_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 
TEST_F(SortImportsTestJS,AffectedRange)224 TEST_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 
TEST_F(SortImportsTestJS,SortingCanShrink)250 TEST_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 
TEST_F(SortImportsTestJS,TrailingComma)263 TEST_F(SortImportsTestJS, TrailingComma) {
264   verifySort("import {A, B,} from 'aa';\n", "import {B, A,} from 'aa';");
265 }
266 
TEST_F(SortImportsTestJS,SortCaseInsensitive)267 TEST_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 
TEST_F(SortImportsTestJS,SortMultiLine)288 TEST_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 
TEST_F(SortImportsTestJS,SortDefaultImports)305 TEST_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 
TEST_F(SortImportsTestJS,MergeImports)313 TEST_F(SortImportsTestJS, MergeImports) {
314   // basic operation
315   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 imports
330   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 import
340   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 comments
346   // 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 exports
363   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 exports
369   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 ones
373   verifySort("import './a';\n"
374              "\n"
375              "import {bar} from './a';\n",
376              "import {bar} from './a';\n"
377              "import './a';");
378 }
379 
TEST_F(SortImportsTestJS,RespectsClangFormatOff)380 TEST_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 cases
408   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 comment
423   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 imports
428                   // section from main code body
429              "// 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 
TEST_F(SortImportsTestJS,RespectsClangFormatOffInNamedImports)437 TEST_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 
TEST_F(SortImportsTestJS,ImportEqAliases)448 TEST_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 
TEST_F(SortImportsTestJS,ImportExportType)467 TEST_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 statement
483   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   // Merging
487   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 imports
498   verifySort("import type {A} from 'foo';\n", "import type {} from 'foo';\n"
499                                               "import type {A} from 'foo';");
500 
501   // Merging: exports
502   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 export
507   // 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 
TEST_F(SortImportsTestJS,TemplateKeyword)516 TEST_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 namespace
525 } // end namespace format
526 } // end namespace clang
527