bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / indentation.cxx
blob9621ef549bbec05be9e090254bad3583cedf97fc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * Based on LLVM/Clang.
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
13 #include <cassert>
14 #include <string>
15 #include <iostream>
16 #include <fstream>
17 #include <set>
18 #include "plugin.hxx"
21 Check for child statements inside a compound statement that do not share the same indentation.
23 TODO if an open-brace starts on a new line by itself, check that it lines up with it's closing-brace
24 TODO else should line up with if
27 namespace
29 class Indentation : public loplugin::FilteringPlugin<Indentation>
31 public:
32 explicit Indentation(loplugin::InstantiationData const& data)
33 : FilteringPlugin(data)
37 virtual bool preRun() override
39 std::string fn(handler.getMainFileName());
40 loplugin::normalizeDotDotInFilePath(fn);
41 // include another file to build a table
42 if (fn == SRCDIR "/sc/source/core/tool/cellkeytranslator.cxx")
43 return false;
44 // weird structure
45 if (fn == SRCDIR "/sc/source/core/tool/compiler.cxx")
46 return false;
47 // looks like lex/yacc output
48 if (fn == SRCDIR "/hwpfilter/source/grammar.cxx")
49 return false;
50 // TODO need to learn to handle attributes like "[[maybe_unused]]"
51 if (fn == SRCDIR "/binaryurp/source/bridge.cxx")
52 return false;
53 // the QEMIT macros
54 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/qt5/")
55 || loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx"))
56 return false;
57 return true;
60 virtual void run() override
62 if (preRun())
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*);
72 private:
73 std::vector<const Stmt*> switchStmtBodies;
76 bool Indentation::PreTraverseSwitchStmt(SwitchStmt* switchStmt)
78 switchStmtBodies.push_back(switchStmt->getBody());
79 return true;
82 bool Indentation::PostTraverseSwitchStmt(SwitchStmt*, bool)
84 switchStmtBodies.pop_back();
85 return true;
88 bool Indentation::TraverseSwitchStmt(SwitchStmt* switchStmt)
90 PreTraverseSwitchStmt(switchStmt);
91 auto ret = FilteringPlugin::TraverseSwitchStmt(switchStmt);
92 PostTraverseSwitchStmt(switchStmt, ret);
93 return ret;
96 bool Indentation::VisitCompoundStmt(CompoundStmt const* compoundStmt)
98 if (ignoreLocation(compoundStmt))
99 return true;
100 // these are kind of free form
101 if (!switchStmtBodies.empty() && switchStmtBodies.back() == compoundStmt)
102 return true;
104 constexpr unsigned MAX = std::numeric_limits<unsigned>::max();
105 unsigned column = MAX;
106 Stmt const* firstStmt = nullptr;
107 unsigned curLine = MAX;
108 unsigned prevLine = MAX;
109 SourceLocation prevEnd;
110 auto& SM = compiler.getSourceManager();
111 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
113 auto stmt = *i;
114 auto const actualPrevEnd = prevEnd;
115 prevEnd = compat::getEndLoc(stmt); // compute early, before below `continue`s
117 // these show up in macro expansions, not interesting
118 if (isa<NullStmt>(stmt))
119 continue;
120 // these are always weirdly indented
121 if (isa<LabelStmt>(stmt))
122 continue;
123 #if CLANG_VERSION < 100000
124 // Before
125 // <https://github.com/llvm/llvm-project/commit/dc3957ec215dd17b8d293461f18696566637a6cd>
126 // "Include leading attributes in DeclStmt's SourceRange", getBeginLoc of a VarDecl DeclStmt
127 // with an UnusedAttr pointed after the attr (and getLocation of the attr pointed at
128 // "maybe_unused", not at the leading "[["), so just ignore those in old compiler versions:
129 if (auto const declStmt = dyn_cast<DeclStmt>(stmt))
131 if (declStmt->decl_begin() != declStmt->decl_end())
133 if (auto const decl = dyn_cast<VarDecl>(*declStmt->decl_begin()))
135 if (decl->hasAttr<UnusedAttr>())
137 continue;
142 #endif
144 auto stmtLoc = compat::getBeginLoc(stmt);
146 StringRef macroName;
147 bool partOfMacro = false;
148 if (SM.isMacroArgExpansion(stmtLoc) || SM.isMacroBodyExpansion(stmtLoc))
150 partOfMacro = true;
151 macroName = Lexer::getImmediateMacroNameForDiagnostics(
152 stmtLoc, compiler.getSourceManager(), compiler.getLangOpts());
153 // CPPUNIT_TEST_SUITE/CPPUNIT_TEST/CPPUNIT_TEST_SUITE_END work together, so the one is indented inside the other
154 if (macroName == "CPPUNIT_TEST_SUITE")
155 continue;
156 // similar thing in dbaccess/
157 if (macroName == "DECL_PROP_IMPL")
158 continue;
159 // similar thing in forms/
160 if (macroName == "DECL_IFACE_PROP_IMPL" || macroName == "DECL_BOOL_PROP_IMPL")
161 continue;
162 #if CLANG_VERSION >= 70000
163 stmtLoc = SM.getExpansionRange(stmtLoc).getBegin();
164 #else
165 stmtLoc = SM.getExpansionRange(stmtLoc).first;
166 #endif
169 // check for comment to the left of the statement
171 const char* p1 = SM.getCharacterData(stmtLoc);
172 --p1;
173 bool foundComment = false;
174 while (*p1 && *p1 != '\n')
176 if (*p1 == '/')
178 foundComment = true;
179 break;
181 --p1;
183 if (foundComment)
184 continue;
187 bool invalid1 = false;
188 bool invalid2 = false;
189 unsigned tmpColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
190 unsigned tmpLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
191 if (invalid1 || invalid2)
192 continue;
193 prevLine = curLine;
194 curLine = tmpLine;
195 if (column == MAX)
197 column = tmpColumn;
198 firstStmt = stmt;
200 else if (curLine == prevLine)
202 // ignore multiple statements on same line
204 else if (column != tmpColumn)
206 if (containsPreprocessingConditionalInclusion(SourceRange(
207 locationAfterToken(compiler.getSourceManager().getExpansionLoc(actualPrevEnd)),
208 compiler.getSourceManager().getExpansionLoc(compat::getBeginLoc(stmt)))))
209 continue;
210 report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours %0",
211 stmtLoc)
212 << macroName;
213 report(DiagnosticsEngine::Note, "measured against this one",
214 compat::getBeginLoc(firstStmt));
215 //getParentStmt(compoundStmt)->dump();
216 //stmt->dump();
219 if (!partOfMacro)
220 if (auto ifStmt = dyn_cast<IfStmt>(stmt))
222 auto bodyStmt = ifStmt->getThen();
223 if (bodyStmt && !isa<CompoundStmt>(bodyStmt))
225 stmtLoc = compat::getBeginLoc(bodyStmt);
226 invalid1 = false;
227 invalid2 = false;
228 unsigned bodyColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
229 unsigned bodyLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
230 if (invalid1 || invalid2)
231 return true;
233 if (bodyLine != tmpLine && bodyColumn <= tmpColumn)
234 report(DiagnosticsEngine::Warning, "if body should be indented", stmtLoc);
237 auto elseStmt = ifStmt->getElse();
238 if (elseStmt && !isa<CompoundStmt>(elseStmt) && !isa<IfStmt>(elseStmt))
240 stmtLoc = compat::getBeginLoc(elseStmt);
241 invalid1 = false;
242 invalid2 = false;
243 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
244 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
245 if (invalid1 || invalid2)
246 return true;
247 if (elseLine != tmpLine && elseColumn <= tmpColumn)
248 report(DiagnosticsEngine::Warning, "else body should be indented", stmtLoc);
250 if (elseStmt && !isa<CompoundStmt>(bodyStmt))
252 stmtLoc = ifStmt->getElseLoc();
253 invalid1 = false;
254 invalid2 = false;
255 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
256 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
257 if (invalid1 || invalid2)
258 return true;
259 if (elseLine != tmpLine && elseColumn != tmpColumn)
260 report(DiagnosticsEngine::Warning, "if and else not aligned", stmtLoc);
264 return true;
267 bool Indentation::VisitSwitchStmt(SwitchStmt const* switchStmt)
269 if (ignoreLocation(switchStmt))
270 return true;
272 constexpr unsigned MAX = std::numeric_limits<unsigned>::max();
273 unsigned column = MAX;
274 Stmt const* firstStmt = nullptr;
275 unsigned curLine = MAX;
276 unsigned prevLine = MAX;
277 auto& SM = compiler.getSourceManager();
278 auto compoundStmt = dyn_cast<CompoundStmt>(switchStmt->getBody());
279 if (!compoundStmt)
280 return true;
281 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
283 Stmt const* caseStmt = dyn_cast<CaseStmt>(*i);
284 if (!caseStmt)
285 caseStmt = dyn_cast<DefaultStmt>(*i);
286 if (!caseStmt)
287 continue;
289 auto stmtLoc = compat::getBeginLoc(caseStmt);
291 bool invalid1 = false;
292 bool invalid2 = false;
293 unsigned tmpColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
294 unsigned tmpLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
295 if (invalid1 || invalid2)
296 continue;
297 prevLine = curLine;
298 curLine = tmpLine;
299 if (column == MAX)
301 column = tmpColumn;
302 firstStmt = caseStmt;
304 else if (curLine == prevLine)
306 // ignore multiple statements on same line
308 else if (column != tmpColumn)
310 // disable this for now, ends up touching some very large switch statements in sw/ and sc/
311 (void)firstStmt;
312 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
313 // stmtLoc);
314 // report(DiagnosticsEngine::Note, "measured against this one",
315 // compat::getBeginLoc(firstStmt));
316 //getParentStmt(compoundStmt)->dump();
317 //stmt->dump();
320 return true;
323 loplugin::Plugin::Registration<Indentation> indentation("indentation");
325 } // namespace
327 #endif // LO_CLANG_SHARED_PLUGINS
329 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */