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>
19 #include "config_clang.h"
23 Check for child statements inside a compound statement that do not share the same indentation.
25 TODO if an open-brace starts on a new line by itself, check that it lines up with it's closing-brace
26 TODO else should line up with if
31 class Indentation
: public loplugin::FilteringPlugin
<Indentation
>
34 explicit Indentation(loplugin::InstantiationData
const& data
)
35 : FilteringPlugin(data
)
39 virtual bool preRun() override
41 std::string
fn(handler
.getMainFileName());
42 loplugin::normalizeDotDotInFilePath(fn
);
43 // include another file to build a table
44 if (fn
== SRCDIR
"/sc/source/core/tool/cellkeytranslator.cxx")
47 if (fn
== SRCDIR
"/sc/source/core/tool/compiler.cxx")
49 // looks like lex/yacc output
50 if (fn
== SRCDIR
"/hwpfilter/source/grammar.cxx")
53 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/vcl/qt5/")
54 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/vcl/qt6/")
55 || loplugin::isSamePathname(fn
, SRCDIR
"/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx"))
60 virtual void run() override
63 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
66 bool VisitCompoundStmt(CompoundStmt
const*);
67 bool PreTraverseSwitchStmt(SwitchStmt
*);
68 bool PostTraverseSwitchStmt(SwitchStmt
*, bool);
69 bool TraverseSwitchStmt(SwitchStmt
*);
70 bool VisitSwitchStmt(SwitchStmt
const*);
71 bool VisitIfStmt(IfStmt
const*);
72 bool VisitForStmt(ForStmt
const*);
73 bool VisitWhileStmt(WhileStmt
const*);
74 bool VisitDoStmt(DoStmt
const*);
75 bool VisitCXXForRangeStmt(CXXForRangeStmt
const*);
78 void checkCompoundStmtBraces(const Stmt
* parentStmt
, const CompoundStmt
*);
80 std::vector
<const Stmt
*> switchStmtBodies
;
81 std::unordered_set
<const Stmt
*> chainedSet
;
84 bool Indentation::PreTraverseSwitchStmt(SwitchStmt
* switchStmt
)
86 switchStmtBodies
.push_back(switchStmt
->getBody());
90 bool Indentation::PostTraverseSwitchStmt(SwitchStmt
*, bool)
92 switchStmtBodies
.pop_back();
96 bool Indentation::TraverseSwitchStmt(SwitchStmt
* switchStmt
)
98 PreTraverseSwitchStmt(switchStmt
);
99 auto ret
= FilteringPlugin::TraverseSwitchStmt(switchStmt
);
100 PostTraverseSwitchStmt(switchStmt
, ret
);
104 bool Indentation::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
106 if (ignoreLocation(compoundStmt
))
108 // these are kind of free form
109 if (!switchStmtBodies
.empty() && switchStmtBodies
.back() == compoundStmt
)
112 constexpr unsigned MAX
= std::numeric_limits
<unsigned>::max();
113 unsigned column
= MAX
;
114 Stmt
const* firstStmt
= nullptr;
115 unsigned curLine
= MAX
;
116 unsigned prevLine
= MAX
;
117 SourceLocation prevEnd
;
118 auto& SM
= compiler
.getSourceManager();
119 for (auto i
= compoundStmt
->body_begin(); i
!= compoundStmt
->body_end(); ++i
)
122 auto const actualPrevEnd
= prevEnd
;
123 prevEnd
= stmt
->getEndLoc(); // compute early, before below `continue`s
125 // these show up in macro expansions, not interesting
126 if (isa
<NullStmt
>(stmt
))
128 // these are always weirdly indented
129 if (isa
<LabelStmt
>(stmt
))
132 auto stmtLoc
= stmt
->getBeginLoc();
135 bool partOfMacro
= false;
136 if (SM
.isMacroArgExpansion(stmtLoc
) || SM
.isMacroBodyExpansion(stmtLoc
))
139 macroName
= Lexer::getImmediateMacroNameForDiagnostics(
140 stmtLoc
, compiler
.getSourceManager(), compiler
.getLangOpts());
141 // CPPUNIT_TEST_SUITE/CPPUNIT_TEST/CPPUNIT_TEST_SUITE_END work together, so the one is indented inside the other
142 if (macroName
== "CPPUNIT_TEST_SUITE")
144 // similar thing in dbaccess/
145 if (macroName
== "DECL_PROP_IMPL")
147 // similar thing in forms/
148 if (macroName
== "DECL_IFACE_PROP_IMPL" || macroName
== "DECL_BOOL_PROP_IMPL")
150 stmtLoc
= SM
.getExpansionRange(stmtLoc
).getBegin();
153 // check for comment to the left of the statement
155 const char* p1
= SM
.getCharacterData(stmtLoc
);
157 bool foundComment
= false;
158 while (*p1
&& *p1
!= '\n')
171 bool invalid1
= false;
172 bool invalid2
= false;
173 unsigned tmpColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
174 unsigned tmpLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
175 if (invalid1
|| invalid2
)
184 else if (curLine
== prevLine
)
186 // ignore multiple statements on same line
188 else if (column
!= tmpColumn
)
190 if (containsPreprocessingConditionalInclusion(SourceRange(
191 locationAfterToken(compiler
.getSourceManager().getExpansionLoc(actualPrevEnd
)),
192 compiler
.getSourceManager().getExpansionLoc(stmt
->getBeginLoc()))))
194 report(DiagnosticsEngine::Warning
, "statement mis-aligned compared to neighbours %0",
197 report(DiagnosticsEngine::Note
, "measured against this one", firstStmt
->getBeginLoc());
198 //getParentStmt(compoundStmt)->dump();
203 if (auto ifStmt
= dyn_cast
<IfStmt
>(stmt
))
205 auto bodyStmt
= ifStmt
->getThen();
206 if (bodyStmt
&& !isa
<CompoundStmt
>(bodyStmt
))
208 stmtLoc
= bodyStmt
->getBeginLoc();
211 unsigned bodyColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
212 unsigned bodyLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
213 if (invalid1
|| invalid2
)
216 if (bodyLine
!= tmpLine
&& bodyColumn
<= tmpColumn
)
217 report(DiagnosticsEngine::Warning
, "if body should be indented", stmtLoc
);
220 auto elseStmt
= ifStmt
->getElse();
221 if (elseStmt
&& !isa
<CompoundStmt
>(elseStmt
) && !isa
<IfStmt
>(elseStmt
))
223 stmtLoc
= elseStmt
->getBeginLoc();
226 unsigned elseColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
227 unsigned elseLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
228 if (invalid1
|| invalid2
)
230 if (elseLine
!= tmpLine
&& elseColumn
<= tmpColumn
)
231 report(DiagnosticsEngine::Warning
, "else body should be indented", stmtLoc
);
233 if (elseStmt
&& !isa
<CompoundStmt
>(bodyStmt
))
235 stmtLoc
= ifStmt
->getElseLoc();
238 unsigned elseColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
239 unsigned elseLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
240 if (invalid1
|| invalid2
)
242 if (elseLine
!= tmpLine
&& elseColumn
!= tmpColumn
)
243 report(DiagnosticsEngine::Warning
, "if and else not aligned", stmtLoc
);
251 bool Indentation::VisitIfStmt(IfStmt
const* ifStmt
)
253 if (ignoreLocation(ifStmt
))
256 // TODO - ignore chained if statements for now
257 if (auto chained
= ifStmt
->getElse())
258 chainedSet
.insert(chained
);
259 if (chainedSet
.find(ifStmt
) != chainedSet
.end())
262 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(ifStmt
->getThen()))
263 checkCompoundStmtBraces(ifStmt
, compoundStmt
);
264 // TODO - needs to be checked against the line that contains the else keyword, but not against the parent
265 // if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(ifStmt->getElse()))
266 // checkCompoundStmtBraces(ifStmt, compoundStmt);
270 bool Indentation::VisitForStmt(ForStmt
const* forStmt
)
272 if (ignoreLocation(forStmt
))
274 if (chainedSet
.find(forStmt
) != chainedSet
.end())
276 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(forStmt
->getBody()))
277 checkCompoundStmtBraces(forStmt
, compoundStmt
);
281 bool Indentation::VisitWhileStmt(WhileStmt
const* whileStmt
)
283 if (ignoreLocation(whileStmt
))
285 if (chainedSet
.find(whileStmt
) != chainedSet
.end())
287 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(whileStmt
->getBody()))
288 checkCompoundStmtBraces(whileStmt
, compoundStmt
);
292 bool Indentation::VisitDoStmt(DoStmt
const* doStmt
)
294 if (ignoreLocation(doStmt
))
296 if (chainedSet
.find(doStmt
) != chainedSet
.end())
298 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(doStmt
->getBody()))
299 checkCompoundStmtBraces(doStmt
, compoundStmt
);
303 bool Indentation::VisitCXXForRangeStmt(CXXForRangeStmt
const* cxxForRangeStmt
)
305 if (ignoreLocation(cxxForRangeStmt
))
307 if (chainedSet
.find(cxxForRangeStmt
) != chainedSet
.end())
309 if (auto compoundStmt
= dyn_cast_or_null
<CompoundStmt
>(cxxForRangeStmt
->getBody()))
310 checkCompoundStmtBraces(cxxForRangeStmt
, compoundStmt
);
314 void Indentation::checkCompoundStmtBraces(const Stmt
* parentStmt
, const CompoundStmt
* compoundStmt
)
316 auto& SM
= compiler
.getSourceManager();
317 bool invalid1
= false;
318 bool invalid2
= false;
320 auto parentBeginLoc
= parentStmt
->getBeginLoc();
321 unsigned parentColumn
= SM
.getPresumedColumnNumber(parentBeginLoc
, &invalid1
);
325 auto startBraceLoc
= compoundStmt
->getLBracLoc();
326 auto endBraceLoc
= compoundStmt
->getRBracLoc();
327 unsigned beginColumn
= SM
.getPresumedColumnNumber(startBraceLoc
, &invalid1
);
328 unsigned beginLine
= SM
.getPresumedLineNumber(startBraceLoc
, &invalid2
);
329 if (invalid1
|| invalid2
)
331 unsigned endColumn
= SM
.getPresumedColumnNumber(endBraceLoc
, &invalid1
);
332 unsigned endLine
= SM
.getPresumedLineNumber(endBraceLoc
, &invalid2
);
333 if (invalid1
|| invalid2
)
335 if (beginLine
== endLine
)
338 // check for code to the left of the starting brace
339 bool foundCodeToLeft
= false;
341 const char* p1
= SM
.getCharacterData(startBraceLoc
);
343 while (*p1
&& *p1
!= '\n')
347 foundCodeToLeft
= true;
354 // if we found code to the left of the start brace, that means the end-brace needs
355 // to line up with the start of the parent statement
358 if (parentColumn
!= endColumn
)
360 report(DiagnosticsEngine::Warning
, "end brace not aligned with beginning of statement",
362 report(DiagnosticsEngine::Note
, "statement beginning here", parentBeginLoc
);
367 if (parentColumn
!= beginColumn
)
369 report(DiagnosticsEngine::Warning
,
370 "start brace not aligned with beginning of parent statement", startBraceLoc
);
371 report(DiagnosticsEngine::Note
, "statement beginning here", parentBeginLoc
);
373 else if (beginColumn
!= endColumn
)
375 report(DiagnosticsEngine::Warning
, "start and end brace not aligned", endBraceLoc
);
376 report(DiagnosticsEngine::Note
, "start brace here", startBraceLoc
);
379 /** now check that lines inside the compoundstmt are indented */
380 if (!compoundStmt
->size())
382 auto firstStmt
= compoundStmt
->body_front();
383 if (isa
<LabelStmt
>(firstStmt
))
385 auto firstStmtLoc
= firstStmt
->getBeginLoc();
386 unsigned firstStmtBeginColumn
= SM
.getPresumedColumnNumber(firstStmtLoc
, &invalid1
);
389 if (firstStmtBeginColumn
> beginColumn
)
391 StringRef fn
= getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(firstStmtLoc
));
392 // this is doing code generation, so the weird layout is deliberate
393 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sc/source/core/opencl/"))
395 report(DiagnosticsEngine::Warning
, "body inside brace not indented", firstStmtLoc
);
398 bool Indentation::VisitSwitchStmt(SwitchStmt
const* switchStmt
)
400 if (ignoreLocation(switchStmt
))
403 constexpr unsigned MAX
= std::numeric_limits
<unsigned>::max();
404 unsigned column
= MAX
;
405 Stmt
const* firstStmt
= nullptr;
406 unsigned curLine
= MAX
;
407 unsigned prevLine
= MAX
;
408 auto& SM
= compiler
.getSourceManager();
409 auto compoundStmt
= dyn_cast
<CompoundStmt
>(switchStmt
->getBody());
412 for (auto i
= compoundStmt
->body_begin(); i
!= compoundStmt
->body_end(); ++i
)
414 Stmt
const* caseStmt
= dyn_cast
<CaseStmt
>(*i
);
416 caseStmt
= dyn_cast
<DefaultStmt
>(*i
);
420 auto stmtLoc
= caseStmt
->getBeginLoc();
422 bool invalid1
= false;
423 bool invalid2
= false;
424 unsigned tmpColumn
= SM
.getPresumedColumnNumber(stmtLoc
, &invalid1
);
425 unsigned tmpLine
= SM
.getPresumedLineNumber(stmtLoc
, &invalid2
);
426 if (invalid1
|| invalid2
)
433 firstStmt
= caseStmt
;
435 else if (curLine
== prevLine
)
437 // ignore multiple statements on same line
439 else if (column
!= tmpColumn
)
441 // disable this for now, ends up touching some very large switch statements in sw/ and sc/
443 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
445 // report(DiagnosticsEngine::Note, "measured against this one",
446 // firstStmt->getBeginLoc());
447 //getParentStmt(compoundStmt)->dump();
454 loplugin::Plugin::Registration
<Indentation
> indentation("indentation");
458 #endif // LO_CLANG_SHARED_PLUGINS
460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */