Index: include/clang/Format/Format.h =================================================================== --- include/clang/Format/Format.h +++ include/clang/Format/Format.h @@ -766,6 +766,12 @@ const tooling::Replacements &Replaces, const FormatStyle &Style); +/// \brief Returns the replacements corresponding to applying, fixing, and +/// reformatting \p Replaces. +tooling::Replacements fixReplacements(StringRef Code, + const tooling::Replacements &Replaces, + const FormatStyle &Style); + /// \brief Reformats the given \p Ranges in the file \p ID. /// /// Each range is extended on either end to its next bigger logic unit, i.e. @@ -791,6 +797,14 @@ StringRef FileName = "", bool *IncompleteFormat = nullptr); +/// \brief Reformats the given \p Ranges in \p Code. +/// +/// Otherwise identical to the reformat() function using a file ID. +tooling::Replacements fix(const FormatStyle &Style, StringRef Code, + ArrayRef Ranges, + StringRef FileName = "", + bool *IncompleteFormat = nullptr); + /// \brief Returns the ``LangOpts`` that the formatter expects you to set. /// /// \param Style determines specific settings for lexing mode. Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp +++ lib/Format/Format.cpp @@ -1443,6 +1443,477 @@ } } +// Returns true if 'Range' intersects with one of the input ranges. +static bool affectsCharSourceRange(SourceManager &SourceMgr, + SmallVector Ranges, + const CharSourceRange &Range) { + for (SmallVectorImpl::const_iterator I = Ranges.begin(), + E = Ranges.end(); + I != E; ++I) { + if (!SourceMgr.isBeforeInTranslationUnit(Range.getEnd(), I->getBegin()) && + !SourceMgr.isBeforeInTranslationUnit(I->getEnd(), Range.getBegin())) + return true; + } + return false; +} + +// Returns true if the range from 'First' to 'Last' intersects with one of the +// input ranges. +static bool affectsTokenRange(SourceManager &SourceMgr, + SmallVector Ranges, + const FormatToken &First, const FormatToken &Last, + bool IncludeLeadingNewlines) { + SourceLocation Start = First.WhitespaceRange.getBegin(); + if (!IncludeLeadingNewlines) + Start = Start.getLocWithOffset(First.LastNewlineOffset); + SourceLocation End = Last.getStartOfNonWhitespace(); + End = End.getLocWithOffset(Last.TokenText.size()); + CharSourceRange Range = CharSourceRange::getCharRange(Start, End); + return affectsCharSourceRange(SourceMgr, Ranges, Range); +} + +// Returns true if one of the input ranges intersect the leading empty lines +// before 'Tok'. +static bool affectsLeadingEmptyLines(SourceManager &SourceMgr, + SmallVector Ranges, + const FormatToken &Tok) { + CharSourceRange EmptyLineRange = CharSourceRange::getCharRange( + Tok.WhitespaceRange.getBegin(), + Tok.WhitespaceRange.getBegin().getLocWithOffset(Tok.LastNewlineOffset)); + return affectsCharSourceRange(SourceMgr, Ranges, EmptyLineRange); +} + +// Marks all lines between I and E as well as all their children as affected. +static void markAllAsAffected(SmallVectorImpl::iterator I, + SmallVectorImpl::iterator E) { + while (I != E) { + (*I)->Affected = true; + markAllAsAffected((*I)->Children.begin(), (*I)->Children.end()); + ++I; + } +} + +static bool computeAffectedLines(SourceManager &SourceMgr, + SmallVector Ranges, + SmallVectorImpl::iterator I, + SmallVectorImpl::iterator E); + +// Determines whether 'Line' is affected by the SourceRanges given as input. +// Returns \c true if line or one if its children is affected. +static bool nonPPLineAffected(SourceManager &SourceMgr, + SmallVector Ranges, + AnnotatedLine *Line, + const AnnotatedLine *PreviousLine) { + bool SomeLineAffected = false; + Line->ChildrenAffected = computeAffectedLines( + SourceMgr, Ranges, Line->Children.begin(), Line->Children.end()); + if (Line->ChildrenAffected) + SomeLineAffected = true; + + // Stores whether one of the line's tokens is directly affected. + bool SomeTokenAffected = false; + // Stores whether we need to look at the leading newlines of the next token + // in order to determine whether it was affected. + bool IncludeLeadingNewlines = false; + + // Stores whether the first child line of any of this line's tokens is + // affected. + bool SomeFirstChildAffected = false; + + for (FormatToken *Tok = Line->First; Tok; Tok = Tok->Next) { + // Determine whether 'Tok' was affected. + if (affectsTokenRange(SourceMgr, Ranges, *Tok, *Tok, + IncludeLeadingNewlines)) + SomeTokenAffected = true; + + // Determine whether the first child of 'Tok' was affected. + if (!Tok->Children.empty() && Tok->Children.front()->Affected) + SomeFirstChildAffected = true; + + IncludeLeadingNewlines = Tok->Children.empty(); + } + + // Was this line moved, i.e. has it previously been on the same line as an + // affected line? + bool LineMoved = PreviousLine && PreviousLine->Affected && + Line->First->NewlinesBefore == 0; + + bool IsContinuedComment = + Line->First->is(tok::comment) && Line->First->Next == nullptr && + Line->First->NewlinesBefore < 2 && PreviousLine && + PreviousLine->Affected && PreviousLine->Last->is(tok::comment); + + if (SomeTokenAffected || SomeFirstChildAffected || LineMoved || + IsContinuedComment) { + Line->Affected = true; + SomeLineAffected = true; + } + return SomeLineAffected; +} + +// Determines which lines are affected by the SourceRanges given as input. +// Returns \c true if at least one line between I and E or one of their +// children is affected. +static bool computeAffectedLines(SourceManager &SourceMgr, + SmallVector Ranges, + SmallVectorImpl::iterator I, + SmallVectorImpl::iterator E) { + bool SomeLineAffected = false; + const AnnotatedLine *PreviousLine = nullptr; + while (I != E) { + AnnotatedLine *Line = *I; + Line->LeadingEmptyLinesAffected = + affectsLeadingEmptyLines(SourceMgr, Ranges, *Line->First); + + // If a line is part of a preprocessor directive, it needs to be formatted + // if any token within the directive is affected. + if (Line->InPPDirective) { + FormatToken *Last = Line->Last; + SmallVectorImpl::iterator PPEnd = I + 1; + while (PPEnd != E && !(*PPEnd)->First->HasUnescapedNewline) { + Last = (*PPEnd)->Last; + ++PPEnd; + } + + if (affectsTokenRange(SourceMgr, Ranges, *Line->First, *Last, + /*IncludeLeadingNewlines=*/false)) { + SomeLineAffected = true; + markAllAsAffected(I, PPEnd); + } + I = PPEnd; + continue; + } + + if (nonPPLineAffected(SourceMgr, Ranges, Line, PreviousLine)) + SomeLineAffected = true; + + PreviousLine = Line; + ++I; + } + return SomeLineAffected; +} + +class Fixer : public UnwrappedLineConsumer { +public: + Fixer(const FormatStyle &Style, SourceManager &SourceMgr, FileID ID, + ArrayRef Ranges) + : Style(Style), ID(ID), SourceMgr(SourceMgr), + Ranges(Ranges.begin(), Ranges.end()), UnwrappedLines(1), + Encoding(encoding::detectEncoding(SourceMgr.getBufferData(ID))), + RedundantTokens(FormatTokenLess(SourceMgr)) { + DummyToken.Tok.setKind(tok::unknown); + } + + tooling::Replacements fix(bool *IncompleteFormat) { + tooling::Replacements Result; + FormatTokenLexer Tokens(SourceMgr, ID, Style, Encoding); + + UnwrappedLineParser Parser(Style, Tokens.getKeywords(), Tokens.lex(), + *this); + Parser.parse(); + assert(UnwrappedLines.rbegin()->empty()); + + TokenAnnotator Annotator(Style, Tokens.getKeywords()); + for (unsigned Run = 0, RunE = UnwrappedLines.size(); Run + 1 != RunE; + ++Run) { + DEBUG(llvm::dbgs() << "Run " << Run << "...\n"); + for (unsigned i = 0, e = UnwrappedLines[Run].size(); i != e; ++i) { + AnnotatedLines.push_back(new AnnotatedLine(UnwrappedLines[Run][i])); + Annotator.annotate(*AnnotatedLines.back()); + } + // FIXME: in the current implementation the granularity of affected range + // is an annotated line. However, this is not sufficient. Furthermore, + // redundant code introduced by replacements does not necessarily + // intercept with ranges of replacements that result in the redundancy. + // To determine if some redundant code is actually introduced by + // replacements(e.g. deletions), we need to come up with a more + // sophisticated way of computing affected ranges. + computeAffectedLines(SourceMgr, Ranges, AnnotatedLines.begin(), + AnnotatedLines.end()); + + tooling::Replacements RunResult = fix(Tokens, IncompleteFormat); + 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; + } + + tooling::Replacements fix(FormatTokenLexer &Tokens, bool *IncompleteFormat) { + reset(); + + while (CurrentToken->isNot(tok::eof)) { + switch (CurrentToken->Tok.getKind()) { + case tok::colon: + if (CurrentToken->Type == TT_CtorInitializerColon) { + checkConstructorInitList(); + } else { + nextToken(); + } + break; + case tok::kw_namespace: + // Ignore using namespace ...; + if (!(CurrentToken->Previous && + CurrentToken->Previous->is(tok::kw_using))) { + checkEmptyNamespace(); + } + nextToken(); + break; + default: + nextToken(); + break; + } + } + + return generateFixes(); + } + +private: + void consumeUnwrappedLine(const UnwrappedLine &TheLine) override { + assert(!UnwrappedLines.empty()); + UnwrappedLines.back().push_back(TheLine); + } + + void finishRun() override { + UnwrappedLines.push_back(SmallVector()); + } + + void reset() { + CurrentLine = 0; + LastToken = nullptr; + // Make LastTokenNotDeleted point somewhere in the beginning so that we + // don't need to check nullptr every time we access it. + LastTokenNotDeleted = &DummyToken; + CurrentToken = AnnotatedLines[CurrentLine]->First; + } + + inline void deleteToken(FormatToken *Tok) { + // FIXME: we need better way to determine wether to delete this token. + if (!AnnotatedLines[CurrentLine]->Affected) return; + Tok->Deleted = true; + RedundantTokens.insert(Tok); + } + + void nextToken() { + if (CurrentToken->is(tok::eof)) return; + //llvm::errs() << "Current token is " << CurrentToken->Tok.getName() << "\n"; + LastToken = CurrentToken; + if (!LastToken->Deleted && LastToken->isNot(tok::comment)) { + LastTokenNotDeleted = LastToken; + } + if (CurrentToken->Next) { + CurrentToken = CurrentToken->Next; + return; + } + + ++CurrentLine; + assert(CurrentLine < AnnotatedLines.size()); + CurrentToken = AnnotatedLines[CurrentLine]->First; + LastToken->Next = CurrentToken; + assert(CurrentToken); + } + + void skipParens() { + assert(CurrentToken->is(tok::l_paren)); + nextToken(); + while (CurrentToken->isNot(tok::eof)) { + switch (CurrentToken->Tok.getKind()) { + case tok::l_paren: + skipParens(); + break; + case tok::r_paren: + return; + default: + break; + } + nextToken(); + } + } + + tooling::Replacements generateFixes() { + tooling::Replacements Fixes; + std::vector Tokens; + std::copy(RedundantTokens.begin(), RedundantTokens.end(), + std::back_inserter(Tokens)); + unsigned Idx = 0; + while (Idx < Tokens.size()) { + unsigned St = Idx, Ed = Idx; + while ((Ed + 1) < Tokens.size() && Tokens[Ed]->Next == Tokens[Ed + 1]) { + Ed++; + } + auto SR = CharSourceRange::getCharRange(Tokens[St]->Tok.getLocation(), + Tokens[Ed]->Tok.getEndLoc()); + Fixes.insert(tooling::Replacement(SourceMgr, SR, "")); + Idx = Ed + 1; + } + return Fixes; + } + + void checkConstructorInitList() { + assert(CurrentToken->Type == TT_CtorInitializerColon && + "Expect TT_CtorInitializerColon Token!"); + FormatToken *CtorInitColonTok = CurrentToken; + nextToken(); + bool IsListEmpty = true; + bool Done = false; + // This vector stores comments between the last token not deleted and the + // current token. + SmallVector Comments; + while (!Done && CurrentToken->isNot(tok::eof)) { + switch (CurrentToken->Tok.getKind()) { + case tok::comma: + if (LastTokenNotDeleted->isOneOf(tok::comma, tok::colon)) { + deleteToken(CurrentToken); + // If there is a new line before the deleted comma, the comment may + // belong to the previous token. + if (!CurrentToken->HasUnescapedNewline) { + for (auto *Comment : Comments) { + deleteToken(Comment); + } + } + } + break; + case tok::l_paren: + // We need to skip a pair of parentheses here because it is possible + // that "({ ... })" appears in the initialization list, and we do not + // want to return when we get the "{" in the parentheses. + skipParens(); + break; + case tok::l_brace: + if (LastTokenNotDeleted->is(tok::comma)) { + deleteToken(LastTokenNotDeleted); + for (auto *Comment : Comments) { + deleteToken(Comment); + } + // FIXME: LastTokenNotDeleted should be the token before it now, but + // we do not have a pointer to it. Now we make it point to DummyToken + // in this case since we are finishing the checking. + LastTokenNotDeleted = &DummyToken; + } + Done = true; + break; + case tok::comment: + // If the last deleted token is followed by a comment "//...", then we + // delete the comment as well. + if (LastToken->Deleted && CurrentToken->TokenText.startswith("//")) { + deleteToken(CurrentToken); + } else { + Comments.push_back(CurrentToken); + } + break; + default: + IsListEmpty = false; + break; + } + if (!Done) { + if (CurrentToken->isNot(tok::comment)) { + Comments.clear(); + } + nextToken(); + } + } + if (IsListEmpty) { + // FIXME: LastTokenNotDeleted might also be affected. But since we are at + // the end of the check. This can be ignored now. + deleteToken(CtorInitColonTok); + } + } + + // Check if a namespace is empty (i.e. contains nothing other than comments or + // nested namespace). + // + // Returns true if the namespace is empty . + bool checkEmptyNamespace() { + assert(CurrentToken->is(tok::kw_namespace)); + + FormatToken *InitialTok = + (CurrentToken->Previous && CurrentToken->Previous->is(tok::kw_inline)) + ? CurrentToken->Previous + : CurrentToken; + + nextToken(); + while (CurrentToken->isOneOf(tok::identifier, tok::coloncolon)) { + nextToken(); + } + + if (CurrentToken->isNot(tok::l_brace)) + return false; + + nextToken(); + bool IsEmpty = true; + // BracesDepth is used to find the matching brace. + // +1 on tok::l_brace; -1 on tok::r_brace. + int BracesDepth = 1; + while (BracesDepth > 0 && CurrentToken->isNot(tok::eof)) { + switch (CurrentToken->Tok.getKind()) { + case tok::kw_namespace: + IsEmpty = checkEmptyNamespace() && IsEmpty; + break; + case tok::l_brace: + BracesDepth++; + break; + case tok::r_brace: + BracesDepth--; + break; + default: + if (CurrentToken->isNot(tok::comment)) { + IsEmpty = false; + } + } + if (BracesDepth > 0) { + nextToken(); + } + } + + if (CurrentToken->is(tok::eof) || !IsEmpty) + return false; + + if (CurrentToken->Next && CurrentToken->Next->is(tok::comment) && + !CurrentToken->Next->HasUnescapedNewline) + nextToken(); + + for (FormatToken *Tok = InitialTok; Tok != CurrentToken->Next; + Tok = Tok->Next) { + deleteToken(Tok); + } + return true; + } + + struct FormatTokenLess { + FormatTokenLess(SourceManager &SM) : SM(SM) {} + + bool operator()(const FormatToken *LHS, const FormatToken *RHS) { + return SM.isBeforeInTranslationUnit(LHS->Tok.getLocation(), + RHS->Tok.getLocation()); + } + + SourceManager &SM; + }; + + FormatStyle Style; + FileID ID; + SourceManager &SourceMgr; + SmallVector Ranges; + SmallVector, 2> UnwrappedLines; + encoding::Encoding Encoding; + SmallVector AnnotatedLines; + size_t CurrentLine; + FormatToken DummyToken; + FormatToken *LastToken; + FormatToken *LastTokenNotDeleted; + FormatToken *CurrentToken; + std::set RedundantTokens; +}; + class Formatter : public UnwrappedLineConsumer { public: Formatter(const FormatStyle &Style, SourceManager &SourceMgr, FileID ID, @@ -1503,7 +1974,8 @@ Annotator.annotate(*AnnotatedLines[i]); } deriveLocalStyle(AnnotatedLines); - computeAffectedLines(AnnotatedLines.begin(), AnnotatedLines.end()); + computeAffectedLines(SourceMgr, Ranges, AnnotatedLines.begin(), + AnnotatedLines.end()); if (Style.Language == FormatStyle::LK_JavaScript && Style.JavaScriptQuotes != FormatStyle::JSQS_Leave) requoteJSStringLiteral(AnnotatedLines, Result); @@ -1523,45 +1995,6 @@ } private: - // Determines which lines are affected by the SourceRanges given as input. - // Returns \c true if at least one line between I and E or one of their - // children is affected. - bool computeAffectedLines(SmallVectorImpl::iterator I, - SmallVectorImpl::iterator E) { - bool SomeLineAffected = false; - const AnnotatedLine *PreviousLine = nullptr; - while (I != E) { - AnnotatedLine *Line = *I; - Line->LeadingEmptyLinesAffected = affectsLeadingEmptyLines(*Line->First); - - // If a line is part of a preprocessor directive, it needs to be formatted - // if any token within the directive is affected. - if (Line->InPPDirective) { - FormatToken *Last = Line->Last; - SmallVectorImpl::iterator PPEnd = I + 1; - while (PPEnd != E && !(*PPEnd)->First->HasUnescapedNewline) { - Last = (*PPEnd)->Last; - ++PPEnd; - } - - if (affectsTokenRange(*Line->First, *Last, - /*IncludeLeadingNewlines=*/false)) { - SomeLineAffected = true; - markAllAsAffected(I, PPEnd); - } - I = PPEnd; - continue; - } - - if (nonPPLineAffected(Line, PreviousLine)) - SomeLineAffected = true; - - PreviousLine = Line; - ++I; - } - return SomeLineAffected; - } - // If the last token is a double/single-quoted string literal, generates a // replacement with a single/double quoted string literal, re-escaping the // contents in the process. @@ -1638,101 +2071,6 @@ } } - - // Determines whether 'Line' is affected by the SourceRanges given as input. - // Returns \c true if line or one if its children is affected. - bool nonPPLineAffected(AnnotatedLine *Line, - const AnnotatedLine *PreviousLine) { - bool SomeLineAffected = false; - Line->ChildrenAffected = - computeAffectedLines(Line->Children.begin(), Line->Children.end()); - if (Line->ChildrenAffected) - SomeLineAffected = true; - - // Stores whether one of the line's tokens is directly affected. - bool SomeTokenAffected = false; - // Stores whether we need to look at the leading newlines of the next token - // in order to determine whether it was affected. - bool IncludeLeadingNewlines = false; - - // Stores whether the first child line of any of this line's tokens is - // affected. - bool SomeFirstChildAffected = false; - - for (FormatToken *Tok = Line->First; Tok; Tok = Tok->Next) { - // Determine whether 'Tok' was affected. - if (affectsTokenRange(*Tok, *Tok, IncludeLeadingNewlines)) - SomeTokenAffected = true; - - // Determine whether the first child of 'Tok' was affected. - if (!Tok->Children.empty() && Tok->Children.front()->Affected) - SomeFirstChildAffected = true; - - IncludeLeadingNewlines = Tok->Children.empty(); - } - - // Was this line moved, i.e. has it previously been on the same line as an - // affected line? - bool LineMoved = PreviousLine && PreviousLine->Affected && - Line->First->NewlinesBefore == 0; - - bool IsContinuedComment = - Line->First->is(tok::comment) && Line->First->Next == nullptr && - Line->First->NewlinesBefore < 2 && PreviousLine && - PreviousLine->Affected && PreviousLine->Last->is(tok::comment); - - if (SomeTokenAffected || SomeFirstChildAffected || LineMoved || - IsContinuedComment) { - Line->Affected = true; - SomeLineAffected = true; - } - return SomeLineAffected; - } - - // Marks all lines between I and E as well as all their children as affected. - void markAllAsAffected(SmallVectorImpl::iterator I, - SmallVectorImpl::iterator E) { - while (I != E) { - (*I)->Affected = true; - markAllAsAffected((*I)->Children.begin(), (*I)->Children.end()); - ++I; - } - } - - // Returns true if the range from 'First' to 'Last' intersects with one of the - // input ranges. - bool affectsTokenRange(const FormatToken &First, const FormatToken &Last, - bool IncludeLeadingNewlines) { - SourceLocation Start = First.WhitespaceRange.getBegin(); - if (!IncludeLeadingNewlines) - Start = Start.getLocWithOffset(First.LastNewlineOffset); - SourceLocation End = Last.getStartOfNonWhitespace(); - End = End.getLocWithOffset(Last.TokenText.size()); - CharSourceRange Range = CharSourceRange::getCharRange(Start, End); - return affectsCharSourceRange(Range); - } - - // Returns true if one of the input ranges intersect the leading empty lines - // before 'Tok'. - bool affectsLeadingEmptyLines(const FormatToken &Tok) { - CharSourceRange EmptyLineRange = CharSourceRange::getCharRange( - Tok.WhitespaceRange.getBegin(), - Tok.WhitespaceRange.getBegin().getLocWithOffset(Tok.LastNewlineOffset)); - return affectsCharSourceRange(EmptyLineRange); - } - - // Returns true if 'Range' intersects with one of the input ranges. - bool affectsCharSourceRange(const CharSourceRange &Range) { - for (SmallVectorImpl::const_iterator I = Ranges.begin(), - E = Ranges.end(); - I != E; ++I) { - if (!SourceMgr.isBeforeInTranslationUnit(Range.getEnd(), I->getBegin()) && - !SourceMgr.isBeforeInTranslationUnit(I->getEnd(), Range.getBegin())) - return true; - } - return false; - } - static bool inputUsesCRLF(StringRef Text) { return Text.count('\r') * 2 > Text.count('\n'); } @@ -2009,6 +2347,25 @@ return MergedReplacements; } +tooling::Replacements fixReplacements(StringRef Code, + const tooling::Replacements &Replaces, + const FormatStyle &Style) { + if (Replaces.empty()) + return tooling::Replacements(); + + std::string NewCode = applyAllReplacements(Code, Replaces); + std::vector ChangedRanges = + tooling::calculateChangedRanges(Replaces); + StringRef FileName = Replaces.begin()->getFilePath(); + tooling::Replacements FixedReplaces = + fix(Style, NewCode, ChangedRanges, FileName); + + tooling::Replacements MergedReplacements = + mergeReplacements(Replaces, FixedReplaces); + + return formatReplacements(Code, MergedReplacements, Style); +} + tooling::Replacements reformat(const FormatStyle &Style, SourceManager &SourceMgr, FileID ID, ArrayRef Ranges, @@ -2048,6 +2405,36 @@ return reformat(Style, SourceMgr, ID, CharRanges, IncompleteFormat); } +tooling::Replacements fix(const FormatStyle &Style, StringRef Code, + ArrayRef Ranges, StringRef FileName, + bool *IncompleteFormat) { + if (Style.DisableFormat) + return tooling::Replacements(); + + IntrusiveRefCntPtr InMemoryFileSystem( + new vfs::InMemoryFileSystem); + FileManager Files(FileSystemOptions(), InMemoryFileSystem); + DiagnosticsEngine Diagnostics( + IntrusiveRefCntPtr(new DiagnosticIDs), + new DiagnosticOptions); + SourceManager SourceMgr(Diagnostics, Files); + InMemoryFileSystem->addFile( + FileName, 0, llvm::MemoryBuffer::getMemBuffer( + Code, FileName, /*RequiresNullTerminator=*/false)); + FileID ID = SourceMgr.createFileID(Files.getFile(FileName), SourceLocation(), + clang::SrcMgr::C_User); + SourceLocation StartOfFile = SourceMgr.getLocForStartOfFile(ID); + std::vector 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)); + } + + Fixer Fix(Style, SourceMgr, ID, CharRanges); + return Fix.fix(IncompleteFormat); +} + LangOptions getFormattingLangOpts(const FormatStyle &Style) { LangOptions LangOpts; LangOpts.CPlusPlus = 1; Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -279,6 +279,9 @@ /// changes. bool Finalized = false; + // \brief Fixer will set this to true if the token is going to be deleted. + bool Deleted = false; + bool is(tok::TokenKind Kind) const { return Tok.is(Kind); } bool is(TokenType TT) const { return Type == TT; } bool is(const IdentifierInfo *II) const { Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -11233,6 +11233,151 @@ #endif // _MSC_VER +class FixTest : public ::testing::Test { +protected: + std::string fix(llvm::StringRef Code, + const std::vector &Ranges, + const FormatStyle &Style = getLLVMStyle()) { + DEBUG(llvm::errs() << "---\n"); + DEBUG(llvm::errs() << Code << "\n\n"); + tooling::Replacements Replaces = format::fix(Style, Code, Ranges); + + std::string Result = applyAllReplacements(Code, Replaces); + EXPECT_NE("", Result); + DEBUG(llvm::errs() << "\n" << Result << "\n\n"); + return Result; + } +}; + +TEST_F(FixTest, CtorInitializationSimpleRedundantComma) { + std::string Code = "class A {\nA() : , {} };"; + std::string Expected = "class A {\nA() {} };"; + std::vector Ranges; + Ranges.push_back(tooling::Range(17, 0)); + Ranges.push_back(tooling::Range(19, 0)); + std::string Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + Code = "class A {\nA() : x(1), {} };"; + Expected = "class A {\nA() : x(1) {} };"; + Ranges.clear(); + Ranges.push_back(tooling::Range(23, 0)); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, CtorInitializationBracesInParens) { + std::string Code = "class A {\nA() : x({1}),, {} };"; + std::string Expected = "class A {\nA() : x({1}) {} };"; + std::vector Ranges; + Ranges.push_back(tooling::Range(24, 0)); + Ranges.push_back(tooling::Range(26, 0)); + std::string Result = fix(Code, Ranges); + DEBUG(llvm::errs() << "\n" << Result << "\n"); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, RedundantCommaNotInAffectedRanges) { + std::string Code = + "class A {\nA() : x({1}), /* comment */, { int x = 0; } };"; + std::string Expected = + "class A {\nA() : x({1}), /* comment */, { int x = 0; } };"; + // Set the affected range to be "int x = 0", which does not intercept the + // constructor initialization list. + std::vector Ranges(1, tooling::Range(42, 9)); + std::string Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + Code = "class A {\nA() : x(1), {} };"; + Expected = "class A {\nA() : x(1), {} };"; + // No range. Fixer should do nothing. + Ranges.clear(); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, CtorInitializationCommentAroundCommas) { + // Remove redundant commas and comment between them. + std::string Code = "class A {\nA() : x({1}), /* comment */, {} };"; + std::string Expected = "class A {\nA() : x({1}) {} };"; + std::vector Ranges; + Ranges.push_back(tooling::Range(25, 0)); + Ranges.push_back(tooling::Range(40, 0)); + std::string Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + // Remove trailing comma and comment. + Code = "class A {\nA() : x({1}), // comment\n{} };"; + Expected = "class A {\nA() : x({1})\n{} };"; + Ranges = std::vector(1, tooling::Range(25, 0)); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + // Remove trailing comma, but leave the comment. + Code = "class A {\nA() : x({1}), // comment\n , y(1),{} };"; + Expected = "class A {\nA() : x({1}), // comment\n y(1){} };"; + Ranges = std::vector(1, tooling::Range(38, 0)); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + // Remove trailing comma and the comment before it. + Code = "class A {\nA() : x({1}), \n/* comment */, y(1),{} };"; + Expected = "class A {\nA() : x({1}), \n y(1){} };"; + Ranges = std::vector(1, tooling::Range(40, 0)); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); + + // Remove trailing comma and the comment after it. + Code = "class A {\nA() : , // comment\n y(1),{} };"; + Expected = "class A {\nA() : \n y(1){} };"; + Ranges = std::vector(1, tooling::Range(17, 0)); + Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, SkipImbalancedParentheses) { + std::string Code = "class A {\nA() : x((),, {} };"; + std::string Expected = "class A {\nA() : x((),, {} };"; + std::vector Ranges(1, tooling::Range(0, Code.size())); + std::string Result = fix(Code, Ranges); + DEBUG(llvm::errs() << "\n" << Result << "\n"); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, DeleteEmptyNamespaces) { + std::string Code = "namespace A {\n" + "namespace B {\n" + "} // namespace B\n" + "} // namespace A\n\n" + "namespace C {\n" + "namespace D { int i; }\n" + "inline namespace E { namespace { } }\n" + "}"; + std::string Expected = "\n\nnamespace C {\n" + "namespace D { int i; }\n\n" + "}"; + std::vector Ranges(1, tooling::Range(0, Code.size())); + std::string Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); +} + +TEST_F(FixTest, NamespaceWithSyntaxError) { + std::string Code = "namespace A {\n" + "namespace B {\n" // missing r_brace + "} // namespace A\n\n" + "namespace C {\n" + "namespace D int i; }\n" + "inline namespace E { namespace { } }\n" + "}"; + std::string Expected = "namespace A {\n" + "\n\nnamespace C {\n" + "namespace D int i; }\n\n" + "}"; + std::vector Ranges(1, tooling::Range(0, Code.size())); + std::string Result = fix(Code, Ranges); + EXPECT_EQ(Expected, Result); +} + class ReplacementTest : public ::testing::Test { protected: tooling::Replacement createReplacement(SourceLocation Start, unsigned Length, @@ -11274,6 +11419,42 @@ Code, formatReplacements(Code, Replaces, Style))); } +TEST_F(ReplacementTest, FixCodeAfterReplacements) { + std::string Code = "class A {\n" + " A() : X(0), Y(0) { }\n" + "};"; + std::string Expected = "class A {\n" + " A() : X(0) {}\n" + "};"; + FileID ID = Context.createInMemoryFile("format.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement( + Context.Sources, Context.getLocation(ID, 2, 15), 4, "")); + + format::FormatStyle Style = format::getLLVMStyle(); + EXPECT_EQ(Expected, + applyAllReplacements(Code, fixReplacements(Code, Replaces, Style))); +} + +TEST_F(ReplacementTest, DoNotReformatCodeNotAffectedByReplacements) { + std::string Code = "class A {\n" + " A() : X(0), Y(0) {}\n" + " f( int x) = 0;" + "};"; + std::string Expected = "class A {\n" + " A() : X(0) {}\n" + " f( int x) = 0;" + "};"; + FileID ID = Context.createInMemoryFile("format.cpp", Code); + tooling::Replacements Replaces; + Replaces.insert(tooling::Replacement(Context.Sources, + Context.getLocation(ID, 2, 15), 4, "")); + + format::FormatStyle Style = format::getLLVMStyle(); + EXPECT_EQ(Expected, + applyAllReplacements(Code, fixReplacements(Code, Replaces, Style))); +} + } // end namespace } // end namespace format } // end namespace clang