clang: lib/Format/SortJavaScriptImports.cpp Source File (original) (raw)
72
81
83
84
86
88
90
91
93
94
96
97
99};
108
109
110
111
112 return false;
113 }
114
115 if (LHS.URL.empty() != RHS.URL.empty())
116 return LHS.URL.empty() < RHS.URL.empty();
117 if (int Res = LHS.URL.compare_insensitive(RHS.URL))
118 return Res < 0;
119
120 if (LHS.Prefix.empty() != RHS.Prefix.empty())
121 return LHS.Prefix.empty() < RHS.Prefix.empty();
124 return false;
125}
131public:
134 FileContents(Env.getSourceManager().getBufferData(Env.getFileID())) {
135
136 invalidToken.Tok.startToken();
137 }
138
139 std::pair<tooling::Replacements, unsigned>
145
149 std::tie(References, FirstNonImportLine) =
150 parseModuleReferences(Keywords, AnnotatedLines);
151
152 if (References.empty())
154
155
156 SourceRange InsertionPoint = References[0].Range;
157 InsertionPoint.setEnd(References[References.size() - 1].Range.getEnd());
158
159 References = sortModuleReferences(References);
160
161 std::string ReferencesText;
162 for (unsigned I = 0, E = References.size(); I != E; ++I) {
164 appendReference(ReferencesText, Reference);
165 if (I + 1 < E) {
166
167 ReferencesText += "\n";
168
169
171 (Reference.IsExport != References[I + 1].IsExport ||
172 Reference.Category != References[I + 1].Category)) {
173 ReferencesText += "\n";
174 }
175 }
176 }
177 StringRef PreviousText = getSourceText(InsertionPoint);
178 if (ReferencesText == PreviousText)
180
181
182
183
184
185
186
187
188 unsigned PreviousSize = PreviousText.size();
189 while (ReferencesText.size() < PreviousSize)
190 ReferencesText += " ";
191
192
193 if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 &&
194 !(FirstNonImportLine->First->is(tok::comment) &&
196 ReferencesText += "\n";
197 }
198
199 LLVM_DEBUG(llvm::dbgs() << "Replacing imports:\n"
200 << PreviousText << "\nwith:\n"
201 << ReferencesText << "\n");
204 ReferencesText));
205
206
207 if (Err) {
208 llvm::errs() << toString(std::move(Err)) << "\n";
209 assert(false);
210 }
211
213 }
214
215private:
218
220
221 StringRef FileContents;
222
223 void skipComments() { Current = skipComments(Current); }
224
226 while (Tok && Tok->is(tok::comment))
228 return Tok;
229 }
230
231 void nextToken() {
232 Current = Current->Next;
233 skipComments();
234 if (!Current || Current == LineEnd->Next) {
235
236
237 Current = &invalidToken;
238 }
239 }
240
241 StringRef getSourceText(SourceRange Range) {
242 return getSourceText(Range.getBegin(), Range.getEnd());
243 }
244
245 StringRef getSourceText(SourceLocation Begin, SourceLocation End) {
246 const SourceManager &SM = Env.getSourceManager();
247 return FileContents.substr(SM.getFileOffset(Begin),
248 SM.getFileOffset(End) - SM.getFileOffset(Begin));
249 }
250
251
252
253
254
255 SmallVector<JsModuleReference, 16>
256 sortModuleReferences(const SmallVector<JsModuleReference, 16> &References) {
257
258
259
260
261 const auto *Start = References.begin();
262 SmallVector<JsModuleReference, 16> ReferencesSorted;
263 while (Start != References.end()) {
264 while (Start != References.end() && Start->FormattingOff) {
265
266 ReferencesSorted.push_back(*Start);
267 ++Start;
268 }
269 SmallVector<JsModuleReference, 16> SortChunk;
270 while (Start != References.end() && !Start->FormattingOff) {
271
272 SortChunk.push_back(*Start);
273 ++Start;
274 }
275 stable_sort(SortChunk);
276 mergeModuleReferences(SortChunk);
277 llvm::append_range(ReferencesSorted, SortChunk);
278 }
279 return ReferencesSorted;
280 }
281
282
283
284
285
286
287
288
289
290
291 void mergeModuleReferences(SmallVector<JsModuleReference, 16> &References) {
292 if (References.empty())
293 return;
294 JsModuleReference *PreviousReference = References.begin();
295 auto *Reference = std::next(References.begin());
296 while (Reference != References.end()) {
297
298
299
300
301
304 Reference->IsExport != PreviousReference->IsExport ||
305 Reference->IsTypeOnly != PreviousReference->IsTypeOnly ||
306 !PreviousReference->Prefix.empty() || ->Prefix.empty() ||
307 !PreviousReference->DefaultImport.empty() ||
309 PreviousReference->URL != Reference->URL) {
312 continue;
313 }
314
315 PreviousReference->Symbols.append(Reference->Symbols);
316 PreviousReference->SymbolsMerged = true;
317
319 }
320 }
321
322
323 void appendReference(std::string &Buffer, JsModuleReference &Reference) {
325 Buffer +=
327 return;
328 }
329
330
331 SmallVector<JsImportedSymbol, 1> Symbols = Reference.Symbols;
332 stable_sort(Symbols,
333 [&](const JsImportedSymbol &LHS, const JsImportedSymbol &RHS) {
334 return LHS.Symbol.compare_insensitive(RHS.Symbol) < 0;
335 });
337
338 StringRef ReferenceStmt = getSourceText(Reference.Range);
339 Buffer += ReferenceStmt;
340 return;
341 }
342
343 Buffer += getSourceText(Reference.Range.getBegin(), Reference.SymbolsStart);
344
345 if (!Symbols.empty()) {
346 Buffer += getSourceText(Symbols.front().Range);
347 for (const JsImportedSymbol &Symbol : drop_begin(Symbols)) {
348 Buffer += ",";
349 Buffer += getSourceText(Symbol.Range);
350 }
351 }
352
353 Buffer += getSourceText(Reference.SymbolsEnd, Reference.Range.getEnd());
354 }
355
356
357
358
359 std::pair<SmallVector<JsModuleReference, 16>, AnnotatedLine *>
360 parseModuleReferences(const AdditionalKeywords &Keywords,
361 SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
362 SmallVector<JsModuleReference, 16> References;
363 SourceLocation Start;
364 AnnotatedLine *FirstNonImportLine = nullptr;
365 bool AnyImportAffected = false;
366 bool FormattingOff = false;
367 for (auto *Line : AnnotatedLines) {
368 assert(Line->First);
369 Current = Line->First;
370 LineEnd = Line->Last;
371
372
373 while (Current && Current->is(tok::comment)) {
374 StringRef CommentText = Current->TokenText.trim();
376 FormattingOff = true;
378 FormattingOff = false;
379
380
381
382
383 if (!References.empty()) {
384 References.back().Range.setEnd(Current->Tok.getEndLoc());
385 Start = Current->Tok.getEndLoc().getLocWithOffset(1);
386 }
387 }
388
389 Current = Current->Next;
390 }
391 skipComments();
392 if (Start.isInvalid() || References.empty()) {
393
394
395
396 Start = Line->First->Tok.getLocation();
397 }
398 if (!Current) {
399
400 FirstNonImportLine = Line;
401 continue;
402 }
404 Reference.FormattingOff = FormattingOff;
406
408 if (!parseModuleReference(Keywords, Reference)) {
409 if (!FirstNonImportLine)
410 FirstNonImportLine = Line;
411 break;
412 }
413 FirstNonImportLine = nullptr;
414 AnyImportAffected = AnyImportAffected || Line->Affected;
415 Reference.Range.setEnd(LineEnd->Tok.getEndLoc());
416 LLVM_DEBUG({
417 llvm::dbgs() << "JsModuleReference: {"
418 << "formatting_off: " << Reference.FormattingOff
419 << ", is_export: " << Reference.IsExport
420 << ", cat: " << Reference.Category
422 << ", prefix: " << Reference.Prefix;
423 for (const JsImportedSymbol &Symbol : Reference.Symbols)
424 llvm::dbgs() << ", " << Symbol.Symbol << " as " << Symbol.Alias;
425 llvm::dbgs() << ", text: " << getSourceText(Reference.Range);
426 llvm::dbgs() << "}\n";
427 });
429 Start = SourceLocation();
430 }
431
432 if (!AnyImportAffected)
433 References.clear();
434 return std::make_pair(References, FirstNonImportLine);
435 }
436
437
438
439
440 bool parseModuleReference(const AdditionalKeywords &Keywords,
442 if (!Current || Current->isNoneOf(Keywords.kw_import, tok::kw_export))
443 return false;
444 Reference.IsExport = Current->is(tok::kw_export);
445
446 nextToken();
447 if (Current->isStringLiteral() && .IsExport) {
448
451 Current->TokenText.substr(1, Current->TokenText.size() - 2);
452 return true;
453 }
454
455 if (!parseModuleBindings(Keywords, Reference))
456 return false;
457
458 if (Current->is(Keywords.kw_from)) {
459
460 nextToken();
461 if (!Current->isStringLiteral())
462 return false;
463
465 Current->TokenText.substr(1, Current->TokenText.size() - 2);
466 if (Reference.URL.starts_with("..")) {
469 } else if (Reference.URL.starts_with(".")) {
471 } else {
473 }
474 }
475 return true;
476 }
477
478 bool parseModuleBindings(const AdditionalKeywords &Keywords,
480 if (parseStarBinding(Keywords, Reference))
481 return true;
482 return parseNamedBindings(Keywords, Reference);
483 }
484
485 bool parseStarBinding(const AdditionalKeywords &Keywords,
487
488 if (Current->is(Keywords.kw_type) && Current->Next &&
489 Current->Next->is(tok::star)) {
491 nextToken();
492 }
493 if (Current->isNot(tok::star))
494 return false;
495 nextToken();
496 if (Current->isNot(Keywords.kw_as))
497 return false;
498 nextToken();
499 if (Current->isNot(tok::identifier))
500 return false;
501 Reference.Prefix = Current->TokenText;
502 nextToken();
503 return true;
504 }
505
506 bool parseNamedBindings(const AdditionalKeywords &Keywords,
508 if (Current->is(Keywords.kw_type) && Current->Next &&
509 Current->Next->isOneOf(tok::identifier, tok::l_brace)) {
511 nextToken();
512 }
513
514
515 if (.IsExport && Current->is(tok::identifier)) {
516 Reference.DefaultImport = Current->TokenText;
517 nextToken();
518 if (Current->is(Keywords.kw_from))
519 return true;
520
521 if (Current->is(tok::equal)) {
523 nextToken();
524 while (Current->is(tok::identifier)) {
525 nextToken();
526 if (Current->is(tok::semi))
527 return true;
528 if (Current->isNot(tok::period))
529 return false;
530 nextToken();
531 }
532 }
533 if (Current->isNot(tok::comma))
534 return false;
535 nextToken();
536 }
537 if (Current->isNot(tok::l_brace))
538 return false;
539
540
541 Reference.SymbolsStart = Current->Tok.getEndLoc();
542 while (Current->isNot(tok::r_brace)) {
543 nextToken();
544 if (Current->is(tok::r_brace))
545 break;
546 auto IsIdentifier = [](const auto *Tok) {
547 return Tok->isOneOf(tok::identifier, tok::kw_default, tok::kw_template);
548 };
549 bool isTypeOnly = Current->is(Keywords.kw_type) && Current->Next &&
550 IsIdentifier(Current->Next);
551 if (!isTypeOnly && !IsIdentifier(Current))
552 return false;
553
554 JsImportedSymbol Symbol;
555
556 Symbol.Range.setBegin(
557 Current->getPreviousNonComment()->Next->WhitespaceRange.getBegin());
558 if (isTypeOnly)
559 nextToken();
560 Symbol.Symbol = Current->TokenText;
561 nextToken();
562
563 if (Current->is(Keywords.kw_as)) {
564 nextToken();
565 if (!IsIdentifier(Current))
566 return false;
567 Symbol.Alias = Current->TokenText;
568 nextToken();
569 }
570 Symbol.Range.setEnd(Current->Tok.getLocation());
571 Reference.Symbols.push_back(Symbol);
572
573 if (Current->isNoneOf(tok::r_brace, tok::comma))
574 return false;
575 }
576 Reference.SymbolsEnd = Current->Tok.getLocation();
577
578
579 if (Current->Previous->is(tok::comma))
580 Reference.SymbolsEnd = Current->Previous->Tok.getLocation();
581 nextToken();
582 return true;
583 }
584};
587 StringRef Code,
590
592 if (!Env)
593 return {};
595}