Skip to content

Commit 0badd09

Browse files
committed
[clang-format] extend clang-format directive with options to prevent formatting for one line
1 parent e9dc6c5 commit 0badd09

9 files changed

+154
-35
lines changed

clang/include/clang/Format/Format.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5620,8 +5620,15 @@ inline StringRef getLanguageName(FormatStyle::LanguageKind Language) {
56205620
}
56215621
}
56225622

5623-
bool isClangFormatOn(StringRef Comment);
5624-
bool isClangFormatOff(StringRef Comment);
5623+
enum class ClangFormatDirective {
5624+
None,
5625+
Off,
5626+
On,
5627+
OffLine,
5628+
OffNextLine,
5629+
};
5630+
5631+
ClangFormatDirective parseClangFormatDirective(StringRef Comment);
56255632

56265633
} // end namespace format
56275634
} // end namespace clang

clang/lib/Format/DefinitionBlockSeparator.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,8 @@ void DefinitionBlockSeparator::separateBlocks(
144144
return false;
145145

146146
if (const auto *Tok = OperateLine->First;
147-
Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
147+
Tok->is(tok::comment) && parseClangFormatDirective(Tok->TokenText) ==
148+
ClangFormatDirective::None) {
148149
return true;
149150
}
150151

clang/lib/Format/Format.cpp

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3267,9 +3267,9 @@ tooling::Replacements sortCppIncludes(const FormatStyle &Style, StringRef Code,
32673267

32683268
bool IsBlockComment = false;
32693269

3270-
if (isClangFormatOff(Trimmed)) {
3270+
if (parseClangFormatDirective(Trimmed) == ClangFormatDirective::Off) {
32713271
FormattingOff = true;
3272-
} else if (isClangFormatOn(Trimmed)) {
3272+
} else if (parseClangFormatDirective(Trimmed) == ClangFormatDirective::On) {
32733273
FormattingOff = false;
32743274
} else if (Trimmed.starts_with("/*")) {
32753275
IsBlockComment = true;
@@ -3452,9 +3452,9 @@ tooling::Replacements sortJavaImports(const FormatStyle &Style, StringRef Code,
34523452
Code.substr(Prev, (Pos != StringRef::npos ? Pos : Code.size()) - Prev);
34533453

34543454
StringRef Trimmed = Line.trim();
3455-
if (isClangFormatOff(Trimmed))
3455+
if (parseClangFormatDirective(Trimmed) == ClangFormatDirective::Off)
34563456
FormattingOff = true;
3457-
else if (isClangFormatOn(Trimmed))
3457+
else if (parseClangFormatDirective(Trimmed) == ClangFormatDirective::On)
34583458
FormattingOff = false;
34593459

34603460
if (ImportRegex.match(Line, &Matches)) {
@@ -4190,24 +4190,45 @@ Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
41904190
return FallbackStyle;
41914191
}
41924192

4193-
static bool isClangFormatOnOff(StringRef Comment, bool On) {
4194-
if (Comment == (On ? "/* clang-format on */" : "/* clang-format off */"))
4195-
return true;
4193+
static unsigned skipWhitespace(unsigned Pos, StringRef Str, unsigned Length) {
4194+
while (Pos < Length && isspace(Str[Pos]))
4195+
++Pos;
4196+
return Pos;
4197+
}
41964198

4197-
static const char ClangFormatOn[] = "// clang-format on";
4198-
static const char ClangFormatOff[] = "// clang-format off";
4199-
const unsigned Size = (On ? sizeof ClangFormatOn : sizeof ClangFormatOff) - 1;
4199+
ClangFormatDirective parseClangFormatDirective(StringRef Comment) {
4200+
size_t Pos = std::min(Comment.find("/*"), Comment.find("//"));
4201+
unsigned Length = Comment.size();
4202+
if (Pos == StringRef::npos)
4203+
return ClangFormatDirective::None;
42004204

4201-
return Comment.starts_with(On ? ClangFormatOn : ClangFormatOff) &&
4202-
(Comment.size() == Size || Comment[Size] == ':');
4203-
}
4205+
Pos = skipWhitespace(Pos + 2, Comment, Length);
4206+
StringRef ClangFormatDirectiveName = "clang-format";
42044207

4205-
bool isClangFormatOn(StringRef Comment) {
4206-
return isClangFormatOnOff(Comment, /*On=*/true);
4207-
}
4208+
if (Comment.substr(Pos, ClangFormatDirectiveName.size()) ==
4209+
ClangFormatDirectiveName) {
4210+
Pos =
4211+
skipWhitespace(Pos + ClangFormatDirectiveName.size(), Comment, Length);
4212+
4213+
unsigned EndDirectiveValuePos = Pos;
4214+
while (EndDirectiveValuePos < Length) {
4215+
char Char = Comment[EndDirectiveValuePos];
4216+
if (isspace(Char) || Char == '*' || Char == ':')
4217+
break;
4218+
4219+
++EndDirectiveValuePos;
4220+
}
4221+
4222+
return llvm::StringSwitch<ClangFormatDirective>(
4223+
Comment.substr(Pos, EndDirectiveValuePos - Pos))
4224+
.Case("off", ClangFormatDirective::Off)
4225+
.Case("on", ClangFormatDirective::On)
4226+
.Case("off-line", ClangFormatDirective::OffLine)
4227+
.Case("off-next-line", ClangFormatDirective::OffNextLine)
4228+
.Default(ClangFormatDirective::None);
4229+
}
42084230

4209-
bool isClangFormatOff(StringRef Comment) {
4210-
return isClangFormatOnOff(Comment, /*On=*/false);
4231+
return ClangFormatDirective::None;
42114232
}
42124233

42134234
} // namespace format

clang/lib/Format/FormatTokenLexer.cpp

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ FormatTokenLexer::FormatTokenLexer(
3232
LangOpts(getFormattingLangOpts(Style)), SourceMgr(SourceMgr), ID(ID),
3333
Style(Style), IdentTable(IdentTable), Keywords(IdentTable),
3434
Encoding(Encoding), Allocator(Allocator), FirstInLineIndex(0),
35-
FormattingDisabled(false), MacroBlockBeginRegex(Style.MacroBlockBegin),
35+
FDS(FormatDisableState::None),
36+
MacroBlockBeginRegex(Style.MacroBlockBegin),
3637
MacroBlockEndRegex(Style.MacroBlockEnd) {
3738
Lex.reset(new Lexer(ID, SourceMgr.getBufferOrFake(ID), SourceMgr, LangOpts));
3839
Lex->SetKeepWhitespaceMode(true);
@@ -82,7 +83,23 @@ ArrayRef<FormatToken *> FormatTokenLexer::lex() {
8283
assert(Tokens.empty());
8384
assert(FirstInLineIndex == 0);
8485
do {
85-
Tokens.push_back(getNextToken());
86+
FormatToken *NextToken = getNextToken();
87+
88+
if (FDS == FormatDisableState::None && NextToken->is(tok::comment) &&
89+
parseClangFormatDirective(NextToken->TokenText) ==
90+
ClangFormatDirective::OffLine) {
91+
for (unsigned i = FirstInLineIndex; i < Tokens.size(); ++i)
92+
Tokens[i]->Finalized = true;
93+
}
94+
95+
if (Tokens.size() >= 1 && Tokens.back()->isNot(tok::comment) &&
96+
FDS == FormatDisableState::SingleLine &&
97+
(NextToken->NewlinesBefore > 0 || NextToken->IsMultiline)) {
98+
FDS = FormatDisableState::None;
99+
}
100+
101+
Tokens.push_back(NextToken);
102+
86103
if (Style.isJavaScript()) {
87104
tryParseJSRegexLiteral();
88105
handleTemplateStrings();
@@ -1450,13 +1467,21 @@ void FormatTokenLexer::readRawToken(FormatToken &Tok) {
14501467
if ((Style.isJavaScript() || Style.isProto()) && Tok.is(tok::char_constant))
14511468
Tok.Tok.setKind(tok::string_literal);
14521469

1453-
if (Tok.is(tok::comment) && isClangFormatOn(Tok.TokenText))
1454-
FormattingDisabled = false;
1470+
if (Tok.is(tok::comment) &&
1471+
parseClangFormatDirective(Tok.TokenText) == ClangFormatDirective::On) {
1472+
FDS = FormatDisableState::None;
1473+
}
1474+
1475+
Tok.Finalized = FDS != FormatDisableState::None;
14551476

1456-
Tok.Finalized = FormattingDisabled;
1477+
if (Tok.is(tok::comment)) {
1478+
ClangFormatDirective FSD = parseClangFormatDirective(Tok.TokenText);
1479+
if (FSD == ClangFormatDirective::Off)
1480+
FDS = FormatDisableState::Range;
14571481

1458-
if (Tok.is(tok::comment) && isClangFormatOff(Tok.TokenText))
1459-
FormattingDisabled = true;
1482+
if (FSD == ClangFormatDirective::OffNextLine)
1483+
FDS = FormatDisableState::SingleLine;
1484+
}
14601485
}
14611486

14621487
void FormatTokenLexer::resetLexer(unsigned Offset) {

clang/lib/Format/FormatTokenLexer.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ enum LexerState {
3232
TOKEN_STASHED,
3333
};
3434

35+
enum class FormatDisableState {
36+
None,
37+
SingleLine,
38+
Range,
39+
};
40+
3541
class FormatTokenLexer {
3642
public:
3743
FormatTokenLexer(const SourceManager &SourceMgr, FileID ID, unsigned Column,
@@ -131,7 +137,7 @@ class FormatTokenLexer {
131137

132138
llvm::SmallPtrSet<IdentifierInfo *, 8> TemplateNames, TypeNames;
133139

134-
bool FormattingDisabled;
140+
FormatDisableState FDS;
135141

136142
llvm::Regex MacroBlockBeginRegex;
137143
llvm::Regex MacroBlockEndRegex;

clang/lib/Format/IntegerLiteralSeparatorFixer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ IntegerLiteralSeparatorFixer::process(const Environment &Env,
9393
auto Location = Tok.getLocation();
9494
auto Text = StringRef(SourceMgr.getCharacterData(Location), Length);
9595
if (Tok.is(tok::comment)) {
96-
if (isClangFormatOff(Text))
96+
if (parseClangFormatDirective(Text) == ClangFormatDirective::Off)
9797
Skip = true;
98-
else if (isClangFormatOn(Text))
98+
else if (parseClangFormatDirective(Text) == ClangFormatDirective::On)
9999
Skip = false;
100100
continue;
101101
}

clang/lib/Format/SortJavaScriptImports.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ class JavaScriptImportSorter : public TokenAnalyzer {
194194
// Separate references from the main code body of the file.
195195
if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 &&
196196
!(FirstNonImportLine->First->is(tok::comment) &&
197-
isClangFormatOn(FirstNonImportLine->First->TokenText.trim()))) {
197+
parseClangFormatDirective(
198+
FirstNonImportLine->First->TokenText.trim()) ==
199+
ClangFormatDirective::On)) {
198200
ReferencesText += "\n";
199201
}
200202

@@ -375,9 +377,11 @@ class JavaScriptImportSorter : public TokenAnalyzer {
375377
// This is tracked in FormattingOff here and on JsModuleReference.
376378
while (Current && Current->is(tok::comment)) {
377379
StringRef CommentText = Current->TokenText.trim();
378-
if (isClangFormatOff(CommentText)) {
380+
if (parseClangFormatDirective(CommentText) ==
381+
ClangFormatDirective::Off) {
379382
FormattingOff = true;
380-
} else if (isClangFormatOn(CommentText)) {
383+
} else if (parseClangFormatDirective(CommentText) ==
384+
ClangFormatDirective::On) {
381385
FormattingOff = false;
382386
// Special case: consider a trailing "clang-format on" line to be part
383387
// of the module reference, so that it gets moved around together with

clang/lib/Format/TokenAnnotator.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3559,7 +3559,9 @@ void TokenAnnotator::setCommentLineLevels(
35593559
// If the comment is currently aligned with the line immediately following
35603560
// it, that's probably intentional and we should keep it.
35613561
if (NextNonCommentLine && NextNonCommentLine->First->NewlinesBefore < 2 &&
3562-
Line->isComment() && !isClangFormatOff(Line->First->TokenText) &&
3562+
Line->isComment() &&
3563+
parseClangFormatDirective(Line->First->TokenText) ==
3564+
ClangFormatDirective::None &&
35633565
NextNonCommentLine->First->OriginalColumn ==
35643566
Line->First->OriginalColumn) {
35653567
const bool PPDirectiveOrImportStmt =

clang/unittests/Format/FormatTest.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28273,6 +28273,59 @@ TEST_F(FormatTest, KeepFormFeed) {
2827328273
Style);
2827428274
}
2827528275

28276+
TEST_F(FormatTest, DisableLine) {
28277+
verifyFormat("int a = 1; // clang-format off-line\n"
28278+
"int b = 1;",
28279+
"int a = 1; // clang-format off-line\n"
28280+
"int b = 1;");
28281+
28282+
verifyFormat("int a = 1;\n"
28283+
"int b = 1; // clang-format off-line\n"
28284+
"int c = 1;",
28285+
"int a = 1;\n"
28286+
"int b = 1; // clang-format off-line\n"
28287+
"int c = 1;");
28288+
28289+
verifyFormat("int a = 1; /* clang-format off-line */\n"
28290+
"int b = 1;",
28291+
"int a = 1; /* clang-format off-line */\n"
28292+
"int b = 1;");
28293+
28294+
verifyFormat("int a = 1;\n"
28295+
"int b = 1; /* clang-format off-line */\n"
28296+
"int c = 1;",
28297+
"int a = 1;\n"
28298+
"int b = 1; /* clang-format off-line */\n"
28299+
"int c = 1;");
28300+
}
28301+
28302+
TEST_F(FormatTest, DisableNextLine) {
28303+
verifyFormat("// clang-format off-next-line\n"
28304+
"int a = 1;\n"
28305+
"int b = 1;",
28306+
"// clang-format off-next-line\n"
28307+
"int a = 1;\n"
28308+
"int b = 1;");
28309+
28310+
verifyFormat("// clang-format off-next-line\n"
28311+
"\n"
28312+
"\n"
28313+
"int a = 1;\n"
28314+
"int b = 1;",
28315+
"// clang-format off-next-line\n"
28316+
"\n"
28317+
"\n"
28318+
"int a = 1;\n"
28319+
"int b = 1;");
28320+
28321+
verifyFormat("/* clang-format off-next-line */\n"
28322+
"int a = 1;\n"
28323+
"int b = 1;",
28324+
"/* clang-format off-next-line */\n"
28325+
"int a = 1;\n"
28326+
"int b = 1;");
28327+
}
28328+
2827628329
} // namespace
2827728330
} // namespace test
2827828331
} // namespace format

0 commit comments

Comments
 (0)