diff options
Diffstat (limited to 'lib/Edit')
-rw-r--r-- | lib/Edit/CMakeLists.txt | 7 | ||||
-rw-r--r-- | lib/Edit/Commit.cpp | 345 | ||||
-rw-r--r-- | lib/Edit/EditedSource.cpp | 329 | ||||
-rw-r--r-- | lib/Edit/Makefile | 14 | ||||
-rw-r--r-- | lib/Edit/RewriteObjCFoundationAPI.cpp | 589 |
5 files changed, 1284 insertions, 0 deletions
diff --git a/lib/Edit/CMakeLists.txt b/lib/Edit/CMakeLists.txt new file mode 100644 index 0000000000..c87478cf7d --- /dev/null +++ b/lib/Edit/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_USED_LIBS clangBasic clangAST clangLex) + +add_clang_library(clangEdit + Commit.cpp + EditedSource.cpp + RewriteObjCFoundationAPI.cpp + ) diff --git a/lib/Edit/Commit.cpp b/lib/Edit/Commit.cpp new file mode 100644 index 0000000000..e0250ccf05 --- /dev/null +++ b/lib/Edit/Commit.cpp @@ -0,0 +1,345 @@ +//===----- Commit.cpp - A unit of edits -----------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/Commit.h" +#include "clang/Edit/EditedSource.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/PreprocessingRecord.h" +#include "clang/Basic/SourceManager.h" + +using namespace clang; +using namespace edit; + +SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const { + SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID()); + Loc = Loc.getLocWithOffset(Offset.getOffset()); + assert(Loc.isFileID()); + return Loc; +} + +CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const { + SourceLocation Loc = getFileLocation(SM); + return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); +} + +CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const { + SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID()); + Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset()); + assert(Loc.isFileID()); + return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); +} + +Commit::Commit(EditedSource &Editor) + : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOptions()), + PPRec(Editor.getPreprocessingRecord()), + Editor(&Editor), IsCommitable(true) { } + +bool Commit::insert(SourceLocation loc, StringRef text, + bool afterToken, bool beforePreviousInsertions) { + if (text.empty()) + return true; + + FileOffset Offs; + if ((!afterToken && !canInsert(loc, Offs)) || + ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { + IsCommitable = false; + return false; + } + + addInsert(loc, Offs, text, beforePreviousInsertions); + return true; +} + +bool Commit::insertFromRange(SourceLocation loc, + CharSourceRange range, + bool afterToken, bool beforePreviousInsertions) { + FileOffset RangeOffs; + unsigned RangeLen; + if (!canRemoveRange(range, RangeOffs, RangeLen)) { + IsCommitable = false; + return false; + } + + FileOffset Offs; + if ((!afterToken && !canInsert(loc, Offs)) || + ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { + IsCommitable = false; + return false; + } + + if (PPRec && + PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) { + IsCommitable = false; + return false; + } + + addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions); + return true; +} + +bool Commit::remove(CharSourceRange range) { + FileOffset Offs; + unsigned Len; + if (!canRemoveRange(range, Offs, Len)) { + IsCommitable = false; + return false; + } + + addRemove(range.getBegin(), Offs, Len); + return true; +} + +bool Commit::insertWrap(StringRef before, CharSourceRange range, + StringRef after) { + bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false, + /*beforePreviousInsertions=*/true); + bool commitableAfter; + if (range.isTokenRange()) + commitableAfter = insertAfterToken(range.getEnd(), after); + else + commitableAfter = insert(range.getEnd(), after); + + return commitableBefore && commitableAfter; +} + +bool Commit::replace(CharSourceRange range, StringRef text) { + if (text.empty()) + return remove(range); + + FileOffset Offs; + unsigned Len; + if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) { + IsCommitable = false; + return false; + } + + addRemove(range.getBegin(), Offs, Len); + addInsert(range.getBegin(), Offs, text, false); + return true; +} + +bool Commit::replaceWithInner(CharSourceRange range, + CharSourceRange replacementRange) { + FileOffset OuterBegin; + unsigned OuterLen; + if (!canRemoveRange(range, OuterBegin, OuterLen)) { + IsCommitable = false; + return false; + } + + FileOffset InnerBegin; + unsigned InnerLen; + if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) { + IsCommitable = false; + return false; + } + + FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen); + FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen); + if (OuterBegin.getFID() != InnerBegin.getFID() || + InnerBegin < OuterBegin || + InnerBegin > OuterEnd || + InnerEnd > OuterEnd) { + IsCommitable = false; + return false; + } + + addRemove(range.getBegin(), + OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset()); + addRemove(replacementRange.getEnd(), + InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset()); + return true; +} + +bool Commit::replaceText(SourceLocation loc, StringRef text, + StringRef replacementText) { + if (text.empty() || replacementText.empty()) + return true; + + FileOffset Offs; + unsigned Len; + if (!canReplaceText(loc, replacementText, Offs, Len)) { + IsCommitable = false; + return false; + } + + addRemove(loc, Offs, Len); + addInsert(loc, Offs, text, false); + return true; +} + +void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text, + bool beforePreviousInsertions) { + if (text.empty()) + return; + + Edit data; + data.Kind = Act_Insert; + data.OrigLoc = OrigLoc; + data.Offset = Offs; + data.Text = text; + data.BeforePrev = beforePreviousInsertions; + CachedEdits.push_back(data); +} + +void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs, + FileOffset RangeOffs, unsigned RangeLen, + bool beforePreviousInsertions) { + if (RangeLen == 0) + return; + + Edit data; + data.Kind = Act_InsertFromRange; + data.OrigLoc = OrigLoc; + data.Offset = Offs; + data.InsertFromRangeOffs = RangeOffs; + data.Length = RangeLen; + data.BeforePrev = beforePreviousInsertions; + CachedEdits.push_back(data); +} + +void Commit::addRemove(SourceLocation OrigLoc, + FileOffset Offs, unsigned Len) { + if (Len == 0) + return; + + Edit data; + data.Kind = Act_Remove; + data.OrigLoc = OrigLoc; + data.Offset = Offs; + data.Length = Len; + CachedEdits.push_back(data); +} + +bool Commit::canInsert(SourceLocation loc, FileOffset &offs) { + if (loc.isInvalid()) + return false; + + if (loc.isMacroID()) + isAtStartOfMacroExpansion(loc, &loc); + + const SourceManager &SM = SourceMgr; + while (SM.isMacroArgExpansion(loc)) + loc = SM.getImmediateSpellingLoc(loc); + + if (loc.isMacroID()) + if (!isAtStartOfMacroExpansion(loc, &loc)) + return false; + + if (SM.isInSystemHeader(loc)) + return false; + + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); + if (locInfo.first.isInvalid()) + return false; + offs = FileOffset(locInfo.first, locInfo.second); + return canInsertInOffset(loc, offs); +} + +bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs, + SourceLocation &AfterLoc) { + if (loc.isInvalid()) + + return false; + + SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc); + unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts); + AfterLoc = loc.getLocWithOffset(tokLen); + + if (loc.isMacroID()) + isAtEndOfMacroExpansion(loc, &loc); + + const SourceManager &SM = SourceMgr; + while (SM.isMacroArgExpansion(loc)) + loc = SM.getImmediateSpellingLoc(loc); + + if (loc.isMacroID()) + if (!isAtEndOfMacroExpansion(loc, &loc)) + return false; + + if (SM.isInSystemHeader(loc)) + return false; + + loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts); + if (loc.isInvalid()) + return false; + + std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); + if (locInfo.first.isInvalid()) + return false; + offs = FileOffset(locInfo.first, locInfo.second); + return canInsertInOffset(loc, offs); +} + +bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { + for (unsigned i = 0, e = CachedEdits.size(); i != e; ++i) { + Edit &act = CachedEdits[i]; + if (act.Kind == Act_Remove) { + if (act.Offset.getFID() == Offs.getFID() && + Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length)) + return false; // position has been removed. + } + } + + if (!Editor) + return true; + return Editor->canInsertInOffset(OrigLoc, Offs); +} + +bool Commit::canRemoveRange(CharSourceRange range, + FileOffset &Offs, unsigned &Len) { + const SourceManager &SM = SourceMgr; + range = Lexer::makeFileCharRange(range, SM, LangOpts); + if (range.isInvalid()) + return false; + + if (range.getBegin().isMacroID() || range.getEnd().isMacroID()) + return false; + if (SM.isInSystemHeader(range.getBegin()) || + SM.isInSystemHeader(range.getEnd())) + return false; + + if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange())) + return false; + + std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin()); + std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd()); + if (beginInfo.first != endInfo.first || + beginInfo.second > endInfo.second) + return false; + + Offs = FileOffset(beginInfo.first, beginInfo.second); + Len = endInfo.second - beginInfo.second; + return true; +} + +bool Commit::canReplaceText(SourceLocation loc, StringRef text, + FileOffset &Offs, unsigned &Len) { + assert(!text.empty()); + + if (!canInsert(loc, Offs)) + return false; + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp); + if (invalidTemp) + return false; + + return file.substr(Offs.getOffset()).startswith(text); +} + +bool Commit::isAtStartOfMacroExpansion(SourceLocation loc, + SourceLocation *MacroBegin) const { + return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin); +} +bool Commit::isAtEndOfMacroExpansion(SourceLocation loc, + SourceLocation *MacroEnd) const { + return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd); +} diff --git a/lib/Edit/EditedSource.cpp b/lib/Edit/EditedSource.cpp new file mode 100644 index 0000000000..5b7fa4ad1b --- /dev/null +++ b/lib/Edit/EditedSource.cpp @@ -0,0 +1,329 @@ +//===----- EditedSource.cpp - Collection of source edits ------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/EditedSource.h" +#include "clang/Edit/Commit.h" +#include "clang/Edit/EditsReceiver.h" +#include "clang/Lex/Lexer.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/Twine.h" + +using namespace clang; +using namespace edit; + +void EditsReceiver::remove(CharSourceRange range) { + replace(range, StringRef()); +} + +StringRef EditedSource::copyString(const Twine &twine) { + llvm::SmallString<128> Data; + return copyString(twine.toStringRef(Data)); +} + +bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { + FileEditsTy::iterator FA = getActionForOffset(Offs); + if (FA != FileEdits.end()) { + if (FA->first != Offs) + return false; // position has been removed. + } + + if (SourceMgr.isMacroArgExpansion(OrigLoc)) { + SourceLocation + DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; + SourceLocation + ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; + llvm::DenseMap<unsigned, SourceLocation>::iterator + I = ExpansionToArgMap.find(ExpLoc.getRawEncoding()); + if (I != ExpansionToArgMap.end() && I->second != DefArgLoc) + return false; // Trying to write in a macro argument input that has + // already been written for another argument of the same macro. + } + + return true; +} + +bool EditedSource::commitInsert(SourceLocation OrigLoc, + FileOffset Offs, StringRef text, + bool beforePreviousInsertions) { + if (!canInsertInOffset(OrigLoc, Offs)) + return false; + if (text.empty()) + return true; + + if (SourceMgr.isMacroArgExpansion(OrigLoc)) { + SourceLocation + DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; + SourceLocation + ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; + ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc; + } + + FileEdit &FA = FileEdits[Offs]; + if (FA.Text.empty()) { + FA.Text = copyString(text); + return true; + } + + Twine concat; + if (beforePreviousInsertions) + concat = Twine(text) + FA.Text; + else + concat = Twine(FA.Text) + text; + + FA.Text = copyString(concat); + return true; +} + +bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc, + FileOffset Offs, + FileOffset InsertFromRangeOffs, unsigned Len, + bool beforePreviousInsertions) { + if (Len == 0) + return true; + + llvm::SmallString<128> StrVec; + FileOffset BeginOffs = InsertFromRangeOffs; + FileOffset EndOffs = BeginOffs.getWithOffset(Len); + FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); + if (I != FileEdits.begin()) + --I; + + for (; I != FileEdits.end(); ++I) { + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + + if (BeginOffs < E) { + if (BeginOffs >= B) { + BeginOffs = E; + ++I; + } + break; + } + } + + for (; I != FileEdits.end() && EndOffs > I->first; ++I) { + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + + if (BeginOffs < B) { + bool Invalid = false; + StringRef text = getSourceText(BeginOffs, B, Invalid); + if (Invalid) + return false; + StrVec += text; + } + StrVec += FA.Text; + BeginOffs = E; + } + + if (BeginOffs < EndOffs) { + bool Invalid = false; + StringRef text = getSourceText(BeginOffs, EndOffs, Invalid); + if (Invalid) + return false; + StrVec += text; + } + + return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions); +} + +void EditedSource::commitRemove(SourceLocation OrigLoc, + FileOffset BeginOffs, unsigned Len) { + if (Len == 0) + return; + + FileOffset EndOffs = BeginOffs.getWithOffset(Len); + FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); + if (I != FileEdits.begin()) + --I; + + for (; I != FileEdits.end(); ++I) { + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + + if (BeginOffs < E) + break; + } + + FileOffset TopBegin, TopEnd; + FileEdit *TopFA = 0; + + if (I == FileEdits.end()) { + FileEditsTy::iterator + NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); + NewI->second.RemoveLen = Len; + return; + } + + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + if (BeginOffs < B) { + FileEditsTy::iterator + NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); + TopBegin = BeginOffs; + TopEnd = EndOffs; + TopFA = &NewI->second; + TopFA->RemoveLen = Len; + } else { + TopBegin = B; + TopEnd = E; + TopFA = &I->second; + if (TopEnd >= EndOffs) + return; + unsigned diff = EndOffs.getOffset() - TopEnd.getOffset(); + TopEnd = EndOffs; + TopFA->RemoveLen += diff; + ++I; + } + + while (I != FileEdits.end()) { + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + + if (B >= TopEnd) + break; + + if (E <= TopEnd) { + FileEdits.erase(I++); + continue; + } + + if (B < TopEnd) { + unsigned diff = E.getOffset() - TopEnd.getOffset(); + TopEnd = E; + TopFA->RemoveLen += diff; + FileEdits.erase(I); + } + + break; + } +} + +bool EditedSource::commit(const Commit &commit) { + if (!commit.isCommitable()) + return false; + + for (edit::Commit::edit_iterator + I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) { + const edit::Commit::Edit &edit = *I; + switch (edit.Kind) { + case edit::Commit::Act_Insert: + commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev); + break; + case edit::Commit::Act_InsertFromRange: + commitInsertFromRange(edit.OrigLoc, edit.Offset, + edit.InsertFromRangeOffs, edit.Length, + edit.BeforePrev); + break; + case edit::Commit::Act_Remove: + commitRemove(edit.OrigLoc, edit.Offset, edit.Length); + break; + } + } + + return true; +} + +static void applyRewrite(EditsReceiver &receiver, + StringRef text, FileOffset offs, unsigned len, + const SourceManager &SM) { + assert(!offs.getFID().isInvalid()); + SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID()); + Loc = Loc.getLocWithOffset(offs.getOffset()); + assert(Loc.isFileID()); + CharSourceRange range = CharSourceRange::getCharRange(Loc, + Loc.getLocWithOffset(len)); + + if (text.empty()) { + assert(len); + receiver.remove(range); + return; + } + + if (len) + receiver.replace(range, text); + else + receiver.insert(Loc, text); +} + +void EditedSource::applyRewrites(EditsReceiver &receiver) { + llvm::SmallString<128> StrVec; + FileOffset CurOffs, CurEnd; + unsigned CurLen; + + if (FileEdits.empty()) + return; + + FileEditsTy::iterator I = FileEdits.begin(); + CurOffs = I->first; + StrVec = I->second.Text; + CurLen = I->second.RemoveLen; + CurEnd = CurOffs.getWithOffset(CurLen); + ++I; + + for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) { + FileOffset offs = I->first; + FileEdit act = I->second; + assert(offs >= CurEnd); + + if (offs == CurEnd) { + StrVec += act.Text; + CurLen += act.RemoveLen; + CurEnd.getWithOffset(act.RemoveLen); + continue; + } + + applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr); + CurOffs = offs; + StrVec = act.Text; + CurLen = act.RemoveLen; + CurEnd = CurOffs.getWithOffset(CurLen); + } + + applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr); +} + +void EditedSource::clearRewrites() { + FileEdits.clear(); + StrAlloc.Reset(); +} + +StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs, + bool &Invalid) { + assert(BeginOffs.getFID() == EndOffs.getFID()); + assert(BeginOffs <= EndOffs); + SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID()); + BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset()); + assert(BLoc.isFileID()); + SourceLocation + ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); + return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), + SourceMgr, LangOpts, &Invalid); +} + +EditedSource::FileEditsTy::iterator +EditedSource::getActionForOffset(FileOffset Offs) { + FileEditsTy::iterator I = FileEdits.upper_bound(Offs); + if (I == FileEdits.begin()) + return FileEdits.end(); + --I; + FileEdit &FA = I->second; + FileOffset B = I->first; + FileOffset E = B.getWithOffset(FA.RemoveLen); + if (Offs >= B && Offs < E) + return I; + + return FileEdits.end(); +} diff --git a/lib/Edit/Makefile b/lib/Edit/Makefile new file mode 100644 index 0000000000..92a67ebc82 --- /dev/null +++ b/lib/Edit/Makefile @@ -0,0 +1,14 @@ +##===- clang/lib/Edit/Makefile -----------------------------*- Makefile -*-===## +# +# The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +# +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../.. +LIBRARYNAME := clangEdit + +include $(CLANG_LEVEL)/Makefile + diff --git a/lib/Edit/RewriteObjCFoundationAPI.cpp b/lib/Edit/RewriteObjCFoundationAPI.cpp new file mode 100644 index 0000000000..a092768034 --- /dev/null +++ b/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -0,0 +1,589 @@ +//===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Rewrites legacy method calls to modern syntax. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/Rewriters.h" +#include "clang/Edit/Commit.h" +#include "clang/Lex/Lexer.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/NSAPI.h" + +using namespace clang; +using namespace edit; + +static bool checkForLiteralCreation(const ObjCMessageExpr *Msg, + IdentifierInfo *&ClassId) { + if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl()) + return false; + + const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface(); + if (!Receiver) + return false; + ClassId = Receiver->getIdentifier(); + + if (Msg->getReceiverKind() == ObjCMessageExpr::Class) + return true; + + return false; +} + +//===----------------------------------------------------------------------===// +// rewriteObjCRedundantCallWithLiteral. +//===----------------------------------------------------------------------===// + +bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit) { + IdentifierInfo *II = 0; + if (!checkForLiteralCreation(Msg, II)) + return false; + if (Msg->getNumArgs() != 1) + return false; + + const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); + Selector Sel = Msg->getSelector(); + + if ((isa<ObjCStringLiteral>(Arg) && + NS.getNSClassId(NSAPI::ClassId_NSString) == II && + NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel) || + + (isa<ObjCArrayLiteral>(Arg) && + NS.getNSClassId(NSAPI::ClassId_NSArray) == II && + NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel) || + + (isa<ObjCDictionaryLiteral>(Arg) && + NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II && + NS.getNSDictionarySelector( + NSAPI::NSDict_dictionaryWithDictionary) == Sel)) { + + commit.replaceWithInner(Msg->getSourceRange(), + Msg->getArg(0)->getSourceRange()); + return true; + } + + return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToObjCSubscriptSyntax. +//===----------------------------------------------------------------------===// + +static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) { + Receiver = Receiver->IgnoreImpCasts(); + if (isa<BinaryOperator>(Receiver) || isa<UnaryOperator>(Receiver)) { + SourceRange RecRange = Receiver->getSourceRange(); + commit.insertWrap("(", RecRange, ")"); + } +} + +static bool rewriteToSubscriptGet(const ObjCMessageExpr *Msg, Commit &commit) { + if (Msg->getNumArgs() != 1) + return false; + const Expr *Rec = Msg->getInstanceReceiver(); + if (!Rec) + return false; + + SourceRange MsgRange = Msg->getSourceRange(); + SourceRange RecRange = Rec->getSourceRange(); + SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); + + commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), + ArgRange.getBegin()), + CharSourceRange::getTokenRange(RecRange)); + commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()), + ArgRange); + commit.insertWrap("[", ArgRange, "]"); + maybePutParensOnReceiver(Rec, commit); + return true; +} + +static bool rewriteToArraySubscriptSet(const ObjCMessageExpr *Msg, + Commit &commit) { + if (Msg->getNumArgs() != 2) + return false; + const Expr *Rec = Msg->getInstanceReceiver(); + if (!Rec) + return false; + + SourceRange MsgRange = Msg->getSourceRange(); + SourceRange RecRange = Rec->getSourceRange(); + SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); + SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); + + commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), + Arg0Range.getBegin()), + CharSourceRange::getTokenRange(RecRange)); + commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(), + Arg1Range.getBegin()), + CharSourceRange::getTokenRange(Arg0Range)); + commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()), + Arg1Range); + commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(), + Arg1Range.getBegin()), + "] = "); + maybePutParensOnReceiver(Rec, commit); + return true; +} + +static bool rewriteToDictionarySubscriptSet(const ObjCMessageExpr *Msg, + Commit &commit) { + if (Msg->getNumArgs() != 2) + return false; + const Expr *Rec = Msg->getInstanceReceiver(); + if (!Rec) + return false; + + SourceRange MsgRange = Msg->getSourceRange(); + SourceRange RecRange = Rec->getSourceRange(); + SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); + SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); + + SourceLocation LocBeforeVal = Arg0Range.getBegin(); + commit.insertBefore(LocBeforeVal, "] = "); + commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false, + /*beforePreviousInsertions=*/true); + commit.insertBefore(LocBeforeVal, "["); + commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), + Arg0Range.getBegin()), + CharSourceRange::getTokenRange(RecRange)); + commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()), + Arg0Range); + maybePutParensOnReceiver(Rec, commit); + return true; +} + +bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit) { + if (!Msg || Msg->isImplicit() || + Msg->getReceiverKind() != ObjCMessageExpr::Instance) + return false; + const ObjCMethodDecl *Method = Msg->getMethodDecl(); + if (!Method) + return false; + + const ObjCInterfaceDecl * + IFace = NS.getASTContext().getObjContainingInterface( + const_cast<ObjCMethodDecl *>(Method)); + if (!IFace) + return false; + IdentifierInfo *II = IFace->getIdentifier(); + Selector Sel = Msg->getSelector(); + + if ((II == NS.getNSClassId(NSAPI::ClassId_NSArray) && + Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex)) || + (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary) && + Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey))) + return rewriteToSubscriptGet(Msg, commit); + + if (Msg->getNumArgs() != 2) + return false; + + if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableArray) && + Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex)) + return rewriteToArraySubscriptSet(Msg, commit); + + if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableDictionary) && + Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey)) + return rewriteToDictionarySubscriptSet(Msg, commit); + + return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToObjCLiteralSyntax. +//===----------------------------------------------------------------------===// + +static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit); +static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit); +static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit); + +bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit) { + IdentifierInfo *II = 0; + if (!checkForLiteralCreation(Msg, II)) + return false; + + if (II == NS.getNSClassId(NSAPI::ClassId_NSArray)) + return rewriteToArrayLiteral(Msg, NS, commit); + if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary)) + return rewriteToDictionaryLiteral(Msg, NS, commit); + if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber)) + return rewriteToNumberLiteral(Msg, NS, commit); + + return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToArrayLiteral. +//===----------------------------------------------------------------------===// + +static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit) { + Selector Sel = Msg->getSelector(); + SourceRange MsgRange = Msg->getSourceRange(); + + if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) { + if (Msg->getNumArgs() != 0) + return false; + commit.replace(MsgRange, "@[]"); + return true; + } + + if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { + if (Msg->getNumArgs() != 1) + return false; + SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); + commit.replaceWithInner(MsgRange, ArgRange); + commit.insertWrap("@[", ArgRange, "]"); + return true; + } + + if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects)) { + if (Msg->getNumArgs() == 0) + return false; + const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); + if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) + return false; + + if (Msg->getNumArgs() == 1) { + commit.replace(MsgRange, "@[]"); + return true; + } + SourceRange ArgRange(Msg->getArg(0)->getLocStart(), + Msg->getArg(Msg->getNumArgs()-2)->getLocEnd()); + commit.replaceWithInner(MsgRange, ArgRange); + commit.insertWrap("@[", ArgRange, "]"); + return true; + } + + return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToDictionaryLiteral. +//===----------------------------------------------------------------------===// + +static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, + const NSAPI &NS, Commit &commit) { + Selector Sel = Msg->getSelector(); + SourceRange MsgRange = Msg->getSourceRange(); + + if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) { + if (Msg->getNumArgs() != 0) + return false; + commit.replace(MsgRange, "@{}"); + return true; + } + + if (Sel == NS.getNSDictionarySelector( + NSAPI::NSDict_dictionaryWithObjectForKey)) { + if (Msg->getNumArgs() != 2) + return false; + SourceRange ValRange = Msg->getArg(0)->getSourceRange(); + SourceRange KeyRange = Msg->getArg(1)->getSourceRange(); + // Insert key before the value. + commit.insertBefore(ValRange.getBegin(), ": "); + commit.insertFromRange(ValRange.getBegin(), + CharSourceRange::getTokenRange(KeyRange), + /*afterToken=*/false, /*beforePreviousInsertions=*/true); + commit.insertBefore(ValRange.getBegin(), "@{"); + commit.insertAfterToken(ValRange.getEnd(), "}"); + commit.replaceWithInner(MsgRange, ValRange); + return true; + } + + if (Sel == NS.getNSDictionarySelector( + NSAPI::NSDict_dictionaryWithObjectsAndKeys)) { + if (Msg->getNumArgs() % 2 != 1) + return false; + unsigned SentinelIdx = Msg->getNumArgs() - 1; + const Expr *SentinelExpr = Msg->getArg(SentinelIdx); + if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) + return false; + + if (Msg->getNumArgs() == 1) { + commit.replace(MsgRange, "@{}"); + return true; + } + + for (unsigned i = 0; i < SentinelIdx; i += 2) { + SourceRange ValRange = Msg->getArg(i)->getSourceRange(); + SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange(); + // Insert value after key. + commit.insertAfterToken(KeyRange.getEnd(), ": "); + commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); + commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(), |