1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
11 #ifndef LO_CLANG_SHARED_PLUGINS
18 #include <unordered_set>
22 Check for child statements inside a compound statement that do not share the same indentation.
24 TODO if an open-brace starts on a new line by itself, check that it lines up with it's closing-brace
25 TODO else should line up with if
30 class Indentation
: public loplugin::FilteringPlugin
<Indentation
>
33 explicit Indentation(loplugin::InstantiationData
const& data
)
34 : FilteringPlugin(data
)
38 virtual bool preRun() override
40 std::string
fn(handler
.getMainFileName());
41 loplugin::normalizeDotDotInFilePath(fn
);
42 // include another file to build a table
43 if (fn
== SRCDIR
"/sc/source/core/tool/cellkeytranslator.cxx")
46 if (fn
== SRCDIR
"/sc/source/core/tool/compiler.cxx")
48 // looks like lex/yacc output
49 if (fn
== SRCDIR
"/hwpfilter/source/grammar.cxx")
52 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/vcl/qt5/")
53 || loplugin::isSamePathname(fn
, SRCDIR
"/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx"))
58 virtual void run() override
61 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
64 bool VisitCompoundStmt(CompoundStmt
const*);
65 bool PreTraverseSwitchStmt(SwitchStmt
*);
66 bool PostTraverseSwitchStmt(SwitchStmt
*, bool);
67 bool TraverseSwitchStmt(SwitchStmt
*);
68 bool VisitSwitchStmt(SwitchStmt
const*);
69 bool VisitIfStmt(IfStmt
const*);
70 bool VisitForStmt(ForStmt
const*);
71 bool VisitWhileStmt(WhileStmt
const*);
72 bool VisitDoStmt(DoStmt
const*);
73 bool VisitCXXForRangeStmt(CXXForRangeStmt
const*);
76 void checkCompoundStmtBraces(const Stmt
* parentStmt
, const CompoundStmt
*);
78 std::vector
<const Stmt
*> switchStmtBodies
;
79 std::unordered_set
<const Stmt
*> chainedSet
;
82 bool Indentation::PreTraverseSwitchStmt(SwitchStmt
* switchStmt
)
84 switchStmtBodies
.push_back(switchStmt
->getBody());
88 bool Indentation::PostTraverseSwitchStmt(SwitchStmt
*, bool)
90 switchStmtBodies
.pop_back();
94 bool Indentation::TraverseSwitchStmt(SwitchStmt
* switchStmt
)
96 PreTraverseSwitchStmt(switchStmt
);
97 auto ret
= FilteringPlugin::TraverseSwitchStmt(switchStmt
);
98 PostTraverseSwitchStmt(switchStmt
, ret
);
102 bool Indentation::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
104 if (ignoreLocation(compoundStmt
))
106 // these are kind of free form
107 if (!switchStmtBodies
.empty() && switchStmtBodies
.back() == compoundStmt
)
110 constexpr unsigned MAX
= std::numeric_limits
<unsigned>::max();
111 unsigned column
= MAX
;
112 Stmt
const* firstStmt
= nullptr;
113 unsigned curLine
= MAX
;
114 unsigned prevLine
= MAX
;
115 SourceLocation prevEnd
;
116 auto& SM
= compiler
.getSourceManager();
117 for (auto i
= compoundStmt
->body_begin(); i
!= compoundStmt
->body_end(); ++i
)
120 auto const actualPrevEnd
= prevEnd
;
121 prevEnd
= compat::getEndLoc(stmt
); // compute early, before below `continue`s
123 // these show up in macro expansions, not interesting
124 if (isa
<NullStmt
>(stmt
))
126 // these are always weirdly indented
127 if (isa
<LabelStmt
>(stmt
))
129 #if CLANG_VERSION < 100000
131 // <https://github.com/llvm/llvm-project/commit/dc3957ec215dd17b8d293461f18696566637a6cd>
132 // "Include leading attributes in DeclStmt's SourceRange", getBeginLoc of a VarDecl DeclStmt
133 // with an UnusedAttr pointed after the attr (and getLocation of the attr pointed at
134 // "maybe_unused", not at the leading "[["), so just ignore those in old compiler versions:
135 if (auto const declStmt
= dyn_cast
<DeclStmt
>(stmt
))
137 if (declStmt
->decl_begin() != declStmt
->decl_end())
139 if (auto const decl
= dyn_cast
<VarDecl
>(*declStmt
->decl_begin()))
141 if (decl
->hasAttr
<UnusedAttr
>())
150 auto stmtLoc
= compat::getBeginLoc(stmt
);
153 bool partOfMacro
= false;
154 if (SM
.isMacroArgExpansion(stmtLoc
) || SM
.isMacroBodyExpansion(stmtLoc
))
157 macroName
= Lexer::getImmediateMacroNameForDiagnostics(
158 stmtLoc
, compiler
.getSourceManager(), compiler
.getLangOpts());
159 // CPPUNIT_TEST_SUITE/CPPUNIT_TEST/CPPUNIT_TEST_SUITE_END work together, so the one is indented inside the other
160 if (macroName
== "CPPUNIT_TEST_SUITE")
162 // similar thing in dbaccess/
163 if (macroName
== "DECL_PROP_IMPL")
165 // similar thing in forms/
166 if (macroName
== "DECL_IFACE_PROP_IMPL" || macroName
== "DECL_BOOL_PROP_IMPL")
168 #if CLANG_VERSION >= 70000
169 stmtLoc
= SM
.getExpansionRange(stmtLoc
).getBegin();
171 stmtLoc
= SM
.getExpansionRange(stmtLoc
).first
;
175 // check for comment to the left of the statement
177 const char* p1
= SM
.getCharacterData(stmtLoc
);
179 bool foundComment
= false;
180 while (*p1
&& *p1
!= '\n')
193 bool invalid1
= false;
194 bool invalid2
= false;
195 unsigned tmpColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
196 unsigned tmpLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
197 if (invalid1
|| invalid2
)
206 else if (curLine
== prevLine
)
208 // ignore multiple statements on same line
210 else if (column
!= tmpColumn
)
212 if (containsPreprocessingConditionalInclusion(SourceRange(
213 locationAfterToken(compiler
.getSourceManager().getExpansionLoc(actualPrevEnd
)),
214 compiler
.getSourceManager().getExpansionLoc(compat::getBeginLoc(stmt
)))))
216 report(DiagnosticsEngine::Warning
, "statement mis-aligned compared to neighbours %0",
219 report(DiagnosticsEngine::Note
, "measured against this one",
220 compat::getBeginLoc(firstStmt
));
221 //getParentStmt(compoundStmt)->dump();
226 if (auto ifStmt
= dyn_cast
<IfStmt
>(stmt
))
228 auto bodyStmt
= ifStmt
->getThen();
229 if (bodyStmt
&& !isa
<CompoundStmt
>(bodyStmt
))
231 stmtLoc
= compat::getBeginLoc(bodyStmt
);
234 unsigned bodyColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
235 unsigned bodyLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
236 if (invalid1
|| invalid2
)
239 if (bodyLine
!= tmpLine
&& bodyColumn
<= tmpColumn
)
240 report(DiagnosticsEngine::Warning
, "if body should be indented", stmtLoc
);
243 auto elseStmt
= ifStmt
->getElse();
244 if (elseStmt
&& !isa
<CompoundStmt
>(elseStmt
) && !isa
<IfStmt
>(elseStmt
))
246 stmtLoc
= compat::getBeginLoc(elseStmt
);
249 unsigned elseColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
250 unsigned elseLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
251 if (invalid1
|| invalid2
)
253 if (elseLine
!= tmpLine
&& elseColumn
<= tmpColumn
)
254 report(DiagnosticsEngine::Warning
, "else body should be indented", stmtLoc
);
256 if (elseStmt
&& !isa
<CompoundStmt
>(bodyStmt
))
258 stmtLoc
= ifStmt
->getElseLoc();
261 unsigned elseColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
262 unsigned elseLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
263 if (invalid1
|| invalid2
)
265 if (elseLine
!= tmpLine
&& elseColumn
!= tmpColumn
)
266 report(DiagnosticsEngine::Warning
, "if and else not aligned", stmtLoc
);
274 bool Indentation::VisitIfStmt(IfStmt
const* ifStmt
)
276 if (ignoreLocation(ifStmt
))
279 // TODO - ignore chained if statements for now
280 if (auto chained
= ifStmt
->getElse())
281 chainedSet
.insert(chained
);
282 if (chainedSet
.find(ifStmt
) != chainedSet
.end())
285 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(ifStmt
->getThen()))
286 checkCompoundStmtBraces(ifStmt
, compoundStmt
);
287 // TODO - needs to be checked against the line that contains the else keyword, but not against the parent
288 // if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(ifStmt->getElse()))
289 // checkCompoundStmtBraces(ifStmt, compoundStmt);
293 bool Indentation::VisitForStmt(ForStmt
const* forStmt
)
295 if (ignoreLocation(forStmt
))
297 if (chainedSet
.find(forStmt
) != chainedSet
.end())
299 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(forStmt
->getBody()))
300 checkCompoundStmtBraces(forStmt
, compoundStmt
);
304 bool Indentation::VisitWhileStmt(WhileStmt
const* whileStmt
)
306 if (ignoreLocation(whileStmt
))
308 if (chainedSet
.find(whileStmt
) != chainedSet
.end())
310 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(whileStmt
->getBody()))
311 checkCompoundStmtBraces(whileStmt
, compoundStmt
);
315 bool Indentation::VisitDoStmt(DoStmt
const* doStmt
)
317 if (ignoreLocation(doStmt
))
319 if (chainedSet
.find(doStmt
) != chainedSet
.end())
321 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(doStmt
->getBody()))
322 checkCompoundStmtBraces(doStmt
, compoundStmt
);
326 bool Indentation::VisitCXXForRangeStmt(CXXForRangeStmt
const* cxxForRangeStmt
)
328 if (ignoreLocation(cxxForRangeStmt
))
330 if (chainedSet
.find(cxxForRangeStmt
) != chainedSet
.end())
332 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(cxxForRangeStmt
->getBody()))
333 checkCompoundStmtBraces(cxxForRangeStmt
, compoundStmt
);
337 void Indentation::checkCompoundStmtBraces(const Stmt
* parentStmt
, const CompoundStmt
* compoundStmt
)
339 auto& SM
= compiler
.getSourceManager();
340 bool invalid1
= false;
341 bool invalid2
= false;
343 auto parentBeginLoc
= compat::getBeginLoc(parentStmt
);
344 unsigned parentColumn
= SM
.getPresumedColumnNumber(parentBeginLoc
, &invalid1
);
348 auto startBraceLoc
= compoundStmt
->getLBracLoc();
349 auto endBraceLoc
= compoundStmt
->getRBracLoc();
350 unsigned beginColumn
= SM
.getPresumedColumnNumber(startBraceLoc
, &invalid1
);
351 unsigned beginLine
= SM
.getPresumedLineNumber(startBraceLoc
, &invalid2
);
352 if (invalid1
|| invalid2
)
354 unsigned endColumn
= SM
.getPresumedColumnNumber(endBraceLoc
, &invalid1
);
355 unsigned endLine
= SM
.getPresumedLineNumber(endBraceLoc
, &invalid2
);
356 if (invalid1
|| invalid2
)
358 if (beginLine
== endLine
)
361 // check for code to the left of the starting brace
362 bool foundCodeToLeft
= false;
364 const char* p1
= SM
.getCharacterData(startBraceLoc
);
366 while (*p1
&& *p1
!= '\n')
370 foundCodeToLeft
= true;
377 // if we found code to the left of the start brace, that means the end-brace needs
378 // to line up with the start of the parent statement
381 if (parentColumn
!= endColumn
)
383 report(DiagnosticsEngine::Warning
, "end brace not aligned with beginning of statement",
385 report(DiagnosticsEngine::Note
, "statement beginning here", parentBeginLoc
);
390 if (parentColumn
!= beginColumn
)
392 report(DiagnosticsEngine::Warning
,
393 "start brace not aligned with beginning of parent statement", startBraceLoc
);
394 report(DiagnosticsEngine::Note
, "statement beginning here", parentBeginLoc
);
396 else if (beginColumn
!= endColumn
)
398 report(DiagnosticsEngine::Warning
, "start and end brace not aligned", endBraceLoc
);
399 report(DiagnosticsEngine::Note
, "start brace here", startBraceLoc
);
402 /** now check that lines inside the compoundstmt are indented */
403 if (!compoundStmt
->size())
405 auto firstStmt
= compoundStmt
->body_front();
406 if (isa
<LabelStmt
>(firstStmt
))
408 auto firstStmtLoc
= compat::getBeginLoc(firstStmt
);
409 unsigned firstStmtBeginColumn
= SM
.getPresumedColumnNumber(firstStmtLoc
, &invalid1
);
412 if (firstStmtBeginColumn
> beginColumn
)
414 StringRef fn
= getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(firstStmtLoc
));
415 // this is doing code generation, so the weird layout is deliberate
416 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sc/source/core/opencl/"))
418 report(DiagnosticsEngine::Warning
, "body inside brace not indented", firstStmtLoc
);
421 bool Indentation::VisitSwitchStmt(SwitchStmt
const* switchStmt
)
423 if (ignoreLocation(switchStmt
))
426 constexpr unsigned MAX
= std::numeric_limits
<unsigned>::max();
427 unsigned column
= MAX
;
428 Stmt
const* firstStmt
= nullptr;
429 unsigned curLine
= MAX
;
430 unsigned prevLine
= MAX
;
431 auto& SM
= compiler
.getSourceManager();
432 auto compoundStmt
= dyn_cast
<CompoundStmt
>(switchStmt
->getBody());
435 for (auto i
= compoundStmt
->body_begin(); i
!= compoundStmt
->body_end(); ++i
)
437 Stmt
const* caseStmt
= dyn_cast
<CaseStmt
>(*i
);
439 caseStmt
= dyn_cast
<DefaultStmt
>(*i
);
443 auto stmtLoc
= compat::getBeginLoc(caseStmt
);
445 bool invalid1
= false;
446 bool invalid2
= false;
447 unsigned tmpColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
448 unsigned tmpLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
449 if (invalid1
|| invalid2
)
456 firstStmt
= caseStmt
;
458 else if (curLine
== prevLine
)
460 // ignore multiple statements on same line
462 else if (column
!= tmpColumn
)
464 // disable this for now, ends up touching some very large switch statements in sw/ and sc/
466 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
468 // report(DiagnosticsEngine::Note, "measured against this one",
469 // compat::getBeginLoc(firstStmt));
470 //getParentStmt(compoundStmt)->dump();
477 loplugin::Plugin::Registration
<Indentation
> indentation("indentation");
481 #endif // LO_CLANG_SHARED_PLUGINS
483 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */