[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
This commit is contained in:
Ben Hamilton 2018-05-30 15:21:38 +00:00
parent 761abc05aa
commit 707e68fb21
3 changed files with 83 additions and 1 deletions

View File

@ -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();
}

View File

@ -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();

View File

@ -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) {