Skip to content
Snippets Groups Projects
Commit 3e258a6c authored by Martin Probst's avatar Martin Probst
Browse files

clang-format: [JS] sort ES6 imports.

Summary:
This change automatically sorts ES6 imports and exports into four groups:
absolute imports, parent imports, relative imports, and then exports. Exports
are sorted in the same order, but not grouped further.

To keep JS import sorting out of Format.cpp, this required extracting the
TokenAnalyzer infrastructure to separate header and implementation files.

Reviewers: djasper

Subscribers: cfe-commits, klimek

Differential Revision: http://reviews.llvm.org/D20198

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@270203 91177308-0d34-0410-b5e6-96231b3b80d8
parent b01e8464
No related branches found
No related tags found
No related merge requests found
......@@ -852,6 +852,22 @@ extern const char *StyleOptionHelpDescription;
FormatStyle getStyle(StringRef StyleName, StringRef FileName,
StringRef FallbackStyle, vfs::FileSystem *FS = nullptr);
// \brief Returns a string representation of ``Language``.
inline StringRef getLanguageName(FormatStyle::LanguageKind Language) {
switch (Language) {
case FormatStyle::LK_Cpp:
return "C++";
case FormatStyle::LK_Java:
return "Java";
case FormatStyle::LK_JavaScript:
return "JavaScript";
case FormatStyle::LK_Proto:
return "Proto";
default:
return "Unknown";
}
}
} // end namespace format
} // end namespace clang
......
......@@ -6,6 +6,9 @@ add_clang_library(clangFormat
ContinuationIndenter.cpp
Format.cpp
FormatToken.cpp
FormatTokenLexer.cpp
SortJavaScriptImports.cpp
TokenAnalyzer.cpp
TokenAnnotator.cpp
UnwrappedLineFormatter.cpp
UnwrappedLineParser.cpp
......
This diff is collapsed.
......@@ -535,6 +535,7 @@ struct AdditionalKeywords {
kw_NS_ENUM = &IdentTable.get("NS_ENUM");
kw_NS_OPTIONS = &IdentTable.get("NS_OPTIONS");
kw_as = &IdentTable.get("as");
kw_async = &IdentTable.get("async");
kw_await = &IdentTable.get("await");
kw_finally = &IdentTable.get("finally");
......@@ -585,6 +586,7 @@ struct AdditionalKeywords {
IdentifierInfo *kw___except;
// JavaScript keywords.
IdentifierInfo *kw_as;
IdentifierInfo *kw_async;
IdentifierInfo *kw_await;
IdentifierInfo *kw_finally;
......
This diff is collapsed.
//===--- FormatTokenLexer.h - Format C++ code ----------------*- C++ ----*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file contains FormatTokenLexer, which tokenizes a source file
/// into a token stream suitable for ClangFormat.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_FORMAT_FORMATTOKENLEXER_H
#define LLVM_CLANG_LIB_FORMAT_FORMATTOKENLEXER_H
#include "Encoding.h"
#include "FormatToken.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/Support/Regex.h"
namespace clang {
namespace format {
class FormatTokenLexer {
public:
FormatTokenLexer(const SourceManager &SourceMgr, FileID ID,
const FormatStyle &Style, encoding::Encoding Encoding);
ArrayRef<FormatToken *> lex();
const AdditionalKeywords &getKeywords() { return Keywords; }
private:
void tryMergePreviousTokens();
bool tryMergeLessLess();
bool tryMergeTokens(ArrayRef<tok::TokenKind> Kinds, TokenType NewType);
// Returns \c true if \p Tok can only be followed by an operand in JavaScript.
bool precedesOperand(FormatToken *Tok);
bool canPrecedeRegexLiteral(FormatToken *Prev);
// Tries to parse a JavaScript Regex literal starting at the current token,
// if that begins with a slash and is in a location where JavaScript allows
// regex literals. Changes the current token to a regex literal and updates
// its text if successful.
void tryParseJSRegexLiteral();
void tryParseTemplateString();
bool tryMerge_TMacro();
bool tryMergeConflictMarkers();
FormatToken *getStashedToken();
FormatToken *getNextToken();
FormatToken *FormatTok;
bool IsFirstToken;
bool GreaterStashed, LessStashed;
unsigned Column;
unsigned TrailingWhitespace;
std::unique_ptr<Lexer> Lex;
const SourceManager &SourceMgr;
FileID ID;
const FormatStyle &Style;
IdentifierTable IdentTable;
AdditionalKeywords Keywords;
encoding::Encoding Encoding;
llvm::SpecificBumpPtrAllocator<FormatToken> Allocator;
// Index (in 'Tokens') of the last token that starts a new line.
unsigned FirstInLineIndex;
SmallVector<FormatToken *, 16> Tokens;
SmallVector<IdentifierInfo *, 8> ForEachMacros;
bool FormattingDisabled;
llvm::Regex MacroBlockBeginRegex;
llvm::Regex MacroBlockEndRegex;
void readRawToken(FormatToken &Tok);
void resetLexer(unsigned Offset);
};
} // namespace format
} // namespace clang
#endif
//===--- SortJavaScriptImports.h - Sort ES6 Imports -------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements a sort operation for JavaScript ES6 imports.
///
//===----------------------------------------------------------------------===//
#include "SortJavaScriptImports.h"
#include "SortJavaScriptImports.h"
#include "TokenAnalyzer.h"
#include "TokenAnnotator.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Debug.h"
#include <string>
#define DEBUG_TYPE "format-formatter"
namespace clang {
namespace format {
class FormatTokenLexer;
using clang::format::FormatStyle;
// An imported symbol in a JavaScript ES6 import/export, possibly aliased.
struct JsImportedSymbol {
StringRef Symbol;
StringRef Alias;
};
// An ES6 module reference.
//
// ES6 implements a module system, where individual modules (~= source files)
// can reference other modules, either importing symbols from them, or exporting
// symbols from them:
// import {foo} from 'foo';
// export {foo};
// export {bar} from 'bar';
//
// `export`s with URLs are syntactic sugar for an import of the symbol from the
// URL, followed by an export of the symbol, allowing this code to treat both
// statements more or less identically, with the exception being that `export`s
// are sorted last.
//
// imports and exports support individual symbols, but also a wildcard syntax:
// import * as prefix from 'foo';
// export * from 'bar';
//
// This struct represents both exports and imports to build up the information
// required for sorting module references.
struct JsModuleReference {
bool IsExport = false;
// Module references are sorted into these categories, in order.
enum ReferenceCategory {
SIDE_EFFECT, // "import 'something';"
ABSOLUTE, // from 'something'
RELATIVE_PARENT, // from '../*'
RELATIVE, // from './*'
};
ReferenceCategory Category = ReferenceCategory::SIDE_EFFECT;
// The URL imported, e.g. `import .. from 'url';`. Empty for `export {a, b};`.
StringRef URL;
// Prefix from "import * as prefix". Empty for symbol imports and `export *`.
// Implies an empty names list.
StringRef Prefix;
// Symbols from `import {SymbolA, SymbolB, ...} from ...;`.
SmallVector<JsImportedSymbol, 1> Symbols;
// Textual position of the import/export, including preceding and trailing
// comments.
SourceRange Range;
};
bool operator<(const JsModuleReference &LHS, const JsModuleReference &RHS) {
if (LHS.IsExport != RHS.IsExport)
return LHS.IsExport < RHS.IsExport;
if (LHS.Category != RHS.Category)
return LHS.Category < RHS.Category;
if (LHS.Category == JsModuleReference::ReferenceCategory::SIDE_EFFECT)
// Side effect imports might be ordering sensitive. Consider them equal so
// that they maintain their relative order in the stable sort below.
// This retains transitivity because LHS.Category == RHS.Category here.
return false;
// Empty URLs sort *last* (for export {...};).
if (LHS.URL.empty() != RHS.URL.empty())
return LHS.URL.empty() < RHS.URL.empty();
if (LHS.URL != RHS.URL)
return LHS.URL < RHS.URL;
// '*' imports (with prefix) sort before {a, b, ...} imports.
if (LHS.Prefix.empty() != RHS.Prefix.empty())
return LHS.Prefix.empty() < RHS.Prefix.empty();
if (LHS.Prefix != RHS.Prefix)
return LHS.Prefix > RHS.Prefix;
return false;
}
// JavaScriptImportSorter sorts JavaScript ES6 imports and exports. It is
// implemented as a TokenAnalyzer because ES6 imports have substantial syntactic
// structure, making it messy to sort them using regular expressions.
class JavaScriptImportSorter : public TokenAnalyzer {
public:
JavaScriptImportSorter(const Environment &Env, const FormatStyle &Style)
: TokenAnalyzer(Env, Style),
FileContents(Env.getSourceManager().getBufferData(Env.getFileID())) {}
tooling::Replacements
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens, tooling::Replacements &Result) override {
AffectedRangeMgr.computeAffectedLines(AnnotatedLines.begin(),
AnnotatedLines.end());
const AdditionalKeywords &Keywords = Tokens.getKeywords();
SmallVector<JsModuleReference, 16> References;
AnnotatedLine *FirstNonImportLine;
std::tie(References, FirstNonImportLine) =
parseModuleReferences(Keywords, AnnotatedLines);
if (References.empty())
return Result;
SmallVector<unsigned, 16> Indices;
for (unsigned i = 0, e = References.size(); i != e; ++i)
Indices.push_back(i);
std::stable_sort(Indices.begin(), Indices.end(),
[&](unsigned LHSI, unsigned RHSI) {
return References[LHSI] < References[RHSI];
});
// FIXME: Pull this into a common function.
bool OutOfOrder = false;
for (unsigned i = 0, e = Indices.size(); i != e; ++i) {
if (i != Indices[i]) {
OutOfOrder = true;
break;
}
}
if (!OutOfOrder)
return Result;
// Replace all existing import/export statements.
std::string ReferencesText;
for (unsigned i = 0, e = Indices.size(); i != e; ++i) {
JsModuleReference Reference = References[Indices[i]];
StringRef ReferenceStmt = getSourceText(Reference.Range);
ReferencesText += ReferenceStmt;
if (i + 1 < e) {
// Insert breaks between imports and exports.
ReferencesText += "\n";
// Separate imports groups with two line breaks, but keep all exports
// in a single group.
if (!Reference.IsExport &&
(Reference.IsExport != References[Indices[i + 1]].IsExport ||
Reference.Category != References[Indices[i + 1]].Category))
ReferencesText += "\n";
}
}
// Separate references from the main code body of the file.
if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2)
ReferencesText += "\n";
SourceRange InsertionPoint = References[0].Range;
InsertionPoint.setEnd(References[References.size() - 1].Range.getEnd());
DEBUG(llvm::dbgs() << "Replacing imports:\n"
<< getSourceText(InsertionPoint) << "\nwith:\n"
<< ReferencesText << "\n");
Result.insert(tooling::Replacement(
Env.getSourceManager(), CharSourceRange::getCharRange(InsertionPoint),
ReferencesText));
return Result;
}
private:
FormatToken *Current;
FormatToken *LineEnd;
FormatToken invalidToken;
StringRef FileContents;
void skipComments() { Current = skipComments(Current); }
FormatToken *skipComments(FormatToken *Tok) {
while (Tok && Tok->is(tok::comment))
Tok = Tok->Next;
return Tok;
}
void nextToken() {
Current = Current->Next;
skipComments();
if (!Current || Current == LineEnd->Next) {
// Set the current token to an invalid token, so that further parsing on
// this line fails.
invalidToken.Tok.setKind(tok::unknown);
Current = &invalidToken;
}
}
StringRef getSourceText(SourceRange Range) {
const SourceManager &SM = Env.getSourceManager();
return FileContents.substr(SM.getFileOffset(Range.getBegin()),
SM.getFileOffset(Range.getEnd()) -
SM.getFileOffset(Range.getBegin()));
}
// Parses module references in the given lines. Returns the module references,
// and a pointer to the first "main code" line if that is adjacent to the
// affected lines of module references, nullptr otherwise.
std::pair<SmallVector<JsModuleReference, 16>, AnnotatedLine*>
parseModuleReferences(const AdditionalKeywords &Keywords,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines) {
SmallVector<JsModuleReference, 16> References;
SourceLocation Start;
bool FoundLines = false;
AnnotatedLine *FirstNonImportLine = nullptr;
for (auto Line : AnnotatedLines) {
if (!Line->Affected) {
// Only sort the first contiguous block of affected lines.
if (FoundLines)
break;
else
continue;
}
Current = Line->First;
LineEnd = Line->Last;
skipComments();
if (Start.isInvalid() || References.empty())
// After the first file level comment, consider line comments to be part
// of the import that immediately follows them by using the previously
// set Start.
Start = Line->First->Tok.getLocation();
if (!Current)
continue; // Only comments on this line.
FoundLines = true;
JsModuleReference Reference;
Reference.Range.setBegin(Start);
if (!parseModuleReference(Keywords, Reference)) {
FirstNonImportLine = Line;
break;
}
Reference.Range.setEnd(LineEnd->Tok.getEndLoc());
DEBUG({
llvm::dbgs() << "JsModuleReference: {"
<< "is_export: " << Reference.IsExport
<< ", cat: " << Reference.Category
<< ", url: " << Reference.URL
<< ", prefix: " << Reference.Prefix;
for (size_t i = 0; i < Reference.Symbols.size(); ++i)
llvm::dbgs() << ", " << Reference.Symbols[i].Symbol << " as "
<< Reference.Symbols[i].Alias;
llvm::dbgs() << ", text: " << getSourceText(Reference.Range);
llvm::dbgs() << "}\n";
});
References.push_back(Reference);
Start = SourceLocation();
}
return std::make_pair(References, FirstNonImportLine);
}
// Parses a JavaScript/ECMAScript 6 module reference.
// See http://www.ecma-international.org/ecma-262/6.0/#sec-scripts-and-modules
// for grammar EBNF (production ModuleItem).
bool parseModuleReference(const AdditionalKeywords &Keywords,
JsModuleReference &Reference) {
if (!Current || !Current->isOneOf(Keywords.kw_import, tok::kw_export))
return false;
Reference.IsExport = Current->is(tok::kw_export);
nextToken();
if (Current->isStringLiteral() && !Reference.IsExport) {
// "import 'side-effect';"
Reference.Category = JsModuleReference::ReferenceCategory::SIDE_EFFECT;
Reference.URL =
Current->TokenText.substr(1, Current->TokenText.size() - 2);
return true;
}
if (!parseModuleBindings(Keywords, Reference))
return false;
nextToken();
if (Current->is(Keywords.kw_from)) {
// imports have a 'from' clause, exports might not.
nextToken();
if (!Current->isStringLiteral())
return false;
// URL = TokenText without the quotes.
Reference.URL =
Current->TokenText.substr(1, Current->TokenText.size() - 2);
if (Reference.URL.startswith(".."))
Reference.Category =
JsModuleReference::ReferenceCategory::RELATIVE_PARENT;
else if (Reference.URL.startswith("."))
Reference.Category = JsModuleReference::ReferenceCategory::RELATIVE;
else
Reference.Category = JsModuleReference::ReferenceCategory::ABSOLUTE;
} else {
// w/o URL groups with "empty".
Reference.Category = JsModuleReference::ReferenceCategory::RELATIVE;
}
return true;
}
bool parseModuleBindings(const AdditionalKeywords &Keywords,
JsModuleReference &Reference) {
if (parseStarBinding(Keywords, Reference))
return true;
return parseNamedBindings(Keywords, Reference);
}
bool parseStarBinding(const AdditionalKeywords &Keywords,
JsModuleReference &Reference) {
// * as prefix from '...';
if (Current->isNot(tok::star))
return false;
nextToken();
if (Current->isNot(Keywords.kw_as))
return false;
nextToken();
if (Current->isNot(tok::identifier))
return false;
Reference.Prefix = Current->TokenText;
return true;
}
bool parseNamedBindings(const AdditionalKeywords &Keywords,
JsModuleReference &Reference) {
if (Current->isNot(tok::l_brace))
return false;
// {sym as alias, sym2 as ...} from '...';
nextToken();
while (true) {
if (Current->isNot(tok::identifier))
return false;
JsImportedSymbol Symbol;
Symbol.Symbol = Current->TokenText;
nextToken();
if (Current->is(Keywords.kw_as)) {
nextToken();
if (Current->isNot(tok::identifier))
return false;
Symbol.Alias = Current->TokenText;
nextToken();
}
Reference.Symbols.push_back(Symbol);
if (Current->is(tok::r_brace))
return true;
if (Current->isNot(tok::comma))
return false;
nextToken();
}
}
};
tooling::Replacements sortJavaScriptImports(const FormatStyle &Style,
StringRef Code,
ArrayRef<tooling::Range> Ranges,
StringRef FileName) {
// FIXME: Cursor support.
std::unique_ptr<Environment> Env =
Environment::CreateVirtualEnvironment(Code, FileName, Ranges);
JavaScriptImportSorter Sorter(*Env, Style);
return Sorter.process();
}
} // end namespace format
} // end namespace clang
//===--- SortJavaScriptImports.h - Sort ES6 Imports -------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements a sorter for JavaScript ES6 imports.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_FORMAT_SORTJAVASCRIPTIMPORTS_H
#define LLVM_CLANG_LIB_FORMAT_SORTJAVASCRIPTIMPORTS_H
#include "clang/Basic/LLVM.h"
#include "clang/Format/Format.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
namespace clang {
namespace format {
// Sort JavaScript ES6 imports/exports in ``Code``. The generated replacements
// only monotonically increase the length of the given code.
tooling::Replacements sortJavaScriptImports(const FormatStyle &Style,
StringRef Code,
ArrayRef<tooling::Range> Ranges,
StringRef FileName);
} // end namespace format
} // end namespace clang
#endif
//===--- TokenAnalyzer.cpp - Analyze Token Streams --------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements an abstract TokenAnalyzer and associated helper
/// classes. TokenAnalyzer can be extended to generate replacements based on
/// an annotated and pre-processed token stream.
///
//===----------------------------------------------------------------------===//
#include "TokenAnalyzer.h"
#include "AffectedRangeManager.h"
#include "Encoding.h"
#include "FormatToken.h"
#include "FormatTokenLexer.h"
#include "TokenAnnotator.h"
#include "UnwrappedLineParser.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "format-formatter"
namespace clang {
namespace format {
// This sets up an virtual file system with file \p FileName containing \p
// Code.
std::unique_ptr<Environment>
Environment::CreateVirtualEnvironment(StringRef Code, StringRef FileName,
ArrayRef<tooling::Range> Ranges) {
// This is referenced by `FileMgr` and will be released by `FileMgr` when it
// is deleted.
IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem(
new vfs::InMemoryFileSystem);
// This is passed to `SM` as reference, so the pointer has to be referenced
// in `Environment` so that `FileMgr` can out-live this function scope.
std::unique_ptr<FileManager> FileMgr(
new FileManager(FileSystemOptions(), InMemoryFileSystem));
// This is passed to `SM` as reference, so the pointer has to be referenced
// by `Environment` due to the same reason above.
std::unique_ptr<DiagnosticsEngine> Diagnostics(new DiagnosticsEngine(
IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs),
new DiagnosticOptions));
// This will be stored as reference, so the pointer has to be stored in
// due to the same reason above.
std::unique_ptr<SourceManager> VirtualSM(
new SourceManager(*Diagnostics, *FileMgr));
InMemoryFileSystem->addFile(
FileName, 0, llvm::MemoryBuffer::getMemBuffer(
Code, FileName, /*RequiresNullTerminator=*/false));
FileID ID = VirtualSM->createFileID(FileMgr->getFile(FileName),
SourceLocation(), clang::SrcMgr::C_User);
assert(ID.isValid());
SourceLocation StartOfFile = VirtualSM->getLocForStartOfFile(ID);
std::vector<CharSourceRange> CharRanges;
for (const tooling::Range &Range : Ranges) {
SourceLocation Start = StartOfFile.getLocWithOffset(Range.getOffset());
SourceLocation End = Start.getLocWithOffset(Range.getLength());
CharRanges.push_back(CharSourceRange::getCharRange(Start, End));
}
return llvm::make_unique<Environment>(ID, std::move(FileMgr),
std::move(VirtualSM),
std::move(Diagnostics), CharRanges);
}
TokenAnalyzer::TokenAnalyzer(const Environment &Env, const FormatStyle &Style)
: Style(Style), Env(Env),
AffectedRangeMgr(Env.getSourceManager(), Env.getCharRanges()),
UnwrappedLines(1),
Encoding(encoding::detectEncoding(
Env.getSourceManager().getBufferData(Env.getFileID()))) {
DEBUG(
llvm::dbgs() << "File encoding: "
<< (Encoding == encoding::Encoding_UTF8 ? "UTF8" : "unknown")
<< "\n");
DEBUG(llvm::dbgs() << "Language: " << getLanguageName(Style.Language)
<< "\n");
}
tooling::Replacements TokenAnalyzer::process() {
tooling::Replacements Result;
FormatTokenLexer Tokens(Env.getSourceManager(), Env.getFileID(), Style,
Encoding);
UnwrappedLineParser Parser(Style, Tokens.getKeywords(), Tokens.lex(), *this);
Parser.parse();
assert(UnwrappedLines.rbegin()->empty());
for (unsigned Run = 0, RunE = UnwrappedLines.size(); Run + 1 != RunE; ++Run) {
DEBUG(llvm::dbgs() << "Run " << Run << "...\n");
SmallVector<AnnotatedLine *, 16> AnnotatedLines;
TokenAnnotator Annotator(Style, Tokens.getKeywords());
for (unsigned i = 0, e = UnwrappedLines[Run].size(); i != e; ++i) {
AnnotatedLines.push_back(new AnnotatedLine(UnwrappedLines[Run][i]));
Annotator.annotate(*AnnotatedLines.back());
}
tooling::Replacements RunResult =
analyze(Annotator, AnnotatedLines, Tokens, Result);
DEBUG({
llvm::dbgs() << "Replacements for run " << Run << ":\n";
for (tooling::Replacements::iterator I = RunResult.begin(),
E = RunResult.end();
I != E; ++I) {
llvm::dbgs() << I->toString() << "\n";
}
});
for (unsigned i = 0, e = AnnotatedLines.size(); i != e; ++i) {
delete AnnotatedLines[i];
}
Result.insert(RunResult.begin(), RunResult.end());
}
return Result;
}
void TokenAnalyzer::consumeUnwrappedLine(const UnwrappedLine &TheLine) {
assert(!UnwrappedLines.empty());
UnwrappedLines.back().push_back(TheLine);
}
void TokenAnalyzer::finishRun() {
UnwrappedLines.push_back(SmallVector<UnwrappedLine, 16>());
}
} // end namespace format
} // end namespace clang
//===--- TokenAnalyzer.h - Analyze Token Streams ----------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file declares an abstract TokenAnalyzer, and associated helper
/// classes. TokenAnalyzer can be extended to generate replacements based on
/// an annotated and pre-processed token stream.
///
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_FORMAT_TOKENANALYZER_H
#define LLVM_CLANG_LIB_FORMAT_TOKENANALYZER_H
#include "AffectedRangeManager.h"
#include "Encoding.h"
#include "FormatToken.h"
#include "FormatTokenLexer.h"
#include "TokenAnnotator.h"
#include "UnwrappedLineParser.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Format/Format.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "format-formatter"
namespace clang {
namespace format {
class Environment {
public:
Environment(SourceManager &SM, FileID ID, ArrayRef<CharSourceRange> Ranges)
: ID(ID), CharRanges(Ranges.begin(), Ranges.end()), SM(SM) {}
Environment(FileID ID, std::unique_ptr<FileManager> FileMgr,
std::unique_ptr<SourceManager> VirtualSM,
std::unique_ptr<DiagnosticsEngine> Diagnostics,
const std::vector<CharSourceRange> &CharRanges)
: ID(ID), CharRanges(CharRanges.begin(), CharRanges.end()),
SM(*VirtualSM), FileMgr(std::move(FileMgr)),
VirtualSM(std::move(VirtualSM)), Diagnostics(std::move(Diagnostics)) {}
// This sets up an virtual file system with file \p FileName containing \p
// Code.
static std::unique_ptr<Environment>
CreateVirtualEnvironment(StringRef Code, StringRef FileName,
ArrayRef<tooling::Range> Ranges);
FileID getFileID() const { return ID; }
StringRef getFileName() const { return FileName; }
ArrayRef<CharSourceRange> getCharRanges() const { return CharRanges; }
const SourceManager &getSourceManager() const { return SM; }
private:
FileID ID;
StringRef FileName;
SmallVector<CharSourceRange, 8> CharRanges;
SourceManager &SM;
// The order of these fields are important - they should be in the same order
// as they are created in `CreateVirtualEnvironment` so that they can be
// deleted in the reverse order as they are created.
std::unique_ptr<FileManager> FileMgr;
std::unique_ptr<SourceManager> VirtualSM;
std::unique_ptr<DiagnosticsEngine> Diagnostics;
};
class TokenAnalyzer : public UnwrappedLineConsumer {
public:
TokenAnalyzer(const Environment &Env, const FormatStyle &Style);
tooling::Replacements process();
protected:
virtual tooling::Replacements
analyze(TokenAnnotator &Annotator,
SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
FormatTokenLexer &Tokens, tooling::Replacements &Result) = 0;
void consumeUnwrappedLine(const UnwrappedLine &TheLine) override;
void finishRun() override;
FormatStyle Style;
// Stores Style, FileID and SourceManager etc.
const Environment &Env;
// AffectedRangeMgr stores ranges to be fixed.
AffectedRangeManager AffectedRangeMgr;
SmallVector<SmallVector<UnwrappedLine, 16>, 2> UnwrappedLines;
encoding::Encoding Encoding;
};
} // end namespace format
} // end namespace clang
#endif
......@@ -9,6 +9,7 @@ add_clang_unittest(FormatTests
FormatTestJS.cpp
FormatTestProto.cpp
FormatTestSelective.cpp
SortImportsTestJS.cpp
SortIncludesTest.cpp
)
......
//===- unittest/Format/SortImportsTestJS.cpp - JS import sort unit tests --===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "FormatTestUtils.h"
#include "clang/Format/Format.h"
#include "llvm/Support/Debug.h"
#include "gtest/gtest.h"
#define DEBUG_TYPE "format-test"
namespace clang {
namespace format {
namespace {
class SortImportsTestJS : public ::testing::Test {
protected:
std::string sort(StringRef Code, unsigned Offset = 0, unsigned Length = 0) {
StringRef FileName = "input.js";
if (Length == 0U)
Length = Code.size() - Offset;
std::vector<tooling::Range> Ranges(1, tooling::Range(Offset, Length));
std::string Sorted =
applyAllReplacements(Code, sortIncludes(Style, Code, Ranges, FileName));
return applyAllReplacements(Sorted,
reformat(Style, Sorted, Ranges, FileName));
}
void verifySort(llvm::StringRef Expected, llvm::StringRef Code,
unsigned Offset = 0, unsigned Length = 0) {
std::string Result = sort(Code, Offset, Length);
EXPECT_EQ(Expected.str(), Result) << "Expected:\n"
<< Expected << "\nActual:\n"
<< Result;
}
FormatStyle Style = getGoogleStyle(FormatStyle::LK_JavaScript);
};
TEST_F(SortImportsTestJS, AlreadySorted) {
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;",
"import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, BasicSorting) {
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;",
"import {sym} from 'a';\n"
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, SeparateMainCodeBody) {
verifySort("import {sym} from 'a';"
"\n"
"let x = 1;\n",
"import {sym} from 'a'; let x = 1;\n");
}
TEST_F(SortImportsTestJS, Comments) {
verifySort("/** @fileoverview This is a great file. */\n"
"// A very important import follows.\n"
"import {sym} from 'a'; /* more comments */\n"
"import {sym} from 'b'; // from //foo:bar\n",
"/** @fileoverview This is a great file. */\n"
"import {sym} from 'b'; // from //foo:bar\n"
"// A very important import follows.\n"
"import {sym} from 'a'; /* more comments */\n");
}
TEST_F(SortImportsTestJS, SortStar) {
verifySort("import * as foo from 'a';\n"
"import {sym} from 'a';\n"
"import * as bar from 'b';\n",
"import {sym} from 'a';\n"
"import * as foo from 'a';\n"
"import * as bar from 'b';\n");
}
TEST_F(SortImportsTestJS, AliasesSymbols) {
verifySort("import {sym1 as alias1} from 'b';\n"
"import {sym2 as alias2, sym3 as alias3} from 'c';\n",
"import {sym2 as alias2, sym3 as alias3} from 'c';\n"
"import {sym1 as alias1} from 'b';\n");
}
TEST_F(SortImportsTestJS, GroupImports) {
verifySort("import {a} from 'absolute';\n"
"\n"
"import {b} from '../parent';\n"
"import {b} from '../parent/nested';\n"
"\n"
"import {b} from './relative/path';\n"
"import {b} from './relative/path/nested';\n"
"\n"
"let x = 1;\n",
"import {b} from './relative/path/nested';\n"
"import {b} from './relative/path';\n"
"import {b} from '../parent/nested';\n"
"import {b} from '../parent';\n"
"import {a} from 'absolute';\n"
"let x = 1;\n");
}
TEST_F(SortImportsTestJS, Exports) {
verifySort("import {S} from 'bpath';\n"
"\n"
"import {T} from './cpath';\n"
"\n"
"export {A, B} from 'apath';\n"
"export {P} from '../parent';\n"
"export {R} from './relative';\n"
"export {S};\n"
"\n"
"let x = 1;\n"
"export y = 1;\n",
"export {R} from './relative';\n"
"import {T} from './cpath';\n"
"export {S};\n"
"export {A, B} from 'apath';\n"
"import {S} from 'bpath';\n"
"export {P} from '../parent';\n"
"let x = 1;\n"
"export y = 1;\n");
verifySort("import {S} from 'bpath';\n"
"\n"
"export {T} from 'epath';\n",
"export {T} from 'epath';\n"
"import {S} from 'bpath';\n");
}
TEST_F(SortImportsTestJS, SideEffectImports) {
verifySort("import 'ZZside-effect';\n"
"import 'AAside-effect';\n"
"\n"
"import {A} from 'absolute';\n"
"\n"
"import {R} from './relative';\n",
"import {R} from './relative';\n"
"import 'ZZside-effect';\n"
"import {A} from 'absolute';\n"
"import 'AAside-effect';\n");
}
TEST_F(SortImportsTestJS, AffectedRange) {
// Sort excluding a suffix.
verifySort("import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"import {sym} from 'a';\n"
"let x = 1;",
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"let x = 1;",
0, 30);
// Sort excluding a prefix.
verifySort("import {sym} from 'c';\n"
"import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"\n"
"let x = 1;",
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"\n"
"let x = 1;",
30, 0);
// Sort a range within imports.
verifySort("import {sym} from 'c';\n"
"import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"let x = 1;",
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"import {sym} from 'c';\n"
"let x = 1;",
24, 30);
}
} // end namespace
} // end namespace format
} // end namespace clang
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment