llvm-project

Форк
0
/
SortJavaScriptImports.cpp 
601 строка · 22.8 Кб
1
//===--- SortJavaScriptImports.cpp - Sort ES6 Imports -----------*- C++ -*-===//
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
/// \file
10
/// This file implements a sort operation for JavaScript ES6 imports.
11
///
12
//===----------------------------------------------------------------------===//
13

14
#include "SortJavaScriptImports.h"
15
#include "TokenAnalyzer.h"
16
#include "TokenAnnotator.h"
17
#include "clang/Basic/Diagnostic.h"
18
#include "clang/Basic/DiagnosticOptions.h"
19
#include "clang/Basic/LLVM.h"
20
#include "clang/Basic/SourceLocation.h"
21
#include "clang/Basic/SourceManager.h"
22
#include "clang/Basic/TokenKinds.h"
23
#include "clang/Format/Format.h"
24
#include "llvm/ADT/STLExtras.h"
25
#include "llvm/ADT/SmallVector.h"
26
#include "llvm/Support/Debug.h"
27
#include <algorithm>
28
#include <string>
29

30
#define DEBUG_TYPE "format-formatter"
31

32
namespace clang {
33
namespace format {
34

35
class FormatTokenLexer;
36

37
// An imported symbol in a JavaScript ES6 import/export, possibly aliased.
38
struct JsImportedSymbol {
39
  StringRef Symbol;
40
  StringRef Alias;
41
  SourceRange Range;
42

43
  bool operator==(const JsImportedSymbol &RHS) const {
44
    // Ignore Range for comparison, it is only used to stitch code together,
45
    // but imports at different code locations are still conceptually the same.
46
    return Symbol == RHS.Symbol && Alias == RHS.Alias;
47
  }
48
};
49

50
// An ES6 module reference.
51
//
52
// ES6 implements a module system, where individual modules (~= source files)
53
// can reference other modules, either importing symbols from them, or exporting
54
// symbols from them:
55
//   import {foo} from 'foo';
56
//   export {foo};
57
//   export {bar} from 'bar';
58
//
59
// `export`s with URLs are syntactic sugar for an import of the symbol from the
60
// URL, followed by an export of the symbol, allowing this code to treat both
61
// statements more or less identically, with the exception being that `export`s
62
// are sorted last.
63
//
64
// imports and exports support individual symbols, but also a wildcard syntax:
65
//   import * as prefix from 'foo';
66
//   export * from 'bar';
67
//
68
// This struct represents both exports and imports to build up the information
69
// required for sorting module references.
70
struct JsModuleReference {
71
  bool FormattingOff = false;
72
  bool IsExport = false;
73
  bool IsTypeOnly = false;
74
  // Module references are sorted into these categories, in order.
75
  enum ReferenceCategory {
76
    SIDE_EFFECT,     // "import 'something';"
77
    ABSOLUTE,        // from 'something'
78
    RELATIVE_PARENT, // from '../*'
79
    RELATIVE,        // from './*'
80
    ALIAS,           // import X = A.B;
81
  };
82
  ReferenceCategory Category = ReferenceCategory::SIDE_EFFECT;
83
  // The URL imported, e.g. `import .. from 'url';`. Empty for `export {a, b};`.
84
  StringRef URL;
85
  // Prefix from "import * as prefix". Empty for symbol imports and `export *`.
86
  // Implies an empty names list.
87
  StringRef Prefix;
88
  // Default import from "import DefaultName from '...';".
89
  StringRef DefaultImport;
90
  // Symbols from `import {SymbolA, SymbolB, ...} from ...;`.
91
  SmallVector<JsImportedSymbol, 1> Symbols;
92
  // Whether some symbols were merged into this one. Controls if the module
93
  // reference needs re-formatting.
94
  bool SymbolsMerged = false;
95
  // The source location just after { and just before } in the import.
96
  // Extracted eagerly to allow modification of Symbols later on.
97
  SourceLocation SymbolsStart, SymbolsEnd;
98
  // Textual position of the import/export, including preceding and trailing
99
  // comments.
100
  SourceRange Range;
101
};
102

103
bool operator<(const JsModuleReference &LHS, const JsModuleReference &RHS) {
104
  if (LHS.IsExport != RHS.IsExport)
105
    return LHS.IsExport < RHS.IsExport;
106
  if (LHS.Category != RHS.Category)
107
    return LHS.Category < RHS.Category;
108
  if (LHS.Category == JsModuleReference::ReferenceCategory::SIDE_EFFECT ||
109
      LHS.Category == JsModuleReference::ReferenceCategory::ALIAS) {
110
    // Side effect imports and aliases might be ordering sensitive. Consider
111
    // them equal so that they maintain their relative order in the stable sort
112
    // below. This retains transitivity because LHS.Category == RHS.Category
113
    // here.
114
    return false;
115
  }
116
  // Empty URLs sort *last* (for export {...};).
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
  // '*' imports (with prefix) sort before {a, b, ...} imports.
122
  if (LHS.Prefix.empty() != RHS.Prefix.empty())
123
    return LHS.Prefix.empty() < RHS.Prefix.empty();
124
  if (LHS.Prefix != RHS.Prefix)
125
    return LHS.Prefix > RHS.Prefix;
126
  return false;
127
}
128

129
// JavaScriptImportSorter sorts JavaScript ES6 imports and exports. It is
130
// implemented as a TokenAnalyzer because ES6 imports have substantial syntactic
131
// structure, making it messy to sort them using regular expressions.
132
class JavaScriptImportSorter : public TokenAnalyzer {
133
public:
134
  JavaScriptImportSorter(const Environment &Env, const FormatStyle &Style)
135
      : TokenAnalyzer(Env, Style),
136
        FileContents(Env.getSourceManager().getBufferData(Env.getFileID())) {
137
    // FormatToken.Tok starts out in an uninitialized state.
138
    invalidToken.Tok.startToken();
139
  }
140

141
  std::pair<tooling::Replacements, unsigned>
142
  analyze(TokenAnnotator &Annotator,
143
          SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
144
          FormatTokenLexer &Tokens) override {
145
    tooling::Replacements Result;
146
    AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
147

148
    const AdditionalKeywords &Keywords = Tokens.getKeywords();
149
    SmallVector<JsModuleReference, 16> References;
150
    AnnotatedLine *FirstNonImportLine;
151
    std::tie(References, FirstNonImportLine) =
152
        parseModuleReferences(Keywords, AnnotatedLines);
153

154
    if (References.empty())
155
      return {Result, 0};
156

157
    // The text range of all parsed imports, to be replaced later.
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) {
165
      JsModuleReference Reference = References[I];
166
      appendReference(ReferencesText, Reference);
167
      if (I + 1 < E) {
168
        // Insert breaks between imports and exports.
169
        ReferencesText += "\n";
170
        // Separate imports groups with two line breaks, but keep all exports
171
        // in a single group.
172
        if (!Reference.IsExport &&
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)
181
      return {Result, 0};
182

183
    // The loop above might collapse previously existing line breaks between
184
    // import blocks, and thus shrink the file. SortIncludes must not shrink
185
    // overall source length as there is currently no re-calculation of ranges
186
    // after applying source sorting.
187
    // This loop just backfills trailing spaces after the imports, which are
188
    // harmless and will be stripped by the subsequent formatting pass.
189
    // FIXME: A better long term fix is to re-calculate Ranges after sorting.
190
    unsigned PreviousSize = PreviousText.size();
191
    while (ReferencesText.size() < PreviousSize)
192
      ReferencesText += " ";
193

194
    // Separate references from the main code body of the file.
195
    if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 &&
196
        !(FirstNonImportLine->First->is(tok::comment) &&
197
          isClangFormatOn(FirstNonImportLine->First->TokenText.trim()))) {
198
      ReferencesText += "\n";
199
    }
200

201
    LLVM_DEBUG(llvm::dbgs() << "Replacing imports:\n"
202
                            << PreviousText << "\nwith:\n"
203
                            << ReferencesText << "\n");
204
    auto Err = Result.add(tooling::Replacement(
205
        Env.getSourceManager(), CharSourceRange::getCharRange(InsertionPoint),
206
        ReferencesText));
207
    // FIXME: better error handling. For now, just print error message and skip
208
    // the replacement for the release version.
209
    if (Err) {
210
      llvm::errs() << toString(std::move(Err)) << "\n";
211
      assert(false);
212
    }
213

214
    return {Result, 0};
215
  }
216

217
private:
218
  FormatToken *Current = nullptr;
219
  FormatToken *LineEnd = nullptr;
220

221
  FormatToken invalidToken;
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
      // Set the current token to an invalid token, so that further parsing on
238
      // this line fails.
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) {
248
    const SourceManager &SM = Env.getSourceManager();
249
    return FileContents.substr(SM.getFileOffset(Begin),
250
                               SM.getFileOffset(End) - SM.getFileOffset(Begin));
251
  }
252

253
  // Sorts the given module references.
254
  // Imports can have formatting disabled (FormattingOff), so the code below
255
  // skips runs of "no-formatting" module references, and sorts/merges the
256
  // references that have formatting enabled in individual chunks.
257
  SmallVector<JsModuleReference, 16>
258
  sortModuleReferences(const SmallVector<JsModuleReference, 16> &References) {
259
    // Sort module references.
260
    // Imports can have formatting disabled (FormattingOff), so the code below
261
    // skips runs of "no-formatting" module references, and sorts other
262
    // references per group.
263
    const auto *Start = References.begin();
264
    SmallVector<JsModuleReference, 16> ReferencesSorted;
265
    while (Start != References.end()) {
266
      while (Start != References.end() && Start->FormattingOff) {
267
        // Skip over all imports w/ disabled formatting.
268
        ReferencesSorted.push_back(*Start);
269
        ++Start;
270
      }
271
      SmallVector<JsModuleReference, 16> SortChunk;
272
      while (Start != References.end() && !Start->FormattingOff) {
273
        // Skip over all imports w/ disabled formatting.
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
  // Merge module references.
286
  // After sorting, find all references that import named symbols from the
287
  // same URL and merge their names. E.g.
288
  //   import {X} from 'a';
289
  //   import {Y} from 'a';
290
  // should be rewritten to:
291
  //   import {X, Y} from 'a';
292
  // Note: this modifies the passed in ``References`` vector (by removing no
293
  // longer needed references).
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
      // Skip:
301
      //   import 'foo';
302
      //   import * as foo from 'foo'; on either previous or this.
303
      //   import Default from 'foo'; on either previous or this.
304
      //   mismatching
305
      if (Reference->Category == JsModuleReference::SIDE_EFFECT ||
306
          PreviousReference->Category == JsModuleReference::SIDE_EFFECT ||
307
          Reference->IsExport != PreviousReference->IsExport ||
308
          Reference->IsTypeOnly != PreviousReference->IsTypeOnly ||
309
          !PreviousReference->Prefix.empty() || !Reference->Prefix.empty() ||
310
          !PreviousReference->DefaultImport.empty() ||
311
          !Reference->DefaultImport.empty() || Reference->Symbols.empty() ||
312
          PreviousReference->URL != Reference->URL) {
313
        PreviousReference = Reference;
314
        ++Reference;
315
        continue;
316
      }
317
      // Merge symbols from identical imports.
318
      PreviousReference->Symbols.append(Reference->Symbols);
319
      PreviousReference->SymbolsMerged = true;
320
      // Remove the merged import.
321
      Reference = References.erase(Reference);
322
    }
323
  }
324

325
  // Appends ``Reference`` to ``Buffer``.
326
  void appendReference(std::string &Buffer, JsModuleReference &Reference) {
327
    if (Reference.FormattingOff) {
328
      Buffer +=
329
          getSourceText(Reference.Range.getBegin(), Reference.Range.getEnd());
330
      return;
331
    }
332
    // Sort the individual symbols within the import.
333
    // E.g. `import {b, a} from 'x';` -> `import {a, b} from 'x';`
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
                });
339
    if (!Reference.SymbolsMerged && Symbols == Reference.Symbols) {
340
      // Symbols didn't change, just emit the entire module reference.
341
      StringRef ReferenceStmt = getSourceText(Reference.Range);
342
      Buffer += ReferenceStmt;
343
      return;
344
    }
345
    // Stitch together the module reference start...
346
    Buffer += getSourceText(Reference.Range.getBegin(), Reference.SymbolsStart);
347
    // ... then the references in order ...
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
    // ... followed by the module reference end.
356
    Buffer += getSourceText(Reference.SymbolsEnd, Reference.Range.getEnd());
357
  }
358

359
  // Parses module references in the given lines. Returns the module references,
360
  // and a pointer to the first "main code" line if that is adjacent to the
361
  // affected lines of module references, nullptr otherwise.
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
      // clang-format comments toggle formatting on/off.
375
      // This is tracked in FormattingOff here and on JsModuleReference.
376
      while (Current && Current->is(tok::comment)) {
377
        StringRef CommentText = Current->TokenText.trim();
378
        if (isClangFormatOff(CommentText)) {
379
          FormattingOff = true;
380
        } else if (isClangFormatOn(CommentText)) {
381
          FormattingOff = false;
382
          // Special case: consider a trailing "clang-format on" line to be part
383
          // of the module reference, so that it gets moved around together with
384
          // it (as opposed to the next module reference, which might get sorted
385
          // around).
386
          if (!References.empty()) {
387
            References.back().Range.setEnd(Current->Tok.getEndLoc());
388
            Start = Current->Tok.getEndLoc().getLocWithOffset(1);
389
          }
390
        }
391
        // Handle all clang-format comments on a line, e.g. for an empty block.
392
        Current = Current->Next;
393
      }
394
      skipComments();
395
      if (Start.isInvalid() || References.empty()) {
396
        // After the first file level comment, consider line comments to be part
397
        // of the import that immediately follows them by using the previously
398
        // set Start.
399
        Start = Line->First->Tok.getLocation();
400
      }
401
      if (!Current) {
402
        // Only comments on this line. Could be the first non-import line.
403
        FirstNonImportLine = Line;
404
        continue;
405
      }
406
      JsModuleReference Reference;
407
      Reference.FormattingOff = FormattingOff;
408
      Reference.Range.setBegin(Start);
409
      // References w/o a URL, e.g. export {A}, groups with RELATIVE.
410
      Reference.Category = JsModuleReference::ReferenceCategory::RELATIVE;
411
      if (!parseModuleReference(Keywords, Reference)) {
412
        if (!FirstNonImportLine)
413
          FirstNonImportLine = Line; // if no comment before.
414
        break;
415
      }
416
      FirstNonImportLine = nullptr;
417
      AnyImportAffected = AnyImportAffected || Line->Affected;
418
      Reference.Range.setEnd(LineEnd->Tok.getEndLoc());
419
      LLVM_DEBUG({
420
        llvm::dbgs() << "JsModuleReference: {"
421
                     << "formatting_off: " << Reference.FormattingOff
422
                     << ", is_export: " << Reference.IsExport
423
                     << ", cat: " << Reference.Category
424
                     << ", url: " << Reference.URL
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
      });
431
      References.push_back(Reference);
432
      Start = SourceLocation();
433
    }
434
    // Sort imports if any import line was affected.
435
    if (!AnyImportAffected)
436
      References.clear();
437
    return std::make_pair(References, FirstNonImportLine);
438
  }
439

440
  // Parses a JavaScript/ECMAScript 6 module reference.
441
  // See http://www.ecma-international.org/ecma-262/6.0/#sec-scripts-and-modules
442
  // for grammar EBNF (production ModuleItem).
443
  bool parseModuleReference(const AdditionalKeywords &Keywords,
444
                            JsModuleReference &Reference) {
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() && !Reference.IsExport) {
451
      // "import 'side-effect';"
452
      Reference.Category = JsModuleReference::ReferenceCategory::SIDE_EFFECT;
453
      Reference.URL =
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
      // imports have a 'from' clause, exports might not.
463
      nextToken();
464
      if (!Current->isStringLiteral())
465
        return false;
466
      // URL = TokenText without the quotes.
467
      Reference.URL =
468
          Current->TokenText.substr(1, Current->TokenText.size() - 2);
469
      if (Reference.URL.starts_with("..")) {
470
        Reference.Category =
471
            JsModuleReference::ReferenceCategory::RELATIVE_PARENT;
472
      } else if (Reference.URL.starts_with(".")) {
473
        Reference.Category = JsModuleReference::ReferenceCategory::RELATIVE;
474
      } else {
475
        Reference.Category = JsModuleReference::ReferenceCategory::ABSOLUTE;
476
      }
477
    }
478
    return true;
479
  }
480

481
  bool parseModuleBindings(const AdditionalKeywords &Keywords,
482
                           JsModuleReference &Reference) {
483
    if (parseStarBinding(Keywords, Reference))
484
      return true;
485
    return parseNamedBindings(Keywords, Reference);
486
  }
487

488
  bool parseStarBinding(const AdditionalKeywords &Keywords,
489
                        JsModuleReference &Reference) {
490
    // * as prefix from '...';
491
    if (Current->is(Keywords.kw_type) && Current->Next &&
492
        Current->Next->is(tok::star)) {
493
      Reference.IsTypeOnly = true;
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,
510
                          JsModuleReference &Reference) {
511
    if (Current->is(Keywords.kw_type) && Current->Next &&
512
        Current->Next->isOneOf(tok::identifier, tok::l_brace)) {
513
      Reference.IsTypeOnly = true;
514
      nextToken();
515
    }
516

517
    // eat a potential "import X, " prefix.
518
    if (!Reference.IsExport && Current->is(tok::identifier)) {
519
      Reference.DefaultImport = Current->TokenText;
520
      nextToken();
521
      if (Current->is(Keywords.kw_from))
522
        return true;
523
      // import X = A.B.C;
524
      if (Current->is(tok::equal)) {
525
        Reference.Category = JsModuleReference::ReferenceCategory::ALIAS;
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(); // eat comma.
539
    }
540
    if (Current->isNot(tok::l_brace))
541
      return false;
542

543
    // {sym as alias, sym2 as ...} from '...';
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
      // Make sure to include any preceding comments.
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
    // For named imports with a trailing comma ("import {X,}"), consider the
581
    // comma to be the end of the import list, so that it doesn't get removed.
582
    if (Current->Previous->is(tok::comma))
583
      Reference.SymbolsEnd = Current->Previous->Tok.getLocation();
584
    nextToken(); // consume r_brace
585
    return true;
586
  }
587
};
588

589
tooling::Replacements sortJavaScriptImports(const FormatStyle &Style,
590
                                            StringRef Code,
591
                                            ArrayRef<tooling::Range> Ranges,
592
                                            StringRef FileName) {
593
  // FIXME: Cursor support.
594
  auto Env = Environment::make(Code, FileName, Ranges);
595
  if (!Env)
596
    return {};
597
  return JavaScriptImportSorter(*Env, Style).process().first;
598
}
599

600
} // end namespace format
601
} // end namespace clang
602

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.