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, 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(Language: FormatStyle::LK_JavaScript); |
46 | }; |
47 | |
48 | #define verifySort(...) _verifySort(__FILE__, __LINE__, __VA_ARGS__) |
49 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
263 | TEST_F(SortImportsTestJS, TrailingComma) { |
264 | verifySort("import {A, B,} from 'aa';\n" , "import {B, A,} from 'aa';" ); |
265 | } |
266 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |