bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / indentation.cxx
blobdeac18121443d1918b7e4f3d626b6e4b83cba030
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.
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <set>
17 #include "plugin.hxx"
20 Check for child statements inside a compound statement that do not share the same indentation.
22 TODO if an open-brace starts on a new line by itself, check that it lines up with it's closing-brace
23 TODO else should line up with if
26 namespace
28 class Indentation : public loplugin::FilteringPlugin<Indentation>
30 public:
31 explicit Indentation(loplugin::InstantiationData const& data)
32 : FilteringPlugin(data)
36 virtual void run() override
38 std::string fn(handler.getMainFileName());
39 loplugin::normalizeDotDotInFilePath(fn);
40 // include another file to build a table
41 if (fn == SRCDIR "/sc/source/core/tool/cellkeytranslator.cxx")
42 return;
43 // weird structure
44 if (fn == SRCDIR "/sc/source/core/tool/compiler.cxx")
45 return;
46 // looks like lex/yacc output
47 if (fn == SRCDIR "/hwpfilter/source/grammar.cxx")
48 return;
49 // TODO need to learn to handle attributes like "[[maybe_unused]]"
50 if (fn == SRCDIR "/binaryurp/source/bridge.cxx")
51 return;
52 // the QEMIT macros
53 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/vcl/qt5/")
54 || loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/gtk3_kde5/kde5_filepicker_ipc.cxx"))
55 return;
56 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
59 bool VisitCompoundStmt(CompoundStmt const*);
60 bool TraverseSwitchStmt(SwitchStmt*);
61 bool VisitSwitchStmt(SwitchStmt const*);
63 private:
64 Stmt const* switchStmtBody = nullptr;
67 bool Indentation::TraverseSwitchStmt(SwitchStmt* switchStmt)
69 auto prev = switchStmtBody;
70 switchStmtBody = switchStmt->getBody();
71 FilteringPlugin::TraverseSwitchStmt(switchStmt);
72 switchStmtBody = prev;
73 return true;
76 bool Indentation::VisitCompoundStmt(CompoundStmt const* compoundStmt)
78 if (ignoreLocation(compoundStmt))
79 return true;
80 // these are kind of free form
81 if (switchStmtBody == compoundStmt)
82 return true;
84 constexpr unsigned MAX = std::numeric_limits<unsigned>::max();
85 unsigned column = MAX;
86 Stmt const* firstStmt = nullptr;
87 unsigned curLine = MAX;
88 unsigned prevLine = MAX;
89 SourceLocation prevEnd;
90 auto& SM = compiler.getSourceManager();
91 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
93 auto stmt = *i;
94 auto const actualPrevEnd = prevEnd;
95 prevEnd = compat::getEndLoc(stmt); // compute early, before below `continue`s
97 // these show up in macro expansions, not interesting
98 if (isa<NullStmt>(stmt))
99 continue;
100 // these are always weirdly indented
101 if (isa<LabelStmt>(stmt))
102 continue;
104 auto stmtLoc = compat::getBeginLoc(stmt);
106 StringRef macroName;
107 if (SM.isMacroArgExpansion(stmtLoc) || SM.isMacroBodyExpansion(stmtLoc))
109 macroName = Lexer::getImmediateMacroNameForDiagnostics(
110 stmtLoc, compiler.getSourceManager(), compiler.getLangOpts());
111 // CPPUNIT_TEST_SUITE/CPPUNIT_TEST/CPPUNIT_TEST_SUITE_END work together, so the one is indented inside the other
112 if (macroName == "CPPUNIT_TEST_SUITE")
113 continue;
114 // similar thing in dbaccess/
115 if (macroName == "DECL_PROP_IMPL")
116 continue;
117 // similar thing in forms/
118 if (macroName == "DECL_IFACE_PROP_IMPL" || macroName == "DECL_BOOL_PROP_IMPL")
119 continue;
120 #if CLANG_VERSION >= 70000
121 stmtLoc = SM.getExpansionRange(stmtLoc).getBegin();
122 #else
123 stmtLoc = SM.getExpansionRange(stmtLoc).first;
124 #endif
127 // check for comment to the left of the statement
129 const char* p1 = SM.getCharacterData(stmtLoc);
130 --p1;
131 bool foundComment = false;
132 while (*p1 && *p1 != '\n')
134 if (*p1 == '/')
136 foundComment = true;
137 break;
139 --p1;
141 if (foundComment)
142 continue;
145 bool invalid1 = false;
146 bool invalid2 = false;
147 unsigned tmpColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
148 unsigned tmpLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
149 if (invalid1 || invalid2)
150 continue;
151 prevLine = curLine;
152 curLine = tmpLine;
153 if (column == MAX)
155 column = tmpColumn;
156 firstStmt = stmt;
158 else if (curLine == prevLine)
160 // ignore multiple statements on same line
162 else if (column != tmpColumn)
164 if (containsPreprocessingConditionalInclusion(SourceRange(
165 locationAfterToken(compiler.getSourceManager().getExpansionLoc(actualPrevEnd)),
166 compiler.getSourceManager().getExpansionLoc(compat::getBeginLoc(stmt)))))
167 continue;
168 report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours %0",
169 stmtLoc)
170 << macroName;
171 report(DiagnosticsEngine::Note, "measured against this one",
172 compat::getBeginLoc(firstStmt));
173 //getParentStmt(compoundStmt)->dump();
174 //stmt->dump();
177 return true;
180 bool Indentation::VisitSwitchStmt(SwitchStmt const* switchStmt)
182 if (ignoreLocation(switchStmt))
183 return true;
185 constexpr unsigned MAX = std::numeric_limits<unsigned>::max();
186 unsigned column = MAX;
187 Stmt const* firstStmt = nullptr;
188 unsigned curLine = MAX;
189 unsigned prevLine = MAX;
190 auto& SM = compiler.getSourceManager();
191 auto compoundStmt = dyn_cast<CompoundStmt>(switchStmt->getBody());
192 if (!compoundStmt)
193 return true;
194 for (auto i = compoundStmt->body_begin(); i != compoundStmt->body_end(); ++i)
196 Stmt const* caseStmt = dyn_cast<CaseStmt>(*i);
197 if (!caseStmt)
198 caseStmt = dyn_cast<DefaultStmt>(*i);
199 if (!caseStmt)
200 continue;
202 auto stmtLoc = compat::getBeginLoc(caseStmt);
204 bool invalid1 = false;
205 bool invalid2 = false;
206 unsigned tmpColumn = SM.getPresumedColumnNumber(stmtLoc, &invalid1);
207 unsigned tmpLine = SM.getPresumedLineNumber(stmtLoc, &invalid2);
208 if (invalid1 || invalid2)
209 continue;
210 prevLine = curLine;
211 curLine = tmpLine;
212 if (column == MAX)
214 column = tmpColumn;
215 firstStmt = caseStmt;
217 else if (curLine == prevLine)
219 // ignore multiple statements on same line
221 else if (column != tmpColumn)
223 // disable this for now, ends up touching some very large switch statements in sw/ and sc/
224 (void)firstStmt;
225 // report(DiagnosticsEngine::Warning, "statement mis-aligned compared to neighbours",
226 // stmtLoc);
227 // report(DiagnosticsEngine::Note, "measured against this one",
228 // compat::getBeginLoc(firstStmt));
229 //getParentStmt(compoundStmt)->dump();
230 //stmt->dump();
233 return true;
236 loplugin::Plugin::Registration<Indentation> X("indentation");
238 } // namespace
240 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */