From 707e68fb210e6448b2bd087a594dfd6fe3fb8d5c Mon Sep 17 00:00:00 2001 From: Ben Hamilton Date: Wed, 30 May 2018 15:21:38 +0000 Subject: [PATCH] [clang-format/ObjC] Correctly parse Objective-C methods with 'class' in name Summary: Please take a close look at this CL. I haven't touched much of `UnwrappedLineParser` before, so I may have gotten things wrong. Previously, clang-format would incorrectly format the following: ``` @implementation Foo - (Class)class { } - (void)foo { } @end ``` as: ``` @implementation Foo - (Class)class { } - (void)foo { } @end ``` The problem is whenever `UnwrappedLineParser::parseStructuralElement()` sees any of the keywords `class`, `struct`, or `enum`, it calls `parseRecord()` to parse them as a C/C++ record. This causes subsequent lines to be parsed incorrectly, which causes them to be indented incorrectly. In Objective-C/Objective-C++, these keywords are valid selector components. This diff fixes the issue by explicitly handling `+` and `-` lines inside `@implementation` / `@interface` / `@protocol` blocks and parsing them as Objective-C methods. Test Plan: New tests added. Ran tests with: make -j16 FormatTests && ./tools/clang/unittests/Format/FormatTests Reviewers: jolesiak, klimek Reviewed By: jolesiak, klimek Subscribers: klimek, cfe-commits, Wizard Differential Revision: https://reviews.llvm.org/D47095 llvm-svn: 333553 --- clang/lib/Format/UnwrappedLineParser.cpp | 21 ++++++++ clang/lib/Format/UnwrappedLineParser.h | 1 + clang/unittests/Format/FormatTestObjC.cpp | 62 ++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index f1b0f85eacf9..b257b2b74dd8 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -2130,6 +2130,24 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr) { // "} n, m;" will end up in one unwrapped line. } +void UnwrappedLineParser::parseObjCMethod() { + assert(FormatTok->Tok.isOneOf(tok::l_paren, tok::identifier) && + "'(' or identifier expected."); + do { + if (FormatTok->Tok.is(tok::semi)) { + nextToken(); + addUnwrappedLine(); + return; + } else if (FormatTok->Tok.is(tok::l_brace)) { + parseBlock(/*MustBeDeclaration=*/false); + addUnwrappedLine(); + return; + } else { + nextToken(); + } + } while (!eof()); +} + void UnwrappedLineParser::parseObjCProtocolList() { assert(FormatTok->Tok.is(tok::less) && "'<' expected."); do { @@ -2157,6 +2175,9 @@ void UnwrappedLineParser::parseObjCUntilAtEnd() { // Ignore stray "}". parseStructuralElement doesn't consume them. nextToken(); addUnwrappedLine(); + } else if (FormatTok->isOneOf(tok::minus, tok::plus)) { + nextToken(); + parseObjCMethod(); } else { parseStructuralElement(); } diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h index 9a171362805f..87254832c635 100644 --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -120,6 +120,7 @@ private: // parses the record as a child block, i.e. if the class declaration is an // expression. void parseRecord(bool ParseAsExpr = false); + void parseObjCMethod(); void parseObjCProtocolList(); void parseObjCUntilAtEnd(); void parseObjCInterfaceOrImplementation(); diff --git a/clang/unittests/Format/FormatTestObjC.cpp b/clang/unittests/Format/FormatTestObjC.cpp index c29c9d702bc9..c29bf8545ab1 100644 --- a/clang/unittests/Format/FormatTestObjC.cpp +++ b/clang/unittests/Format/FormatTestObjC.cpp @@ -328,7 +328,14 @@ TEST_F(FormatTestObjC, FormatObjCInterface) { "}\n" "+ (id)init;\n" "@end"); - + verifyFormat("@interface Foo\n" + "- (void)foo {\n" + "}\n" + "@end\n" + "@implementation Bar\n" + "- (void)bar {\n" + "}\n" + "@end"); Style.ColumnLimit = 40; verifyFormat("@interface ccccccccccccc () <\n" " ccccccccccccc, ccccccccccccc,\n" @@ -969,6 +976,59 @@ TEST_F(FormatTestObjC, ObjCCxxKeywords) { verifyFormat("MACRO(new:)\n"); verifyFormat("MACRO(delete:)\n"); verifyFormat("foo = @{MACRO(new:) : MACRO(delete:)}\n"); + verifyFormat("@implementation Foo\n" + "// Testing\n" + "- (Class)class {\n" + "}\n" + "- (void)foo {\n" + "}\n" + "@end\n"); + verifyFormat("@implementation Foo\n" + "- (Class)class {\n" + "}\n" + "- (void)foo {\n" + "}\n" + "@end"); + verifyFormat("@implementation Foo\n" + "+ (Class)class {\n" + "}\n" + "- (void)foo {\n" + "}\n" + "@end"); + verifyFormat("@implementation Foo\n" + "- (Class)class:(Class)klass {\n" + "}\n" + "- (void)foo {\n" + "}\n" + "@end"); + verifyFormat("@implementation Foo\n" + "+ (Class)class:(Class)klass {\n" + "}\n" + "- (void)foo {\n" + "}\n" + "@end"); + + verifyFormat("@interface Foo\n" + "// Testing\n" + "- (Class)class;\n" + "- (void)foo;\n" + "@end\n"); + verifyFormat("@interface Foo\n" + "- (Class)class;\n" + "- (void)foo;\n" + "@end"); + verifyFormat("@interface Foo\n" + "+ (Class)class;\n" + "- (void)foo;\n" + "@end"); + verifyFormat("@interface Foo\n" + "- (Class)class:(Class)klass;\n" + "- (void)foo;\n" + "@end"); + verifyFormat("@interface Foo\n" + "+ (Class)class:(Class)klass;\n" + "- (void)foo;\n" + "@end"); } TEST_F(FormatTestObjC, ObjCLiterals) {