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, 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 | |
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;" , |
115 | "import {sym} from 'a'; let x = 1;" ); |
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 */" ); |
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 | |
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';" ); |
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';" ); |
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';" ); |
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 | |
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;" , |
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 | |
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;" , |
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 | |
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';" ); |
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';" ); |
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';" ); |
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;" , |
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 | |
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" , |
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 | |
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);" , |
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 | |
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';" ); |
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 | |
517 | TEST_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 | |