Index: test/Index/overriding-method-comments.mm =================================================================== --- test/Index/overriding-method-comments.mm (revision 170208) +++ test/Index/overriding-method-comments.mm (working copy) @@ -19,7 +19,7 @@ - (void)METH:(id)AAA; @end -// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void) METH:(id)AAAAAA0in ZZZ ] +// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void)METH:(id)AAAAAA0in ZZZ ] @interface Sub : Root @end @@ -28,13 +28,13 @@ - (void)METH:(id)BBB; @end -// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void) METH:(id)BBBBBB0in ZZZ ] +// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void)METH:(id)BBBBBB0in ZZZ ] @implementation Sub(CAT) - (void)METH:(id)III {} @end -// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void) METH:(id)IIIIII0in ZZZ ] +// CHECK: FullCommentAsXML=[METH:c:objc(cs)Root(im)METH:- (void)METH:(id)IIIIII0in ZZZ ] @interface Redec : Root @end @@ -48,13 +48,13 @@ - (void)EXT_METH:(id)AAA : (double)BBB : (int)CCC; @end -// CHECK: FullCommentAsXML=[EXT_METH:::c:objc(cs)Redec(im)EXT_METH:::- (void) EXT_METH:(id)AAA :(double)BBB :(int)CCCAAA0in input value BBB1in 2nd input value is double CCC2out output value is int ] +// CHECK: FullCommentAsXML=[EXT_METH:::c:objc(cs)Redec(im)EXT_METH:::- (void)EXT_METH:(id)AAA:(double)BBB:(int)CCCAAA0in input value BBB1in 2nd input value is double CCC2out output value is int ] @implementation Redec - (void)EXT_METH:(id)PPP : (double)QQQ : (int)RRR {} @end -// CHECK: FullCommentAsXML=[EXT_METH:::c:objc(cs)Redec(im)EXT_METH:::- (void) EXT_METH:(id)PPP :(double)QQQ :(int)RRRPPP0in input value QQQ1in 2nd input value is double RRR2out output value is int ] +// CHECK: FullCommentAsXML=[EXT_METH:::c:objc(cs)Redec(im)EXT_METH:::- (void)EXT_METH:(id)PPP:(double)QQQ:(int)RRRPPP0in input value QQQ1in 2nd input value is double RRR2out output value is int ] struct Base { /// \brief Does something. Index: test/Index/comment-objc-decls.m =================================================================== --- test/Index/comment-objc-decls.m (revision 170208) +++ test/Index/comment-objc-decls.m (working copy) @@ -20,7 +20,7 @@ * \param[in] range output value is unsigned int * \result return index */ -- (unsigned int)MethodMyProto:(id)anObject inRange:(unsigned int)range; +- (unsigned int)MethodMyProto:(id) anObject inRange : (unsigned int)range; /** * \brief PropertyMyProto - This is protocol's property. */ @@ -30,10 +30,10 @@ */ + ClassMethodMyProto; @end -// CHECK: @protocol MyProto\n@end -// CHECK: - (unsigned int) MethodMyProto:(id)anObject inRange:(unsigned int)range -// CHECK: @optional\n@property ( readwrite,copy,atomic ) id PropertyMyProto -// CHECK: + (id) ClassMethodMyProto +// CHECK: @protocol MyProto @end +// CHECK: - (unsigned int)MethodMyProto:(id)anObject inRange:(unsigned int)range +// CHECK: @optional\n @property(readwrite, copy, atomic) id PropertyMyProto +// CHECK: + (id)ClassMethodMyProto /** * \brief NSObject is the root class. @@ -45,7 +45,7 @@ id IvarNSObject; } @end -// CHECK: Declaration>@interface NSObject{\n id IvarNSObject;\n}\n@end +// CHECK: Declaration>@interface NSObject {\n id IvarNSObject\n}\n@end // CHECK: id IvarNSObject /** @@ -73,11 +73,11 @@ */ @property (copy) id PropertyMyClass; @end -// CHECK: @interface MyClass : NSObject<MyProto> {\n id IvarMyClass;\n}\n@end +// CHECK: @interface MyClass : NSObject<MyProto> {\n id IvarMyClass\n}\n@end // CHECK: id IvarMyClass -// CHECK: - (id) MethodMyClass -// CHECK: + (id) ClassMethodMyClass -// CHECK: @property ( readwrite,copy,atomic ) id PropertyMyClass- (id)MethodMyClass +// CHECK: + (id)ClassMethodMyClass +// CHECK: @property(readwrite, copy, atomic) id PropertyMyClass@interface MyClass()\n{\n id IvarMyClassExtension;\n}\n@end +// CHECK: @interface MyClass() {\n id IvarMyClassExtension\n}\n@end // CHECK: id IvarMyClassExtension @@ -108,11 +108,11 @@ */ @property (copy) id PropertyMyClassCategory; @end -// CHECK: @interface MyClass(Category)\n@end -// CHECK: - (void) MethodMyClassCategory -// CHECK: @property ( readwrite,copy,atomic ) id PropertyMyClassCategory -// CHECK: - (id) PropertyMyClassCategory -// CHECK: - (void) setPropertyMyClassCategory:(id)arg +// CHECK: @interface MyClass(Category) @end +// CHECK: - (void)MethodMyClassCategory +// CHECK: @property(readwrite, copy, atomic) id PropertyMyClassCategory +// CHECK: - (id)PropertyMyClassCategory +// CHECK: - (void)setPropertyMyClassCategory:(id)arg /// @implementation's @@ -139,10 +139,10 @@ return 0; } @end -// CHECK: @implementation MyClass{\n id IvarPrivateToMyClassImpl;\n id _PropertyMyClass;\n}\n@end +// CHECK: @implementation MyClass {\n id IvarPrivateToMyClassImpl\n id _PropertyMyClass\n}\n@end // CHECK: id IvarPrivateToMyClassImpl -// CHECK: - (id) MethodMyClass -// CHECK: + (id) ClassMethodMyClass +// CHECK: - (id)MethodMyClass +// CHECK: + (id)ClassMethodMyClass /** * \brief MyClass (Category) is implementation of private to MyClass. @@ -162,14 +162,14 @@ */ - (void) setPropertyMyClassCategory : (id) arg {} @end -// CHECK: @implementation MyClass(Category)\n@end -// CHECK: - (void) MethodMyClassCategory -// CHECK: - (id) PropertyMyClassCategory -// CHECK: - (void) setPropertyMyClassCategory:(id)arg +// CHECK: @implementation MyClass(Category) @end +// CHECK: - (void)MethodMyClassCategory +// CHECK: - (id)PropertyMyClassCategory +// CHECK: - (void)setPropertyMyClassCategory:(id)arg /** * \brief NSObject implementation */ @implementation NSObject @end -// CHECK: @implementation NSObject@end +// CHECK: @implementation NSObject @end Index: include/clang/Format/RewriterFormatContext.h =================================================================== --- include/clang/Format/RewriterFormatContext.h (revision 0) +++ include/clang/Format/RewriterFormatContext.h (revision 0) @@ -0,0 +1,125 @@ +//===--- RewriterFormatContext.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a utility class for Rewriter related tests. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_REWRITER_FORMAT_CONTEXT_H +#define LLVM_CLANG_REWRITER_FORMAT_CONTEXT_H + +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Rewrite/Core/Rewriter.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +namespace clang { + +/// \brief A class that sets up a ready to use Rewriter. +/// +/// Useful in unit tests that need a Rewriter. Creates all dependencies +/// of a Rewriter with default values for testing and provides convenience +/// methods, which help with writing tests that change files. +class RewriterTestContext { + public: + RewriterTestContext() + : DiagOpts(new DiagnosticOptions()), + Diagnostics(llvm::IntrusiveRefCntPtr(new DiagnosticIDs), + &*DiagOpts), + Files((FileSystemOptions())), + Sources(Diagnostics, Files), + Rewrite(Sources, Options) { + Diagnostics.setClient(new IgnoringDiagConsumer, true); + } + + ~RewriterTestContext() { + if (!TemporaryDirectory.empty()) { + uint32_t RemovedCount = 0; + llvm::sys::fs::remove_all(TemporaryDirectory.str(), RemovedCount); + } + } + + FileID createInMemoryFile(StringRef Name, StringRef Content) { + const llvm::MemoryBuffer *Source = + llvm::MemoryBuffer::getMemBuffer(Content); + const FileEntry *Entry = + Files.getVirtualFile(Name, Source->getBufferSize(), 0); + Sources.overrideFileContents(Entry, Source, true); + assert(Entry != NULL); + return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User); + } + + FileID createOnDiskFile(StringRef Name, StringRef Content) { + if (TemporaryDirectory.empty()) { + int FD; + bool error = + llvm::sys::fs::unique_file("rewriter-test-%%-%%-%%-%%/anchor", FD, + TemporaryDirectory); + assert(!error); (void)error; + llvm::raw_fd_ostream Closer(FD, /*shouldClose=*/true); + TemporaryDirectory = llvm::sys::path::parent_path(TemporaryDirectory); + } + llvm::SmallString<1024> Path(TemporaryDirectory); + llvm::sys::path::append(Path, Name); + std::string ErrorInfo; + llvm::raw_fd_ostream OutStream(Path.c_str(), + ErrorInfo, llvm::raw_fd_ostream::F_Binary); + assert(ErrorInfo.empty()); + OutStream << Content; + OutStream.close(); + const FileEntry *File = Files.getFile(Path); + assert(File != NULL); + return Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); + } + + SourceLocation getLocation(FileID ID, unsigned Line, unsigned Column) { + SourceLocation Result = Sources.translateFileLineCol( + Sources.getFileEntryForID(ID), Line, Column); + assert(Result.isValid()); + return Result; + } + + std::string getRewrittenText(FileID ID) { + std::string Result; + llvm::raw_string_ostream OS(Result); + Rewrite.getEditBuffer(ID).write(OS); + OS.flush(); + return Result; + } + + std::string getFileContentFromDisk(StringRef Name) { + llvm::SmallString<1024> Path(TemporaryDirectory.str()); + llvm::sys::path::append(Path, Name); + // We need to read directly from the FileManager without relaying through + // a FileEntry, as otherwise we'd read through an already opened file + // descriptor, which might not see the changes made. + // FIXME: Figure out whether there is a way to get the SourceManger to + // reopen the file. + return Files.getBufferForFile(Path, NULL)->getBuffer(); + } + + llvm::IntrusiveRefCntPtr DiagOpts; + DiagnosticsEngine Diagnostics; + FileManager Files; + SourceManager Sources; + LangOptions Options; + Rewriter Rewrite; + + // Will be set once on disk files are generated. + SmallString<128> TemporaryDirectory; +}; + +} // end namespace clang + +#endif Index: tools/libclang/CXTranslationUnit.h =================================================================== --- tools/libclang/CXTranslationUnit.h (revision 170208) +++ tools/libclang/CXTranslationUnit.h (working copy) @@ -21,6 +21,8 @@ void *StringPool; void *Diagnostics; void *OverridenCursorsPool; + void *FormatContext; + unsigned FormatInMemoryUniqueId; }; } Index: tools/libclang/CXCursor.h =================================================================== --- tools/libclang/CXCursor.h (revision 170208) +++ tools/libclang/CXCursor.h (working copy) @@ -211,10 +211,18 @@ /// \brief Create an opaque pool used for fast generation of overriden /// CXCursor arrays. void *createOverridenCXCursorsPool(); + +/// \brief Create an opaque object used for declaration comment +/// formatting. +void *createFormatContext(); /// \brief Dispose of the overriden CXCursors pool. void disposeOverridenCXCursorsPool(void *pool); +/// \brief Dispose of the formatting object. +void disposeFormatContext(void *formatContext); + + /// \brief Returns a index/location pair for a selector identifier if the cursor /// points to one. std::pair getSelectorIdentifierIndexAndLoc(CXCursor); Index: tools/libclang/Makefile =================================================================== --- tools/libclang/Makefile (revision 170208) +++ tools/libclang/Makefile (working copy) @@ -21,7 +21,8 @@ clangFrontend.a clangDriver.a \ clangSerialization.a \ clangParse.a clangSema.a clangEdit.a clangAnalysis.a \ - clangAST.a clangLex.a clangTooling.a clangBasic.a + clangAST.a clangLex.a clangTooling.a clangBasic.a \ + clangFormat.a include $(CLANG_LEVEL)/Makefile Index: tools/libclang/CMakeLists.txt =================================================================== --- tools/libclang/CMakeLists.txt (revision 170208) +++ tools/libclang/CMakeLists.txt (working copy) @@ -54,6 +54,7 @@ clangLex clangTooling clangBasic + clangFormat ) set(GENERATED_HEADERS Index: tools/libclang/CXComment.cpp =================================================================== --- tools/libclang/CXComment.cpp (revision 170208) +++ tools/libclang/CXComment.cpp (working copy) @@ -19,9 +19,13 @@ #include "clang/AST/CommentVisitor.h" #include "clang/AST/Decl.h" #include "clang/AST/PrettyPrinter.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" +#include "clang/Format/Format.h" +#include "clang/Format/RewriterFormatContext.h" +#include "clang/Lex/Lexer.h" #include using namespace clang; @@ -856,8 +860,11 @@ CommentASTToXMLConverter(const FullComment *FC, SmallVectorImpl &Str, const CommandTraits &Traits, - const SourceManager &SM) : - FC(FC), Result(Str), Traits(Traits), SM(SM) { } + const SourceManager &SM, + RewriterTestContext &RTC, + unsigned FUID) : + FC(FC), Result(Str), Traits(Traits), SM(SM), TContext(RTC), + FormatInMemoryUniqueId(FUID) { } // Inline content. void visitTextComment(const TextComment *C); @@ -877,7 +884,7 @@ void visitFullComment(const FullComment *C); // Helpers. - void appendToResultWithXMLEscaping(StringRef S); + void appendToResultWithXMLEscaping(StringRef S, bool SkipSemi=false); private: const FullComment *FC; @@ -887,6 +894,8 @@ const CommandTraits &Traits; const SourceManager &SM; + RewriterTestContext &TContext; + unsigned FormatInMemoryUniqueId; }; void getSourceTextOfDeclaration(const DeclInfo *ThisDecl, @@ -1165,7 +1174,43 @@ Result << ""; SmallString<128> Declaration; getSourceTextOfDeclaration(DI, Declaration); - appendToResultWithXMLEscaping(Declaration); + + // FIXME. For now focus on formatting objective-C declarations. + if (isa(DI->CurrentDecl) || + isa(DI->CurrentDecl) || + isa(DI->CurrentDecl) || + isa(DI->CurrentDecl) || + isa(DI->CurrentDecl)) { + // FIXME. formatting API expects null terminated input string. + // There might be more efficient way of doing this. + std::string StringDecl = Declaration.str(); + StringDecl += ";"; + + // Formatter specific code. + // Form a unique in memory buffer name. + llvm::SmallString<128> filename; + filename += "xmldecl"; + filename += llvm::utostr(FormatInMemoryUniqueId); + filename += ".xd"; + FileID ID = TContext.createInMemoryFile(filename, StringDecl); + SourceLocation Start = + TContext.Sources.getLocForStartOfFile(ID).getLocWithOffset(0); + unsigned Length = Declaration.size(); + + std::vector Ranges(1, + CharSourceRange::getCharRange(Start, + Start.getLocWithOffset(Length))); + ASTContext &Context = DI->CurrentDecl->getASTContext(); + const LangOptions &LangOpts = Context.getLangOpts(); + Lexer Lex(ID, TContext.Sources.getBuffer(ID), TContext.Sources, LangOpts); + tooling::Replacements Replace = + reformat(format::getLLVMStyle(), Lex, TContext.Sources, Ranges); + applyAllReplacements(Replace, TContext.Rewrite); + appendToResultWithXMLEscaping(TContext.getRewrittenText(ID), true /*SkipSemi*/); + } + else + appendToResultWithXMLEscaping(Declaration); + Result << ""; } @@ -1288,7 +1333,8 @@ Result.flush(); } -void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S) { +void CommentASTToXMLConverter::appendToResultWithXMLEscaping(StringRef S, + bool SkipSemi) { for (StringRef::iterator I = S.begin(), E = S.end(); I != E; ++I) { const char C = *I; switch (C) { @@ -1307,6 +1353,9 @@ case '\'': Result << "'"; break; + case ';': + if (SkipSemi) + break; default: Result << C; break; @@ -1325,7 +1374,10 @@ SourceManager &SM = static_cast(TU->TUData)->getSourceManager(); SmallString<1024> XML; - CommentASTToXMLConverter Converter(FC, XML, getCommandTraits(CXC), SM); + CommentASTToXMLConverter Converter(FC, XML, getCommandTraits(CXC), SM, + *static_cast( + TU->FormatContext), + TU->FormatInMemoryUniqueId++); Converter.visit(FC); return createCXString(XML.str(), /* DupString = */ true); } Index: tools/libclang/CXCursor.cpp =================================================================== --- tools/libclang/CXCursor.cpp (revision 170208) +++ tools/libclang/CXCursor.cpp (working copy) @@ -26,6 +26,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/Frontend/ASTUnit.h" +#include "clang/Format/RewriterFormatContext.h" #include "llvm/Support/ErrorHandling.h" using namespace clang; @@ -1066,11 +1067,19 @@ void *cxcursor::createOverridenCXCursorsPool() { return new OverridenCursorsPool(); } - + +void *cxcursor::createFormatContext() { + return new RewriterTestContext(); +} + void cxcursor::disposeOverridenCXCursorsPool(void *pool) { delete static_cast(pool); } - + +void cxcursor::disposeFormatContext(void *formatContext) { + delete static_cast(formatContext); +} + extern "C" { void clang_getOverriddenCursors(CXCursor cursor, CXCursor **overridden, Index: tools/libclang/CIndex.cpp =================================================================== --- tools/libclang/CIndex.cpp (revision 170208) +++ tools/libclang/CIndex.cpp (working copy) @@ -61,6 +61,8 @@ D->StringPool = createCXStringPool(); D->Diagnostics = 0; D->OverridenCursorsPool = createOverridenCXCursorsPool(); + D->FormatContext = createFormatContext(); + D->FormatInMemoryUniqueId = 0; return D; } @@ -2773,6 +2775,8 @@ disposeCXStringPool(CTUnit->StringPool); delete static_cast(CTUnit->Diagnostics); disposeOverridenCXCursorsPool(CTUnit->OverridenCursorsPool); + disposeFormatContext(CTUnit->FormatContext); + CTUnit->FormatInMemoryUniqueId = 0; delete CTUnit; } } Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp (revision 170208) +++ unittests/Format/FormatTest.cpp (working copy) @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang/Format/Format.h" -#include "../Tooling/RewriterTestContext.h" +#include "clang/Format/RewriterFormatContext.h" #include "clang/Lex/Lexer.h" #include "gtest/gtest.h" @@ -28,6 +28,8 @@ CharSourceRange::getCharRange(Start, Start.getLocWithOffset(Length))); LangOptions LangOpts; LangOpts.CPlusPlus = 1; + // FIXME. Need this to test ObjC1 features. + LangOpts.ObjC1 = 1; Lexer Lex(ID, Context.Sources.getBuffer(ID), Context.Sources, LangOpts); tooling::Replacements Replace = reformat(Style, Lex, Context.Sources, Ranges); @@ -63,6 +65,31 @@ // Basic function tests. //===----------------------------------------------------------------------===// +TEST_F(FormatTest, FormatForObjectiveCDecls) { + EXPECT_EQ("@protocol MyProto @end;", + format("@protocol MyProto@end;")); +} + +TEST_F(FormatTest, FormatForObjectiveCMethods) { + verifyFormat("- (void)sendAction:(SEL)aSelector to:(BOOL)anObject;"); + EXPECT_EQ("- (NSUInteger)indexOfObject:(id)anObject;", + format("-(NSUInteger)indexOfObject:(id)anObject;")); + + EXPECT_EQ("- (NSInteger)Mthod1;", + format("-(NSInteger)Mthod1;")); + EXPECT_EQ("+ (id)Mthod2;", format("+(id)Mthod2;")); + EXPECT_EQ("- (NSInteger)Method3:(id)anObject;", + format("-(NSInteger)Method3:(id)anObject;")); + EXPECT_EQ("- (NSInteger)Method4:(id)anObject;", + format("-(NSInteger)Method4:(id)anObject;")); + EXPECT_EQ("- (NSInteger)Method5:(id)anObject:(id)AnotherObject;", + format("-(NSInteger)Method5:(id)anObject:(id)AnotherObject;")); + EXPECT_EQ("- (id)Method6:(id)A:(id)B:(id)C:(id)D;", + format("- (id)Method6:(id)A:(id)B:(id)C:(id)D;")); + EXPECT_EQ("- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;", + format("- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;")); +} + TEST_F(FormatTest, DoesNotChangeCorrectlyFormatedCode) { EXPECT_EQ(";", format(";")); } Index: unittests/Tooling/RefactoringTest.cpp =================================================================== --- unittests/Tooling/RefactoringTest.cpp (revision 170208) +++ unittests/Tooling/RefactoringTest.cpp (working copy) @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// -#include "RewriterTestContext.h" +#include "clang/Format/RewriterFormatContext.h" #include "clang/AST/ASTConsumer.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclCXX.h" Index: unittests/Tooling/RewriterTest.cpp =================================================================== --- unittests/Tooling/RewriterTest.cpp (revision 170208) +++ unittests/Tooling/RewriterTest.cpp (working copy) @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// -#include "RewriterTestContext.h" +#include "clang/Format/RewriterFormatContext.h" #include "gtest/gtest.h" namespace clang { Index: unittests/Tooling/RefactoringCallbacksTest.cpp =================================================================== --- unittests/Tooling/RefactoringCallbacksTest.cpp (revision 170208) +++ unittests/Tooling/RefactoringCallbacksTest.cpp (working copy) @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// #include "clang/Tooling/RefactoringCallbacks.h" -#include "RewriterTestContext.h" +#include "clang/Format/RewriterFormatContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "gtest/gtest.h" Index: unittests/Tooling/RewriterTestContext.h =================================================================== --- unittests/Tooling/RewriterTestContext.h (revision 170208) +++ unittests/Tooling/RewriterTestContext.h (working copy) @@ -1,128 +0,0 @@ -//===--- RewriterTestContext.h ----------------------------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// This file defines a utility class for Rewriter related tests. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_REWRITER_TEST_CONTEXT_H -#define LLVM_CLANG_REWRITER_TEST_CONTEXT_H - -#include "clang/Basic/Diagnostic.h" -#include "clang/Basic/DiagnosticOptions.h" -#include "clang/Basic/FileManager.h" -#include "clang/Basic/LangOptions.h" -#include "clang/Basic/SourceManager.h" -#include "clang/Frontend/TextDiagnosticPrinter.h" -#include "clang/Rewrite/Core/Rewriter.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/Path.h" -#include "llvm/Support/raw_ostream.h" - -namespace clang { - -/// \brief A class that sets up a ready to use Rewriter. -/// -/// Useful in unit tests that need a Rewriter. Creates all dependencies -/// of a Rewriter with default values for testing and provides convenience -/// methods, which help with writing tests that change files. -class RewriterTestContext { - public: - RewriterTestContext() - : DiagOpts(new DiagnosticOptions()), - Diagnostics(llvm::IntrusiveRefCntPtr(new DiagnosticIDs), - &*DiagOpts), - DiagnosticPrinter(llvm::outs(), &*DiagOpts), - Files((FileSystemOptions())), - Sources(Diagnostics, Files), - Rewrite(Sources, Options) { - Diagnostics.setClient(&DiagnosticPrinter, false); - } - - ~RewriterTestContext() { - if (!TemporaryDirectory.empty()) { - uint32_t RemovedCount = 0; - llvm::sys::fs::remove_all(TemporaryDirectory.str(), RemovedCount); - } - } - - FileID createInMemoryFile(StringRef Name, StringRef Content) { - const llvm::MemoryBuffer *Source = - llvm::MemoryBuffer::getMemBuffer(Content); - const FileEntry *Entry = - Files.getVirtualFile(Name, Source->getBufferSize(), 0); - Sources.overrideFileContents(Entry, Source, true); - assert(Entry != NULL); - return Sources.createFileID(Entry, SourceLocation(), SrcMgr::C_User); - } - - FileID createOnDiskFile(StringRef Name, StringRef Content) { - if (TemporaryDirectory.empty()) { - int FD; - bool error = - llvm::sys::fs::unique_file("rewriter-test-%%-%%-%%-%%/anchor", FD, - TemporaryDirectory); - assert(!error); (void)error; - llvm::raw_fd_ostream Closer(FD, /*shouldClose=*/true); - TemporaryDirectory = llvm::sys::path::parent_path(TemporaryDirectory); - } - llvm::SmallString<1024> Path(TemporaryDirectory); - llvm::sys::path::append(Path, Name); - std::string ErrorInfo; - llvm::raw_fd_ostream OutStream(Path.c_str(), - ErrorInfo, llvm::raw_fd_ostream::F_Binary); - assert(ErrorInfo.empty()); - OutStream << Content; - OutStream.close(); - const FileEntry *File = Files.getFile(Path); - assert(File != NULL); - return Sources.createFileID(File, SourceLocation(), SrcMgr::C_User); - } - - SourceLocation getLocation(FileID ID, unsigned Line, unsigned Column) { - SourceLocation Result = Sources.translateFileLineCol( - Sources.getFileEntryForID(ID), Line, Column); - assert(Result.isValid()); - return Result; - } - - std::string getRewrittenText(FileID ID) { - std::string Result; - llvm::raw_string_ostream OS(Result); - Rewrite.getEditBuffer(ID).write(OS); - OS.flush(); - return Result; - } - - std::string getFileContentFromDisk(StringRef Name) { - llvm::SmallString<1024> Path(TemporaryDirectory.str()); - llvm::sys::path::append(Path, Name); - // We need to read directly from the FileManager without relaying through - // a FileEntry, as otherwise we'd read through an already opened file - // descriptor, which might not see the changes made. - // FIXME: Figure out whether there is a way to get the SourceManger to - // reopen the file. - return Files.getBufferForFile(Path, NULL)->getBuffer(); - } - - llvm::IntrusiveRefCntPtr DiagOpts; - DiagnosticsEngine Diagnostics; - TextDiagnosticPrinter DiagnosticPrinter; - FileManager Files; - SourceManager Sources; - LangOptions Options; - Rewriter Rewrite; - - // Will be set once on disk files are generated. - SmallString<128> TemporaryDirectory; -}; - -} // end namespace clang - -#endif Index: lib/Format/Format.cpp =================================================================== --- lib/Format/Format.cpp (revision 170208) +++ lib/Format/Format.cpp (working copy) @@ -38,7 +38,8 @@ TT_PointerOrReference, TT_ConditionalExpr, TT_LineComment, - TT_BlockComment + TT_BlockComment, + TT_ObjectiveCMethodDecl }; TokenType Type; @@ -210,7 +211,8 @@ State.LastSpace[ParenLevel] = State.Indent[ParenLevel]; if (Current.Tok.is(tok::colon) && - Annotations[Index].Type != TokenAnnotation::TT_ConditionalExpr) { + Annotations[Index].Type != TokenAnnotation::TT_ConditionalExpr && + Annotations[0].Type != TokenAnnotation::TT_ObjectiveCMethodDecl) { State.Indent[ParenLevel] += 2; State.CtorInitializerOnNewLine = true; State.InCtorInitializer = true; @@ -226,7 +228,8 @@ if (Previous.Tok.is(tok::l_paren) || Annotations[Index - 1].Type == TokenAnnotation::TT_TemplateOpener) State.Indent[ParenLevel] = State.Column; - if (Current.Tok.is(tok::colon)) { + if (Current.Tok.is(tok::colon) && + Annotations[0].Type != TokenAnnotation::TT_ObjectiveCMethodDecl) { State.Indent[ParenLevel] = State.Column + 3; State.InCtorInitializer = true; } @@ -536,16 +539,48 @@ Parser.parseLine(); determineTokenTypes(); - + bool IsObjCMethodDecl = Line.Tokens.size() > 0 && + (Annotations[0].Type == TokenAnnotation::TT_ObjectiveCMethodDecl); for (int i = 1, e = Line.Tokens.size(); i != e; ++i) { TokenAnnotation &Annotation = Annotations[i]; Annotation.CanBreakBefore = canBreakBetween(Line.Tokens[i - 1], Line.Tokens[i]); - - if (Line.Tokens[i].Tok.is(tok::colon)) { + // In ObjC method declaration, in parameter list ':' + // must line break on 2nd as it identifies the next parameter + if (IsObjCMethodDecl && + Line.Tokens[i].Tok.is(tok::identifier) && + (i != e-1) && Line.Tokens[i+1].Tok.is(tok::colon) && + Line.Tokens[i-1].Tok.is(tok::identifier)) { + Annotation.CanBreakBefore = true; + Annotation.SpaceRequiredBefore = true; + } + else if (IsObjCMethodDecl && + Line.Tokens[i].Tok.is(tok::identifier) && + Line.Tokens[i-1].Tok.is(tok::l_paren) && + Line.Tokens[i-2].Tok.is(tok::colon)) { + // Don't break this identifier as ':' or identifier + // before it will break. + Annotation.CanBreakBefore = false; + } + else if (Line.Tokens[i].Tok.is(tok::at) && + Line.Tokens[i-2].Tok.is(tok::at)) { + // Don't put two objc's '@' on the same line. This could happen, + // as in, @optinal @property ... + Annotation.MustBreakBefore = true; + } + else if (Line.Tokens[i].Tok.is(tok::colon)) { Annotation.SpaceRequiredBefore = - Line.Tokens[0].Tok.isNot(tok::kw_case) && i != e - 1; + Line.Tokens[0].Tok.isNot(tok::kw_case) && !IsObjCMethodDecl && + (i != e - 1); + // Don't break at ':' if identifier before it can beak. + if (IsObjCMethodDecl && + Line.Tokens[i-1].Tok.is(tok::identifier) && + Annotations[i-1].CanBreakBefore) + Annotation.CanBreakBefore = false; + } else if (Annotations[i - 1].Type == + TokenAnnotation::TT_ObjectiveCMethodDecl) { + Annotation.SpaceRequiredBefore = true; } else if (Annotations[i - 1].Type == TokenAnnotation::TT_UnaryOperator) { Annotation.SpaceRequiredBefore = false; } else if (Annotation.Type == TokenAnnotation::TT_UnaryOperator) { @@ -570,6 +605,16 @@ } else if (Line.Tokens[i].Tok.is(tok::less) && Line.Tokens[0].Tok.is(tok::hash)) { Annotation.SpaceRequiredBefore = true; + } else if (IsObjCMethodDecl && + Line.Tokens[i - 1].Tok.is(tok::r_paren) && + Line.Tokens[i].Tok.is(tok::identifier)) { + // Don't space between ')' and + Annotation.SpaceRequiredBefore = false; + } else if (IsObjCMethodDecl && + Line.Tokens[i - 1].Tok.is(tok::colon) && + Line.Tokens[i].Tok.is(tok::l_paren)) { + // Don't space between ':' and '(' + Annotation.SpaceRequiredBefore = false; } else { Annotation.SpaceRequiredBefore = spaceRequiredBetween(Line.Tokens[i - 1].Tok, Line.Tokens[i].Tok); @@ -604,6 +649,9 @@ if (Tok.Tok.is(tok::star) || Tok.Tok.is(tok::amp)) Annotation.Type = determineStarAmpUsage(i, AssignmentEncountered); + else if ((Tok.Tok.is(tok::minus) || Tok.Tok.is(tok::plus)) && + Tok.Tok.isAtStartOfLine()) + Annotation.Type = TokenAnnotation::TT_ObjectiveCMethodDecl; else if (isUnaryOperator(i)) Annotation.Type = TokenAnnotation::TT_UnaryOperator; else if (isBinaryOperator(Line.Tokens[i])) @@ -697,6 +745,8 @@ return false; if (Left.is(tok::exclaim) || Left.is(tok::tilde)) return false; + if (Left.is(tok::at) && Right.is(tok::identifier)) + return false; if (Left.is(tok::less) || Right.is(tok::greater) || Right.is(tok::less)) return false; if (Right.is(tok::amp) || Right.is(tok::star))