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() || Reference->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() && Reference.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 (Reference.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 (Env)

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