Skip to content

[clang-format] extend clang-format directive with options to prevent formatting for one line #118566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,7 @@ clang-format
- Adds ``KeepFormFeed`` option and set it to ``true`` for ``GNU`` style.
- Adds ``AllowShortNamespacesOnASingleLine`` option.
- Adds support for bash globstar in ``.clang-format-ignore``.
- Adds ``off-line`` and ``off-next-line`` options to the ``clang-format`` directive.

libclang
--------
Expand Down
11 changes: 9 additions & 2 deletions clang/include/clang/Format/Format.h
Original file line number Diff line number Diff line change
Expand Up @@ -5626,8 +5626,15 @@ inline StringRef getLanguageName(FormatStyle::LanguageKind Language) {
}
}

bool isClangFormatOn(StringRef Comment);
bool isClangFormatOff(StringRef Comment);
enum class ClangFormatDirective {
None,
Off,
On,
OffLine,
OffNextLine,
};

ClangFormatDirective parseClangFormatDirective(StringRef Comment);

} // end namespace format
} // end namespace clang
Expand Down
3 changes: 2 additions & 1 deletion clang/lib/Format/DefinitionBlockSeparator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ void DefinitionBlockSeparator::separateBlocks(
return false;

if (const auto *Tok = OperateLine->First;
Tok->is(tok::comment) && !isClangFormatOn(Tok->TokenText)) {
Tok->is(tok::comment) && parseClangFormatDirective(Tok->TokenText) !=
ClangFormatDirective::On) {
return true;
}

Expand Down
59 changes: 41 additions & 18 deletions clang/lib/Format/Format.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3276,10 +3276,11 @@ tooling::Replacements sortCppIncludes(const FormatStyle &Style, StringRef Code,
FormattingOff = false;

bool IsBlockComment = false;
ClangFormatDirective CFD = parseClangFormatDirective(Trimmed);

if (isClangFormatOff(Trimmed)) {
if (CFD == ClangFormatDirective::Off) {
FormattingOff = true;
} else if (isClangFormatOn(Trimmed)) {
} else if (CFD == ClangFormatDirective::On) {
FormattingOff = false;
} else if (Trimmed.starts_with("/*")) {
IsBlockComment = true;
Expand Down Expand Up @@ -3462,9 +3463,10 @@ tooling::Replacements sortJavaImports(const FormatStyle &Style, StringRef Code,
Code.substr(Prev, (Pos != StringRef::npos ? Pos : Code.size()) - Prev);

StringRef Trimmed = Line.trim();
if (isClangFormatOff(Trimmed))
ClangFormatDirective CFD = parseClangFormatDirective(Trimmed);
if (CFD == ClangFormatDirective::Off)
FormattingOff = true;
else if (isClangFormatOn(Trimmed))
else if (CFD == ClangFormatDirective::On)
FormattingOff = false;

if (ImportRegex.match(Line, &Matches)) {
Expand Down Expand Up @@ -4200,24 +4202,45 @@ Expected<FormatStyle> getStyle(StringRef StyleName, StringRef FileName,
return FallbackStyle;
}

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

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

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

bool isClangFormatOn(StringRef Comment) {
return isClangFormatOnOff(Comment, /*On=*/true);
}
if (Comment.substr(Pos, ClangFormatDirectiveName.size()) ==
ClangFormatDirectiveName) {
Pos =
skipWhitespace(Pos + ClangFormatDirectiveName.size(), Comment, Length);

unsigned EndDirectiveValuePos = Pos;
while (EndDirectiveValuePos < Length) {
char Char = Comment[EndDirectiveValuePos];
if (isspace(Char) || Char == '*' || Char == ':')
break;

++EndDirectiveValuePos;
}

return llvm::StringSwitch<ClangFormatDirective>(
Comment.substr(Pos, EndDirectiveValuePos - Pos))
.Case("off", ClangFormatDirective::Off)
.Case("on", ClangFormatDirective::On)
.Case("off-line", ClangFormatDirective::OffLine)
.Case("off-next-line", ClangFormatDirective::OffNextLine)
.Default(ClangFormatDirective::None);
}

bool isClangFormatOff(StringRef Comment) {
return isClangFormatOnOff(Comment, /*On=*/false);
return ClangFormatDirective::None;
}

} // namespace format
Expand Down
39 changes: 32 additions & 7 deletions clang/lib/Format/FormatTokenLexer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ FormatTokenLexer::FormatTokenLexer(
LangOpts(getFormattingLangOpts(Style)), SourceMgr(SourceMgr), ID(ID),
Style(Style), IdentTable(IdentTable), Keywords(IdentTable),
Encoding(Encoding), Allocator(Allocator), FirstInLineIndex(0),
FormattingDisabled(false), MacroBlockBeginRegex(Style.MacroBlockBegin),
FDS(FormatDisableState::None),
MacroBlockBeginRegex(Style.MacroBlockBegin),
MacroBlockEndRegex(Style.MacroBlockEnd) {
Lex.reset(new Lexer(ID, SourceMgr.getBufferOrFake(ID), SourceMgr, LangOpts));
Lex->SetKeepWhitespaceMode(true);
Expand Down Expand Up @@ -82,7 +83,23 @@ ArrayRef<FormatToken *> FormatTokenLexer::lex() {
assert(Tokens.empty());
assert(FirstInLineIndex == 0);
do {
Tokens.push_back(getNextToken());
FormatToken *NextToken = getNextToken();

if (FDS == FormatDisableState::None && NextToken->is(tok::comment) &&
parseClangFormatDirective(NextToken->TokenText) ==
ClangFormatDirective::OffLine) {
for (unsigned i = FirstInLineIndex; i < Tokens.size(); ++i)
Tokens[i]->Finalized = true;
}

if (Tokens.size() >= 1 && Tokens.back()->isNot(tok::comment) &&
FDS == FormatDisableState::SingleLine &&
(NextToken->NewlinesBefore > 0 || NextToken->IsMultiline)) {
FDS = FormatDisableState::None;
}

Tokens.push_back(NextToken);

if (Style.isJavaScript()) {
tryParseJSRegexLiteral();
handleTemplateStrings();
Expand Down Expand Up @@ -1450,13 +1467,21 @@ void FormatTokenLexer::readRawToken(FormatToken &Tok) {
if ((Style.isJavaScript() || Style.isProto()) && Tok.is(tok::char_constant))
Tok.Tok.setKind(tok::string_literal);

if (Tok.is(tok::comment) && isClangFormatOn(Tok.TokenText))
FormattingDisabled = false;
if (Tok.is(tok::comment) &&
parseClangFormatDirective(Tok.TokenText) == ClangFormatDirective::On) {
FDS = FormatDisableState::None;
}

Tok.Finalized = FDS != FormatDisableState::None;

Tok.Finalized = FormattingDisabled;
if (Tok.is(tok::comment)) {
ClangFormatDirective FSD = parseClangFormatDirective(Tok.TokenText);
if (FSD == ClangFormatDirective::Off)
FDS = FormatDisableState::Range;

if (Tok.is(tok::comment) && isClangFormatOff(Tok.TokenText))
FormattingDisabled = true;
if (FSD == ClangFormatDirective::OffNextLine)
FDS = FormatDisableState::SingleLine;
}
}

void FormatTokenLexer::resetLexer(unsigned Offset) {
Expand Down
8 changes: 7 additions & 1 deletion clang/lib/Format/FormatTokenLexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ enum LexerState {
TOKEN_STASHED,
};

enum class FormatDisableState {
None,
SingleLine,
Range,
};

class FormatTokenLexer {
public:
FormatTokenLexer(const SourceManager &SourceMgr, FileID ID, unsigned Column,
Expand Down Expand Up @@ -131,7 +137,7 @@ class FormatTokenLexer {

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

bool FormattingDisabled;
FormatDisableState FDS;

llvm::Regex MacroBlockBeginRegex;
llvm::Regex MacroBlockEndRegex;
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Format/IntegerLiteralSeparatorFixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ IntegerLiteralSeparatorFixer::process(const Environment &Env,
auto Location = Tok.getLocation();
auto Text = StringRef(SourceMgr.getCharacterData(Location), Length);
if (Tok.is(tok::comment)) {
if (isClangFormatOff(Text))
if (parseClangFormatDirective(Text) == ClangFormatDirective::Off)
Skip = true;
else if (isClangFormatOn(Text))
else if (parseClangFormatDirective(Text) == ClangFormatDirective::On)
Skip = false;
continue;
}
Expand Down
10 changes: 7 additions & 3 deletions clang/lib/Format/SortJavaScriptImports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,9 @@ class JavaScriptImportSorter : public TokenAnalyzer {
// Separate references from the main code body of the file.
if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2 &&
!(FirstNonImportLine->First->is(tok::comment) &&
isClangFormatOn(FirstNonImportLine->First->TokenText.trim()))) {
parseClangFormatDirective(
FirstNonImportLine->First->TokenText.trim()) ==
ClangFormatDirective::On)) {
ReferencesText += "\n";
}

Expand Down Expand Up @@ -375,9 +377,11 @@ class JavaScriptImportSorter : public TokenAnalyzer {
// This is tracked in FormattingOff here and on JsModuleReference.
while (Current && Current->is(tok::comment)) {
StringRef CommentText = Current->TokenText.trim();
if (isClangFormatOff(CommentText)) {
ClangFormatDirective CFD = parseClangFormatDirective(CommentText);

if (CFD == ClangFormatDirective::Off) {
FormattingOff = true;
} else if (isClangFormatOn(CommentText)) {
} else if (CFD == ClangFormatDirective::On) {
FormattingOff = false;
// Special case: consider a trailing "clang-format on" line to be part
// of the module reference, so that it gets moved around together with
Expand Down
4 changes: 3 additions & 1 deletion clang/lib/Format/TokenAnnotator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3561,7 +3561,9 @@ void TokenAnnotator::setCommentLineLevels(
// If the comment is currently aligned with the line immediately following
// it, that's probably intentional and we should keep it.
if (NextNonCommentLine && NextNonCommentLine->First->NewlinesBefore < 2 &&
Line->isComment() && !isClangFormatOff(Line->First->TokenText) &&
Line->isComment() &&
parseClangFormatDirective(Line->First->TokenText) ==
ClangFormatDirective::None &&
NextNonCommentLine->First->OriginalColumn ==
Line->First->OriginalColumn) {
const bool PPDirectiveOrImportStmt =
Expand Down
53 changes: 53 additions & 0 deletions clang/unittests/Format/FormatTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28321,6 +28321,59 @@ TEST_F(FormatTest, KeepFormFeed) {
Style);
}

TEST_F(FormatTest, DisableLine) {
verifyFormat("int a = 1; // clang-format off-line\n"
"int b = 1;",
"int a = 1; // clang-format off-line\n"
"int b = 1;");

verifyFormat("int a = 1;\n"
"int b = 1; // clang-format off-line\n"
"int c = 1;",
"int a = 1;\n"
"int b = 1; // clang-format off-line\n"
"int c = 1;");

verifyFormat("int a = 1; /* clang-format off-line */\n"
"int b = 1;",
"int a = 1; /* clang-format off-line */\n"
"int b = 1;");

verifyFormat("int a = 1;\n"
"int b = 1; /* clang-format off-line */\n"
"int c = 1;",
"int a = 1;\n"
"int b = 1; /* clang-format off-line */\n"
"int c = 1;");
}

TEST_F(FormatTest, DisableNextLine) {
verifyFormat("// clang-format off-next-line\n"
"int a = 1;\n"
"int b = 1;",
"// clang-format off-next-line\n"
"int a = 1;\n"
"int b = 1;");

verifyFormat("// clang-format off-next-line\n"
"\n"
"\n"
"int a = 1;\n"
"int b = 1;",
"// clang-format off-next-line\n"
"\n"
"\n"
"int a = 1;\n"
"int b = 1;");

verifyFormat("/* clang-format off-next-line */\n"
"int a = 1;\n"
"int b = 1;",
"/* clang-format off-next-line */\n"
"int a = 1;\n"
"int b = 1;");
}

TEST_F(FormatTest, ShortNamespacesOption) {
auto Style = getLLVMStyle();
Style.AllowShortNamespacesOnASingleLine = true;
Expand Down
Loading