19
19
#include " clang/Basic/LLVM.h"
20
20
#include " clang/Basic/SourceLocation.h"
21
21
#include " clang/Basic/SourceManager.h"
22
+ #include " clang/Basic/TokenKinds.h"
22
23
#include " clang/Format/Format.h"
23
24
#include " llvm/ADT/STLExtras.h"
24
25
#include " llvm/ADT/SmallVector.h"
@@ -69,6 +70,7 @@ struct JsImportedSymbol {
69
70
// This struct represents both exports and imports to build up the information
70
71
// required for sorting module references.
71
72
struct JsModuleReference {
73
+ bool FormattingOff = false ;
72
74
bool IsExport = false ;
73
75
// Module references are sorted into these categories, in order.
74
76
enum ReferenceCategory {
@@ -146,57 +148,51 @@ class JavaScriptImportSorter : public TokenAnalyzer {
146
148
if (References.empty ())
147
149
return {Result, 0 };
148
150
149
- SmallVector<unsigned , 16 > Indices;
150
- for (unsigned i = 0 , e = References.size (); i != e; ++i)
151
- Indices.push_back (i);
152
- llvm::stable_sort (Indices, [&](unsigned LHSI, unsigned RHSI) {
153
- return References[LHSI] < References[RHSI];
154
- });
155
- bool ReferencesInOrder = llvm::is_sorted (Indices);
151
+ // The text range of all parsed imports, to be replaced later.
152
+ SourceRange InsertionPoint = References[0 ].Range ;
153
+ InsertionPoint.setEnd (References[References.size () - 1 ].Range .getEnd ());
156
154
157
- mergeModuleReferences ( References, Indices );
155
+ References = sortModuleReferences (References );
158
156
159
157
std::string ReferencesText;
160
- bool SymbolsInOrder = true ;
161
- for (unsigned i = 0 , e = Indices.size (); i != e; ++i) {
162
- JsModuleReference Reference = References[Indices[i]];
163
- if (appendReference (ReferencesText, Reference))
164
- SymbolsInOrder = false ;
165
- if (i + 1 < e) {
158
+ for (unsigned I = 0 , E = References.size (); I != E; ++I) {
159
+ JsModuleReference Reference = References[I];
160
+ appendReference (ReferencesText, Reference);
161
+ if (I + 1 < E) {
166
162
// Insert breaks between imports and exports.
167
163
ReferencesText += " \n " ;
168
164
// Separate imports groups with two line breaks, but keep all exports
169
165
// in a single group.
170
166
if (!Reference.IsExport &&
171
- (Reference.IsExport != References[Indices[i + 1 ] ].IsExport ||
172
- Reference.Category != References[Indices[i + 1 ] ].Category ))
167
+ (Reference.IsExport != References[I + 1 ].IsExport ||
168
+ Reference.Category != References[I + 1 ].Category ))
173
169
ReferencesText += " \n " ;
174
170
}
175
171
}
176
- if (ReferencesInOrder && SymbolsInOrder)
172
+ llvm::StringRef PreviousText = getSourceText (InsertionPoint);
173
+ if (ReferencesText == PreviousText)
177
174
return {Result, 0 };
178
175
179
- SourceRange InsertionPoint = References[0 ].Range ;
180
- InsertionPoint.setEnd (References[References.size () - 1 ].Range .getEnd ());
181
-
182
176
// The loop above might collapse previously existing line breaks between
183
177
// import blocks, and thus shrink the file. SortIncludes must not shrink
184
178
// overall source length as there is currently no re-calculation of ranges
185
179
// after applying source sorting.
186
180
// This loop just backfills trailing spaces after the imports, which are
187
181
// harmless and will be stripped by the subsequent formatting pass.
188
182
// FIXME: A better long term fix is to re-calculate Ranges after sorting.
189
- unsigned PreviousSize = getSourceText (InsertionPoint) .size ();
183
+ unsigned PreviousSize = PreviousText .size ();
190
184
while (ReferencesText.size () < PreviousSize) {
191
185
ReferencesText += " " ;
192
186
}
193
187
194
188
// Separate references from the main code body of the file.
195
- if (FirstNonImportLine && FirstNonImportLine->First ->NewlinesBefore < 2 )
189
+ if (FirstNonImportLine && FirstNonImportLine->First ->NewlinesBefore < 2 &&
190
+ !(FirstNonImportLine->First ->is (tok::comment) &&
191
+ FirstNonImportLine->First ->TokenText .trim () == " // clang-format on" ))
196
192
ReferencesText += " \n " ;
197
193
198
194
LLVM_DEBUG (llvm::dbgs () << " Replacing imports:\n "
199
- << getSourceText (InsertionPoint) << " \n with:\n "
195
+ << PreviousText << " \n with:\n "
200
196
<< ReferencesText << " \n " );
201
197
auto Err = Result.add (tooling::Replacement (
202
198
Env.getSourceManager (), CharSourceRange::getCharRange (InsertionPoint),
@@ -248,23 +244,53 @@ class JavaScriptImportSorter : public TokenAnalyzer {
248
244
SM.getFileOffset (End) - SM.getFileOffset (Begin));
249
245
}
250
246
247
+ // Sorts the given module references.
248
+ // Imports can have formatting disabled (FormattingOff), so the code below
249
+ // skips runs of "no-formatting" module references, and sorts/merges the
250
+ // references that have formatting enabled in individual chunks.
251
+ SmallVector<JsModuleReference, 16 >
252
+ sortModuleReferences (const SmallVector<JsModuleReference, 16 > &References) {
253
+ // Sort module references.
254
+ // Imports can have formatting disabled (FormattingOff), so the code below
255
+ // skips runs of "no-formatting" module references, and sorts other
256
+ // references per group.
257
+ const auto *Start = References.begin ();
258
+ SmallVector<JsModuleReference, 16 > ReferencesSorted;
259
+ while (Start != References.end ()) {
260
+ while (Start != References.end () && Start->FormattingOff ) {
261
+ // Skip over all imports w/ disabled formatting.
262
+ ReferencesSorted.push_back (*Start);
263
+ Start++;
264
+ }
265
+ SmallVector<JsModuleReference, 16 > SortChunk;
266
+ while (Start != References.end () && !Start->FormattingOff ) {
267
+ // Skip over all imports w/ disabled formatting.
268
+ SortChunk.push_back (*Start);
269
+ Start++;
270
+ }
271
+ llvm::stable_sort (SortChunk);
272
+ mergeModuleReferences (SortChunk);
273
+ ReferencesSorted.insert (ReferencesSorted.end (), SortChunk.begin (),
274
+ SortChunk.end ());
275
+ }
276
+ return ReferencesSorted;
277
+ }
278
+
251
279
// Merge module references.
252
280
// After sorting, find all references that import named symbols from the
253
281
// same URL and merge their names. E.g.
254
282
// import {X} from 'a';
255
283
// import {Y} from 'a';
256
284
// should be rewritten to:
257
285
// import {X, Y} from 'a';
258
- // Note: this modifies the passed in ``Indices`` vector (by removing no longer
259
- // needed references), but not ``References``.
260
- // ``JsModuleReference``s that get merged have the ``SymbolsMerged`` flag
261
- // flipped to true.
262
- void mergeModuleReferences (SmallVector<JsModuleReference, 16 > &References,
263
- SmallVector<unsigned , 16 > &Indices) {
264
- JsModuleReference *PreviousReference = &References[Indices[0 ]];
265
- auto *It = std::next (Indices.begin ());
266
- while (It != std::end (Indices)) {
267
- JsModuleReference *Reference = &References[*It];
286
+ // Note: this modifies the passed in ``References`` vector (by removing no
287
+ // longer needed references).
288
+ void mergeModuleReferences (SmallVector<JsModuleReference, 16 > &References) {
289
+ if (References.empty ())
290
+ return ;
291
+ JsModuleReference *PreviousReference = References.begin ();
292
+ auto *Reference = std::next (References.begin ());
293
+ while (Reference != References.end ()) {
268
294
// Skip:
269
295
// import 'foo';
270
296
// import * as foo from 'foo'; on either previous or this.
@@ -278,20 +304,19 @@ class JavaScriptImportSorter : public TokenAnalyzer {
278
304
!Reference->DefaultImport .empty () || Reference->Symbols .empty () ||
279
305
PreviousReference->URL != Reference->URL ) {
280
306
PreviousReference = Reference;
281
- ++It ;
307
+ ++Reference ;
282
308
continue ;
283
309
}
284
310
// Merge symbols from identical imports.
285
311
PreviousReference->Symbols .append (Reference->Symbols );
286
312
PreviousReference->SymbolsMerged = true ;
287
313
// Remove the merged import.
288
- It = Indices .erase (It );
314
+ Reference = References .erase (Reference );
289
315
}
290
316
}
291
317
292
- // Appends ``Reference`` to ``Buffer``, returning true if text within the
293
- // ``Reference`` changed (e.g. symbol order).
294
- bool appendReference (std::string &Buffer, JsModuleReference &Reference) {
318
+ // Appends ``Reference`` to ``Buffer``.
319
+ void appendReference (std::string &Buffer, JsModuleReference &Reference) {
295
320
// Sort the individual symbols within the import.
296
321
// E.g. `import {b, a} from 'x';` -> `import {a, b} from 'x';`
297
322
SmallVector<JsImportedSymbol, 1 > Symbols = Reference.Symbols ;
@@ -303,7 +328,7 @@ class JavaScriptImportSorter : public TokenAnalyzer {
303
328
// Symbols didn't change, just emit the entire module reference.
304
329
StringRef ReferenceStmt = getSourceText (Reference.Range );
305
330
Buffer += ReferenceStmt;
306
- return false ;
331
+ return ;
307
332
}
308
333
// Stitch together the module reference start...
309
334
Buffer += getSourceText (Reference.Range .getBegin (), Reference.SymbolsStart );
@@ -315,7 +340,6 @@ class JavaScriptImportSorter : public TokenAnalyzer {
315
340
}
316
341
// ... followed by the module reference end.
317
342
Buffer += getSourceText (Reference.SymbolsEnd , Reference.Range .getEnd ());
318
- return true ;
319
343
}
320
344
321
345
// Parses module references in the given lines. Returns the module references,
@@ -328,9 +352,30 @@ class JavaScriptImportSorter : public TokenAnalyzer {
328
352
SourceLocation Start;
329
353
AnnotatedLine *FirstNonImportLine = nullptr ;
330
354
bool AnyImportAffected = false ;
355
+ bool FormattingOff = false ;
331
356
for (auto *Line : AnnotatedLines) {
332
357
Current = Line->First ;
333
358
LineEnd = Line->Last ;
359
+ // clang-format comments toggle formatting on/off.
360
+ // This is tracked in FormattingOff here and on JsModuleReference.
361
+ while (Current && Current->is (tok::comment)) {
362
+ StringRef CommentText = Current->TokenText .trim ();
363
+ if (CommentText == " // clang-format off" ) {
364
+ FormattingOff = true ;
365
+ } else if (CommentText == " // clang-format on" ) {
366
+ FormattingOff = false ;
367
+ // Special case: consider a trailing "clang-format on" line to be part
368
+ // of the module reference, so that it gets moved around together with
369
+ // it (as opposed to the next module reference, which might get sorted
370
+ // around).
371
+ if (!References.empty ()) {
372
+ References.back ().Range .setEnd (Current->Tok .getEndLoc ());
373
+ Start = Current->Tok .getEndLoc ().getLocWithOffset (1 );
374
+ }
375
+ }
376
+ // Handle all clang-format comments on a line, e.g. for an empty block.
377
+ Current = Current->Next ;
378
+ }
334
379
skipComments ();
335
380
if (Start.isInvalid () || References.empty ())
336
381
// After the first file level comment, consider line comments to be part
@@ -343,6 +388,7 @@ class JavaScriptImportSorter : public TokenAnalyzer {
343
388
continue ;
344
389
}
345
390
JsModuleReference Reference;
391
+ Reference.FormattingOff = FormattingOff;
346
392
Reference.Range .setBegin (Start);
347
393
if (!parseModuleReference (Keywords, Reference)) {
348
394
if (!FirstNonImportLine)
@@ -354,13 +400,14 @@ class JavaScriptImportSorter : public TokenAnalyzer {
354
400
Reference.Range .setEnd (LineEnd->Tok .getEndLoc ());
355
401
LLVM_DEBUG ({
356
402
llvm::dbgs () << " JsModuleReference: {"
357
- << " is_export: " << Reference.IsExport
403
+ << " formatting_off: " << Reference.FormattingOff
404
+ << " , is_export: " << Reference.IsExport
358
405
<< " , cat: " << Reference.Category
359
406
<< " , url: " << Reference.URL
360
407
<< " , prefix: " << Reference.Prefix ;
361
- for (size_t i = 0 ; i < Reference.Symbols .size (); ++i )
362
- llvm::dbgs () << " , " << Reference.Symbols [i ].Symbol << " as "
363
- << Reference.Symbols [i ].Alias ;
408
+ for (size_t I = 0 ; I < Reference.Symbols .size (); ++I )
409
+ llvm::dbgs () << " , " << Reference.Symbols [I ].Symbol << " as "
410
+ << Reference.Symbols [I ].Alias ;
364
411
llvm::dbgs () << " , text: " << getSourceText (Reference.Range );
365
412
llvm::dbgs () << " }\n " ;
366
413
});
0 commit comments