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
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, Replaces: sortIncludes(Style, Code, Ranges, FileName));
29 EXPECT_TRUE(static_cast<bool>(Sorted));
30 auto Formatted = applyAllReplacements(
31 Code: *Sorted, Replaces: reformat(Style, Code: *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(Language: FormatStyle::LK_JavaScript);
47};
48
49#define verifySort(...) _verifySort(__FILE__, __LINE__, __VA_ARGS__)
50
51TEST_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
64TEST_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
76TEST_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
86TEST_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
96TEST_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
111TEST_F(SortImportsTestJS, SeparateMainCodeBody) {
112 verifySort("import {sym} from 'a';"
113 "\n"
114 "let x = 1;",
115 "import {sym} from 'a'; let x = 1;");
116}
117
118TEST_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 */");
127 verifySort("import {sym} from 'a';\n"
128 "import {sym} from 'b';\n"
129 "\n"
130 "/** Comment on variable. */\n"
131 "const x = 1;",
132 "import {sym} from 'b';\n"
133 "import {sym} from 'a';\n"
134 "\n"
135 "/** Comment on variable. */\n"
136 "const x = 1;");
137}
138
139TEST_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';");
146}
147
148TEST_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';");
153}
154
155TEST_F(SortImportsTestJS, SortSymbols) {
156 verifySort("import {sym1, sym2 as a, sym3} from 'b';\n",
157 "import {sym2 as a, sym1, sym3} from 'b';");
158 verifySort("import {sym1 /* important! */, /*!*/ sym2 as a} from 'b';\n",
159 "import {/*!*/ sym2 as a, sym1 /* important! */} from 'b';");
160 verifySort("import {sym1, sym2} from 'b';\n", "import {\n"
161 " sym2 \n"
162 ",\n"
163 " sym1 \n"
164 "} from 'b';");
165}
166
167TEST_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;",
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;");
183}
184
185TEST_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;",
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;");
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';");
210}
211
212TEST_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';");
223}
224
225TEST_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
251TEST_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
264TEST_F(SortImportsTestJS, TrailingComma) {
265 verifySort("import {A, B,} from 'aa';\n", "import {B, A,} from 'aa';");
266}
267
268TEST_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
289TEST_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
306TEST_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';");
312}
313
314TEST_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;",
320 "import {X} from 'a';\n"
321 "import {Z} from 'z';\n"
322 "import {Y} from 'a';\n"
323 "\n"
324 "X + Y + Z;");
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';",
337 "import * as foo from 'foo';\n"
338 "import {A} from 'foo';");
339
340 // ignores default import
341 verifySort("import X from 'foo';\n"
342 "import {A} from 'foo';",
343 "import X from 'foo';\n"
344 "import {A} from 'foo';");
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;",
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;");
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';");
379}
380
381TEST_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",
386 "// clang-format off\n"
387 "import {B} from './b';\n"
388 "import {A} from './a';\n"
389 "// clang-format on");
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';");
407
408 // Boundary cases
409 verifySort("// clang-format on", "// clang-format on");
410 verifySort("// clang-format off", "// clang-format off");
411 verifySort("// clang-format on\n"
412 "// clang-format off",
413 "// clang-format on\n"
414 "// clang-format off");
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';");
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",
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");
436}
437
438TEST_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
449TEST_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);",
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);");
466}
467
468TEST_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';");
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;",
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;");
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};",
512 "export type A = B;\n"
513 "export {X};\n"
514 "export {Y};");
515}
516
517TEST_F(SortImportsTestJS, TemplateKeyword) {
518 // Reproduces issue where importing "template" disables imports sorting.
519 verifySort("import {template} from './a';\n"
520 "import {b} from './b';\n",
521 "import {b} from './b';\n"
522 "import {template} from './a';");
523}
524
525} // end namespace
526} // end namespace format
527} // end namespace clang
528

source code of clang/unittests/Format/SortImportsTestJS.cpp