tdf#130857 qt weld: Support "Insert Breaks" dialog
[LibreOffice.git] / compilerplugins / clang / indentation.cxx
blobcf2512d1d25dde77ddfea55566a4339a7315fe47
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 <unordered_set>
19 #include "config_clang.h"
20 #include "plugin.hxx"
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
29 namespace
31 class Indentation : public loplugin::FilteringPlugin<Indentation>
33 public:
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")
45 return false;
46 // weird structure
47 if (fn == SRCDIR "/sc/source/core/tool/compiler.cxx")
48 return false;
49 // looks like lex/yacc output
50 if (fn == SRCDIR "/hwpfilter/source/grammar.cxx")
51 return false;
52 // the QEMIT macros
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"))
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*);
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*);
77 private:
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());
87 return true;
90 bool Indentation::PostTraverseSwitchStmt(SwitchStmt*, bool)
92 switchStmtBodies.pop_back();
93 return true;
96 bool Indentation::TraverseSwitchStmt(SwitchStmt* switchStmt)
98 PreTraverseSwitchStmt(switchStmt);
99 auto ret = FilteringPlugin::TraverseSwitchStmt(switchStmt);
100 PostTraverseSwitchStmt(switchStmt, ret);
101 return ret;
104 bool Indentation::VisitCompoundStmt(CompoundStmt const* compoundStmt)
106 if (ignoreLocation(compoundStmt))
107 return true;
108 // these are kind of free form
109 if (!switchStmtBodies.empty() && switchStmtBodies.back() == compoundStmt)
110 return true;
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)
121 auto stmt = *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))
127 continue;
128 // these are always weirdly indented
129 if (isa<LabelStmt>(stmt))
130 continue;
132 auto stmtLoc = stmt->getBeginLoc();
134 StringRef macroName;
135 bool partOfMacro = false;
136 if (SM.isMacroArgExpansion(stmtLoc) || SM.isMacroBodyExpansion(stmtLoc))
138 partOfMacro = true;
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")
143 continue;
144 // similar thing in dbaccess/
145 if (macroName == "DECL_PROP_IMPL")
146 continue;
147 // similar thing in forms/
148 if (macroName == "DECL_IFACE_PROP_IMPL" || macroName == "DECL_BOOL_PROP_IMPL")
149 continue;
150 stmtLoc = SM.getExpansionRange(stmtLoc).getBegin();
153 // check for comment to the left of the statement
155 const char* p1 = SM.getCharacterData(stmtLoc);
156 --p1;
157 bool foundComment = false;
158 while (*p1 && *p1 != '\n')
160 if (*p1 == '/')
162 foundComment = true;
163 break;
165 --p1;
167 if (foundComment)
168 continue;
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)
176 continue;
177 prevLine = curLine;
178 curLine = tmpLine;
179 if (column == MAX)
181 column = tmpColumn;
182 firstStmt = stmt;
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()))))
193 continue;
194 report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours %0",
195 stmtLoc)
196 << macroName;
197 report(DiagnosticsEngine::Note, "measured against this one", firstStmt->getBeginLoc());
198 //getParentStmt(compoundStmt)->dump();
199 //stmt->dump();
202 if (!partOfMacro)
203 if (auto ifStmt = dyn_cast<IfStmt>(stmt))
205 auto bodyStmt = ifStmt->getThen();
206 if (bodyStmt && !isa<CompoundStmt>(bodyStmt))
208 stmtLoc = bodyStmt->getBeginLoc();
209 invalid1 = false;
210 invalid2 = false;
211 unsigned bodyColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
212 unsigned bodyLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
213 if (invalid1 || invalid2)
214 return true;
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();
224 invalid1 = false;
225 invalid2 = false;
226 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
227 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
228 if (invalid1 || invalid2)
229 return true;
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();
236 invalid1 = false;
237 invalid2 = false;
238 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
239 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
240 if (invalid1 || invalid2)
241 return true;
242 if (elseLine != tmpLine && elseColumn != tmpColumn)
243 report(DiagnosticsEngine::Warning, "if and else not aligned", stmtLoc);
248 return true;
251 bool Indentation::VisitIfStmt(IfStmt const* ifStmt)
253 if (ignoreLocation(ifStmt))
254 return true;
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())
260 return true;
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);
267 return true;
270 bool Indentation::VisitForStmt(ForStmt const* forStmt)
272 if (ignoreLocation(forStmt))
273 return true;
274 if (chainedSet.find(forStmt) != chainedSet.end())
275 return true;
276 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(forStmt->getBody()))
277 checkCompoundStmtBraces(forStmt, compoundStmt);
278 return true;
281 bool Indentation::VisitWhileStmt(WhileStmt const* whileStmt)
283 if (ignoreLocation(whileStmt))
284 return true;
285 if (chainedSet.find(whileStmt) != chainedSet.end())
286 return true;
287 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(whileStmt->getBody()))
288 checkCompoundStmtBraces(whileStmt, compoundStmt);
289 return true;
292 bool Indentation::VisitDoStmt(DoStmt const* doStmt)
294 if (ignoreLocation(doStmt))
295 return true;
296 if (chainedSet.find(doStmt) != chainedSet.end())
297 return true;
298 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(doStmt->getBody()))
299 checkCompoundStmtBraces(doStmt, compoundStmt);
300 return true;
303 bool Indentation::VisitCXXForRangeStmt(CXXForRangeStmt const* cxxForRangeStmt)
305 if (ignoreLocation(cxxForRangeStmt))
306 return true;
307 if (chainedSet.find(cxxForRangeStmt) != chainedSet.end())
308 return true;
309 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(cxxForRangeStmt->getBody()))
310 checkCompoundStmtBraces(cxxForRangeStmt, compoundStmt);
311 return true;
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);
322 if (invalid1)
323 return;
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)
330 return;
331 unsigned endColumn = SM.getPresumedColumnNumber(endBraceLoc, &invalid1);
332 unsigned endLine = SM.getPresumedLineNumber(endBraceLoc, &invalid2);
333 if (invalid1 || invalid2)
334 return;
335 if (beginLine == endLine)
336 return;
338 // check for code to the left of the starting brace
339 bool foundCodeToLeft = false;
341 const char* p1 = SM.getCharacterData(startBraceLoc);
342 --p1;
343 while (*p1 && *p1 != '\n')
345 if (*p1 != ' ')
347 foundCodeToLeft = true;
348 break;
350 --p1;
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
356 if (foundCodeToLeft)
358 if (parentColumn != endColumn)
360 report(DiagnosticsEngine::Warning, "end brace not aligned with beginning of statement",
361 endBraceLoc);
362 report(DiagnosticsEngine::Note, "statement beginning here", parentBeginLoc);
364 return;
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())
381 return;
382 auto firstStmt = compoundStmt->body_front();
383 if (isa<LabelStmt>(firstStmt))
384 return;
385 auto firstStmtLoc = firstStmt->getBeginLoc();
386 unsigned firstStmtBeginColumn = SM.getPresumedColumnNumber(firstStmtLoc, &invalid1);
387 if (invalid1)
388 return;
389 if (firstStmtBeginColumn > beginColumn)
390 return;
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/"))
394 return;
395 report(DiagnosticsEngine::Warning, "body inside brace not indented", firstStmtLoc);
398 bool Indentation::VisitSwitchStmt(SwitchStmt const* switchStmt)
400 if (ignoreLocation(switchStmt))
401 return true;
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());
410 if (!compoundStmt)
411 return true;
412 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
414 Stmt const* caseStmt = dyn_cast<CaseStmt>(*i);
415 if (!caseStmt)
416 caseStmt = dyn_cast<DefaultStmt>(*i);
417 if (!caseStmt)
418 continue;
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)
427 continue;
428 prevLine = curLine;
429 curLine = tmpLine;
430 if (column == MAX)
432 column = tmpColumn;
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/
442 (void)firstStmt;
443 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
444 // stmtLoc);
445 // report(DiagnosticsEngine::Note, "measured against this one",
446 // firstStmt->getBeginLoc());
447 //getParentStmt(compoundStmt)->dump();
448 //stmt->dump();
451 return true;
454 loplugin::Plugin::Registration<Indentation> indentation("indentation");
456 } // namespace
458 #endif // LO_CLANG_SHARED_PLUGINS
460 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */