clang: lib/Format/SortJavaScriptImports.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/ADT/SmallVector.h"
26#include "llvm/Support/Debug.h"
27#include
28#include
29
30#define DEBUG_TYPE "format-formatter"
31
33namespace format {
34
35class FormatTokenLexer;
36
37
42
44
45
47 }
48};
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
74
81 };
83
85
86
88
90
92
93
95
96
98
99
101};
102
110
111
112
113
114 return false;
115 }
116
117 if (LHS.URL.empty() != RHS.URL.empty())
118 return LHS.URL.empty() < RHS.URL.empty();
119 if (int Res = LHS.URL.compare_insensitive(RHS.URL))
120 return Res < 0;
121
122 if (LHS.Prefix.empty() != RHS.Prefix.empty())
123 return LHS.Prefix.empty() < RHS.Prefix.empty();
126 return false;
127}
128
129
130
131
133public:
136 FileContents(Env.getSourceManager().getBufferData(Env.getFileID())) {
137
139 }
140
141 std::pair<tooling::Replacements, unsigned>
147
151 std::tie(References, FirstNonImportLine) =
152 parseModuleReferences(Keywords, AnnotatedLines);
153
154 if (References.empty())
156
157
158 SourceRange InsertionPoint = References[0].Range;
159 InsertionPoint.setEnd(References[References.size() - 1].Range.getEnd());
160
161 References = sortModuleReferences(References);
162
163 std::string ReferencesText;
164 for (unsigned I = 0, E = References.size(); I != E; ++I) {
166 appendReference(ReferencesText, Reference);
167 if (I + 1 < E) {
168
169 ReferencesText += "\n";
170
171
173 (Reference.IsExport != References[I + 1].IsExport ||
174 Reference.Category != References[I + 1].Category)) {
175 ReferencesText += "\n";
176 }
177 }
178 }
179 StringRef PreviousText = getSourceText(InsertionPoint);
180 if (ReferencesText == PreviousText)
182
183
184
185
186
187
188
189
190 unsigned PreviousSize = PreviousText.size();
191 while (ReferencesText.size() < PreviousSize)
192 ReferencesText += " ";
193
194
195 if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 &&
196 !(FirstNonImportLine->First->is(tok::comment) &&
198 ReferencesText += "\n";
199 }
200
201 LLVM_DEBUG(llvm::dbgs() << "Replacing imports:\n"
202 << PreviousText << "\nwith:\n"
203 << ReferencesText << "\n");
206 ReferencesText));
207
208
209 if (Err) {
210 llvm::errs() << toString(std::move(Err)) << "\n";
211 assert(false);
212 }
213
215 }
216
217private:
220
222
223 StringRef FileContents;
224
225 void skipComments() { Current = skipComments(Current); }
226
227 FormatToken *skipComments(FormatToken *Tok) {
228 while (Tok && Tok->is(tok::comment))
229 Tok = Tok->Next;
230 return Tok;
231 }
232
233 void nextToken() {
234 Current = Current->Next;
235 skipComments();
236 if (!Current || Current == LineEnd->Next) {
237
238
239 Current = &invalidToken;
240 }
241 }
242
243 StringRef getSourceText(SourceRange Range) {
244 return getSourceText(Range.getBegin(), Range.getEnd());
245 }
246
247 StringRef getSourceText(SourceLocation Begin, SourceLocation End) {
249 return FileContents.substr(SM.getFileOffset(Begin),
250 SM.getFileOffset(End) - SM.getFileOffset(Begin));
251 }
252
253
254
255
256
257 SmallVector<JsModuleReference, 16>
258 sortModuleReferences(const SmallVector<JsModuleReference, 16> &References) {
259
260
261
262
263 const auto *Start = References.begin();
264 SmallVector<JsModuleReference, 16> ReferencesSorted;
265 while (Start != References.end()) {
266 while (Start != References.end() && Start->FormattingOff) {
267
268 ReferencesSorted.push_back(*Start);
269 ++Start;
270 }
271 SmallVector<JsModuleReference, 16> SortChunk;
272 while (Start != References.end() && !Start->FormattingOff) {
273
274 SortChunk.push_back(*Start);
275 ++Start;
276 }
277 stable_sort(SortChunk);
278 mergeModuleReferences(SortChunk);
279 ReferencesSorted.insert(ReferencesSorted.end(), SortChunk.begin(),
280 SortChunk.end());
281 }
282 return ReferencesSorted;
283 }
284
285
286
287
288
289
290
291
292
293
294 void mergeModuleReferences(SmallVector<JsModuleReference, 16> &References) {
295 if (References.empty())
296 return;
297 JsModuleReference *PreviousReference = References.begin();
298 auto *Reference = std::next(References.begin());
299 while (Reference != References.end()) {
300
301
302
303
304
307 Reference->IsExport != PreviousReference->IsExport ||
308 Reference->IsTypeOnly != PreviousReference->IsTypeOnly ||
309 !PreviousReference->Prefix.empty() || ->Prefix.empty() ||
310 !PreviousReference->DefaultImport.empty() ||
312 PreviousReference->URL != Reference->URL) {
315 continue;
316 }
317
318 PreviousReference->Symbols.append(Reference->Symbols);
319 PreviousReference->SymbolsMerged = true;
320
322 }
323 }
324
325
326 void appendReference(std::string &Buffer, JsModuleReference &Reference) {
328 Buffer +=
330 return;
331 }
332
333
334 SmallVector<JsImportedSymbol, 1> Symbols = Reference.Symbols;
335 stable_sort(Symbols,
336 [&](const JsImportedSymbol &LHS, const JsImportedSymbol &RHS) {
337 return LHS.Symbol.compare_insensitive(RHS.Symbol) < 0;
338 });
340
341 StringRef ReferenceStmt = getSourceText(Reference.Range);
342 Buffer += ReferenceStmt;
343 return;
344 }
345
346 Buffer += getSourceText(Reference.Range.getBegin(), Reference.SymbolsStart);
347
348 if (!Symbols.empty()) {
349 Buffer += getSourceText(Symbols.front().Range);
350 for (const JsImportedSymbol &Symbol : drop_begin(Symbols)) {
351 Buffer += ",";
352 Buffer += getSourceText(Symbol.Range);
353 }
354 }
355
356 Buffer += getSourceText(Reference.SymbolsEnd, Reference.Range.getEnd());
357 }
358
359
360
361
362 std::pair<SmallVector<JsModuleReference, 16>, AnnotatedLine *>
363 parseModuleReferences(const AdditionalKeywords &Keywords,
364 SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
365 SmallVector<JsModuleReference, 16> References;
366 SourceLocation Start;
367 AnnotatedLine *FirstNonImportLine = nullptr;
368 bool AnyImportAffected = false;
369 bool FormattingOff = false;
370 for (auto *Line : AnnotatedLines) {
371 assert(Line->First);
372 Current = Line->First;
373 LineEnd = Line->Last;
374
375
376 while (Current && Current->is(tok::comment)) {
377 StringRef CommentText = Current->TokenText.trim();
379 FormattingOff = true;
381 FormattingOff = false;
382
383
384
385
386 if (!References.empty()) {
387 References.back().Range.setEnd(Current->Tok.getEndLoc());
388 Start = Current->Tok.getEndLoc().getLocWithOffset(1);
389 }
390 }
391
392 Current = Current->Next;
393 }
394 skipComments();
395 if (Start.isInvalid() || References.empty()) {
396
397
398
399 Start = Line->First->Tok.getLocation();
400 }
401 if (!Current) {
402
403 FirstNonImportLine = Line;
404 continue;
405 }
407 Reference.FormattingOff = FormattingOff;
409
411 if (!parseModuleReference(Keywords, Reference)) {
412 if (!FirstNonImportLine)
413 FirstNonImportLine = Line;
414 break;
415 }
416 FirstNonImportLine = nullptr;
417 AnyImportAffected = AnyImportAffected || Line->Affected;
419 LLVM_DEBUG({
420 llvm::dbgs() << "JsModuleReference: {"
421 << "formatting_off: " << Reference.FormattingOff
422 << ", is_export: " << Reference.IsExport
423 << ", cat: " << Reference.Category
425 << ", prefix: " << Reference.Prefix;
426 for (const JsImportedSymbol &Symbol : Reference.Symbols)
427 llvm::dbgs() << ", " << Symbol.Symbol << " as " << Symbol.Alias;
428 llvm::dbgs() << ", text: " << getSourceText(Reference.Range);
429 llvm::dbgs() << "}\n";
430 });
432 Start = SourceLocation();
433 }
434
435 if (!AnyImportAffected)
436 References.clear();
437 return std::make_pair(References, FirstNonImportLine);
438 }
439
440
441
442
443 bool parseModuleReference(const AdditionalKeywords &Keywords,
445 if (!Current || !Current->isOneOf(Keywords.kw_import, tok::kw_export))
446 return false;
447 Reference.IsExport = Current->is(tok::kw_export);
448
449 nextToken();
450 if (Current->isStringLiteral() && .IsExport) {
451
454 Current->TokenText.substr(1, Current->TokenText.size() - 2);
455 return true;
456 }
457
458 if (!parseModuleBindings(Keywords, Reference))
459 return false;
460
461 if (Current->is(Keywords.kw_from)) {
462
463 nextToken();
464 if (!Current->isStringLiteral())
465 return false;
466
468 Current->TokenText.substr(1, Current->TokenText.size() - 2);
469 if (Reference.URL.starts_with("..")) {
472 } else if (Reference.URL.starts_with(".")) {
474 } else {
476 }
477 }
478 return true;
479 }
480
481 bool parseModuleBindings(const AdditionalKeywords &Keywords,
483 if (parseStarBinding(Keywords, Reference))
484 return true;
485 return parseNamedBindings(Keywords, Reference);
486 }
487
488 bool parseStarBinding(const AdditionalKeywords &Keywords,
490
491 if (Current->is(Keywords.kw_type) && Current->Next &&
492 Current->Next->is(tok::star)) {
494 nextToken();
495 }
496 if (Current->isNot(tok::star))
497 return false;
498 nextToken();
499 if (Current->isNot(Keywords.kw_as))
500 return false;
501 nextToken();
502 if (Current->isNot(tok::identifier))
503 return false;
504 Reference.Prefix = Current->TokenText;
505 nextToken();
506 return true;
507 }
508
509 bool parseNamedBindings(const AdditionalKeywords &Keywords,
511 if (Current->is(Keywords.kw_type) && Current->Next &&
512 Current->Next->isOneOf(tok::identifier, tok::l_brace)) {
514 nextToken();
515 }
516
517
518 if (.IsExport && Current->is(tok::identifier)) {
519 Reference.DefaultImport = Current->TokenText;
520 nextToken();
521 if (Current->is(Keywords.kw_from))
522 return true;
523
524 if (Current->is(tok::equal)) {
526 nextToken();
527 while (Current->is(tok::identifier)) {
528 nextToken();
529 if (Current->is(tok::semi))
530 return true;
531 if (Current->isNot(tok::period))
532 return false;
533 nextToken();
534 }
535 }
536 if (Current->isNot(tok::comma))
537 return false;
538 nextToken();
539 }
540 if (Current->isNot(tok::l_brace))
541 return false;
542
543
544 Reference.SymbolsStart = Current->Tok.getEndLoc();
545 while (Current->isNot(tok::r_brace)) {
546 nextToken();
547 if (Current->is(tok::r_brace))
548 break;
549 auto IsIdentifier = [](const auto *Tok) {
550 return Tok->isOneOf(tok::identifier, tok::kw_default, tok::kw_template);
551 };
552 bool isTypeOnly = Current->is(Keywords.kw_type) && Current->Next &&
553 IsIdentifier(Current->Next);
554 if (!isTypeOnly && !IsIdentifier(Current))
555 return false;
556
557 JsImportedSymbol Symbol;
558
559 Symbol.Range.setBegin(
560 Current->getPreviousNonComment()->Next->WhitespaceRange.getBegin());
561 if (isTypeOnly)
562 nextToken();
563 Symbol.Symbol = Current->TokenText;
564 nextToken();
565
566 if (Current->is(Keywords.kw_as)) {
567 nextToken();
568 if (!IsIdentifier(Current))
569 return false;
570 Symbol.Alias = Current->TokenText;
571 nextToken();
572 }
573 Symbol.Range.setEnd(Current->Tok.getLocation());
574 Reference.Symbols.push_back(Symbol);
575
576 if (!Current->isOneOf(tok::r_brace, tok::comma))
577 return false;
578 }
579 Reference.SymbolsEnd = Current->Tok.getLocation();
580
581
582 if (Current->Previous->is(tok::comma))
583 Reference.SymbolsEnd = Current->Previous->Tok.getLocation();
584 nextToken();
585 return true;
586 }
587};
588
590 StringRef Code,
593
595 if ()
596 return {};
598}
599
600}
601}
Defines the Diagnostic-related interfaces.
Various functions to configurably format source code.
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified.
static std::string toString(const clang::SanitizerSet &Sanitizers)
Produce a string containing comma-separated names of sanitizers in Sanitizers set.
This file implements a sorter for JavaScript ES6 imports.
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
This file declares an abstract TokenAnalyzer, and associated helper classes.
This file implements a token annotator, i.e.
Defines the clang::TokenKind enum and support functions.
static CharSourceRange getCharRange(SourceRange R)
Encodes a location in the source.
A trivial tuple used to represent a source range.
void setEnd(SourceLocation e)
SourceLocation getEndLoc() const
void startToken()
Reset all flags to cleared.
bool computeAffectedLines(SmallVectorImpl< AnnotatedLine * > &Lines)
SourceManager & getSourceManager() const
static std::unique_ptr< Environment > make(StringRef Code, StringRef FileName, ArrayRef< tooling::Range > Ranges, unsigned FirstStartColumn=0, unsigned NextStartColumn=0, unsigned LastStartColumn=0)
std::pair< tooling::Replacements, unsigned > analyze(TokenAnnotator &Annotator, SmallVectorImpl< AnnotatedLine * > &AnnotatedLines, FormatTokenLexer &Tokens) override
JavaScriptImportSorter(const Environment &Env, const FormatStyle &Style)
AffectedRangeManager AffectedRangeMgr
std::pair< tooling::Replacements, unsigned > process(bool SkipAnnotation=false)
Determines extra information about the tokens comprising an UnwrappedLine.
bool operator<(const JsModuleReference &LHS, const JsModuleReference &RHS)
bool isClangFormatOff(StringRef Comment)
tooling::Replacements sortJavaScriptImports(const FormatStyle &Style, StringRef Code, ArrayRef< tooling::Range > Ranges, StringRef FileName)
bool isClangFormatOn(StringRef Comment)
The JSON file list parser is used to communicate input to InstallAPI.
@ Result
The result type of a method or function.
Encapsulates keywords that are context sensitive or for languages not properly supported by Clang's l...
The FormatStyle is used to configure the formatting to follow specific guidelines.
A wrapper around a Token storing information about the whitespace characters preceding it.
StringRef TokenText
The raw text of the token.
FormatToken * Next
The next token in the unwrapped line.
unsigned NewlinesBefore
The number of newlines immediately before the Token.
bool is(tok::TokenKind Kind) const
bool operator==(const JsImportedSymbol &RHS) const
SmallVector< JsImportedSymbol, 1 > Symbols
SourceLocation SymbolsStart
ReferenceCategory Category
SourceLocation SymbolsEnd