LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / indentation.cxx
blob2dda32f8174cb8f8905d4f24a291726d6ac5dbd1
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 "plugin.hxx"
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
28 namespace
30 class Indentation : public loplugin::FilteringPlugin<Indentation>
32 public:
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")
44 return false;
45 // weird structure
46 if (fn == SRCDIR "/sc/source/core/tool/compiler.cxx")
47 return false;
48 // looks like lex/yacc output
49 if (fn == SRCDIR "/hwpfilter/source/grammar.cxx")
50 return false;
51 // the QEMIT macros
52 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/qt5/")
53 || loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx"))
54 return false;
55 return true;
58 virtual void run() override
60 if (preRun())
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*);
75 private:
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());
85 return true;
88 bool Indentation::PostTraverseSwitchStmt(SwitchStmt*, bool)
90 switchStmtBodies.pop_back();
91 return true;
94 bool Indentation::TraverseSwitchStmt(SwitchStmt* switchStmt)
96 PreTraverseSwitchStmt(switchStmt);
97 auto ret = FilteringPlugin::TraverseSwitchStmt(switchStmt);
98 PostTraverseSwitchStmt(switchStmt, ret);
99 return ret;
102 bool Indentation::VisitCompoundStmt(CompoundStmt const* compoundStmt)
104 if (ignoreLocation(compoundStmt))
105 return true;
106 // these are kind of free form
107 if (!switchStmtBodies.empty() && switchStmtBodies.back() == compoundStmt)
108 return true;
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)
119 auto stmt = *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))
125 continue;
126 // these are always weirdly indented
127 if (isa<LabelStmt>(stmt))
128 continue;
129 #if CLANG_VERSION < 100000
130 // Before
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>())
143 continue;
148 #endif
150 auto stmtLoc = compat::getBeginLoc(stmt);
152 StringRef macroName;
153 bool partOfMacro = false;
154 if (SM.isMacroArgExpansion(stmtLoc) || SM.isMacroBodyExpansion(stmtLoc))
156 partOfMacro = true;
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")
161 continue;
162 // similar thing in dbaccess/
163 if (macroName == "DECL_PROP_IMPL")
164 continue;
165 // similar thing in forms/
166 if (macroName == "DECL_IFACE_PROP_IMPL" || macroName == "DECL_BOOL_PROP_IMPL")
167 continue;
168 #if CLANG_VERSION >= 70000
169 stmtLoc = SM.getExpansionRange(stmtLoc).getBegin();
170 #else
171 stmtLoc = SM.getExpansionRange(stmtLoc).first;
172 #endif
175 // check for comment to the left of the statement
177 const char* p1 = SM.getCharacterData(stmtLoc);
178 --p1;
179 bool foundComment = false;
180 while (*p1 && *p1 != '\n')
182 if (*p1 == '/')
184 foundComment = true;
185 break;
187 --p1;
189 if (foundComment)
190 continue;
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)
198 continue;
199 prevLine = curLine;
200 curLine = tmpLine;
201 if (column == MAX)
203 column = tmpColumn;
204 firstStmt = stmt;
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)))))
215 continue;
216 report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours %0",
217 stmtLoc)
218 << macroName;
219 report(DiagnosticsEngine::Note, "measured against this one",
220 compat::getBeginLoc(firstStmt));
221 //getParentStmt(compoundStmt)->dump();
222 //stmt->dump();
225 if (!partOfMacro)
226 if (auto ifStmt = dyn_cast<IfStmt>(stmt))
228 auto bodyStmt = ifStmt->getThen();
229 if (bodyStmt && !isa<CompoundStmt>(bodyStmt))
231 stmtLoc = compat::getBeginLoc(bodyStmt);
232 invalid1 = false;
233 invalid2 = false;
234 unsigned bodyColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
235 unsigned bodyLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
236 if (invalid1 || invalid2)
237 return true;
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);
247 invalid1 = false;
248 invalid2 = false;
249 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
250 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
251 if (invalid1 || invalid2)
252 return true;
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();
259 invalid1 = false;
260 invalid2 = false;
261 unsigned elseColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
262 unsigned elseLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
263 if (invalid1 || invalid2)
264 return true;
265 if (elseLine != tmpLine && elseColumn != tmpColumn)
266 report(DiagnosticsEngine::Warning, "if and else not aligned", stmtLoc);
271 return true;
274 bool Indentation::VisitIfStmt(IfStmt const* ifStmt)
276 if (ignoreLocation(ifStmt))
277 return true;
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())
283 return true;
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);
290 return true;
293 bool Indentation::VisitForStmt(ForStmt const* forStmt)
295 if (ignoreLocation(forStmt))
296 return true;
297 if (chainedSet.find(forStmt) != chainedSet.end())
298 return true;
299 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(forStmt->getBody()))
300 checkCompoundStmtBraces(forStmt, compoundStmt);
301 return true;
304 bool Indentation::VisitWhileStmt(WhileStmt const* whileStmt)
306 if (ignoreLocation(whileStmt))
307 return true;
308 if (chainedSet.find(whileStmt) != chainedSet.end())
309 return true;
310 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(whileStmt->getBody()))
311 checkCompoundStmtBraces(whileStmt, compoundStmt);
312 return true;
315 bool Indentation::VisitDoStmt(DoStmt const* doStmt)
317 if (ignoreLocation(doStmt))
318 return true;
319 if (chainedSet.find(doStmt) != chainedSet.end())
320 return true;
321 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(doStmt->getBody()))
322 checkCompoundStmtBraces(doStmt, compoundStmt);
323 return true;
326 bool Indentation::VisitCXXForRangeStmt(CXXForRangeStmt const* cxxForRangeStmt)
328 if (ignoreLocation(cxxForRangeStmt))
329 return true;
330 if (chainedSet.find(cxxForRangeStmt) != chainedSet.end())
331 return true;
332 if (auto compoundStmt = dyn_cast_or_null<CompoundStmt>(cxxForRangeStmt->getBody()))
333 checkCompoundStmtBraces(cxxForRangeStmt, compoundStmt);
334 return true;
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);
345 if (invalid1)
346 return;
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)
353 return;
354 unsigned endColumn = SM.getPresumedColumnNumber(endBraceLoc, &invalid1);
355 unsigned endLine = SM.getPresumedLineNumber(endBraceLoc, &invalid2);
356 if (invalid1 || invalid2)
357 return;
358 if (beginLine == endLine)
359 return;
361 // check for code to the left of the starting brace
362 bool foundCodeToLeft = false;
364 const char* p1 = SM.getCharacterData(startBraceLoc);
365 --p1;
366 while (*p1 && *p1 != '\n')
368 if (*p1 != ' ')
370 foundCodeToLeft = true;
371 break;
373 --p1;
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
379 if (foundCodeToLeft)
381 if (parentColumn != endColumn)
383 report(DiagnosticsEngine::Warning, "end brace not aligned with beginning of statement",
384 endBraceLoc);
385 report(DiagnosticsEngine::Note, "statement beginning here", parentBeginLoc);
387 return;
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())
404 return;
405 auto firstStmt = compoundStmt->body_front();
406 if (isa<LabelStmt>(firstStmt))
407 return;
408 auto firstStmtLoc = compat::getBeginLoc(firstStmt);
409 unsigned firstStmtBeginColumn = SM.getPresumedColumnNumber(firstStmtLoc, &invalid1);
410 if (invalid1)
411 return;
412 if (firstStmtBeginColumn > beginColumn)
413 return;
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/"))
417 return;
418 report(DiagnosticsEngine::Warning, "body inside brace not indented", firstStmtLoc);
421 bool Indentation::VisitSwitchStmt(SwitchStmt const* switchStmt)
423 if (ignoreLocation(switchStmt))
424 return true;
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());
433 if (!compoundStmt)
434 return true;
435 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
437 Stmt const* caseStmt = dyn_cast<CaseStmt>(*i);
438 if (!caseStmt)
439 caseStmt = dyn_cast<DefaultStmt>(*i);
440 if (!caseStmt)
441 continue;
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)
450 continue;
451 prevLine = curLine;
452 curLine = tmpLine;
453 if (column == MAX)
455 column = tmpColumn;
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/
465 (void)firstStmt;
466 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
467 // stmtLoc);
468 // report(DiagnosticsEngine::Note, "measured against this one",
469 // compat::getBeginLoc(firstStmt));
470 //getParentStmt(compoundStmt)->dump();
471 //stmt->dump();
474 return true;
477 loplugin::Plugin::Registration<Indentation> indentation("indentation");
479 } // namespace
481 #endif // LO_CLANG_SHARED_PLUGINS
483 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */