1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * Based on LLVM/Clang.
6  *
7  * This file is distributed under the University of Illinois Open Source
8  * License. See LICENSE.TXT for details.
9  *
10  */
11 #ifndef LO_CLANG_SHARED_PLUGINS
12 
13 #include <cassert>
14 #include <string>
15 #include <iostream>
16 #include <fstream>
17 #include <set>
18 #include "plugin.hxx"
19 
20 /*
21 Check for child statements inside a compound statement that do not share the same indentation.
22 
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
25 */
26 
27 namespace
28 {
29 class Indentation : public loplugin::FilteringPlugin<Indentation>
30 {
31 public:
Indentation(loplugin::InstantiationData const & data)32     explicit Indentation(loplugin::InstantiationData const& data)
33         : FilteringPlugin(data)
34     {
35     }
36 
preRun()37     virtual bool preRun() override
38     {
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;
58     }
59 
run()60     virtual void run() override
61     {
62         if (preRun())
63             TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
64     }
65 
66     bool VisitCompoundStmt(CompoundStmt const*);
67     bool PreTraverseSwitchStmt(SwitchStmt*);
68     bool PostTraverseSwitchStmt(SwitchStmt*, bool);
69     bool TraverseSwitchStmt(SwitchStmt*);
70     bool VisitSwitchStmt(SwitchStmt const*);
71 
72 private:
73     std::vector<const Stmt*> switchStmtBodies;
74 };
75 
PreTraverseSwitchStmt(SwitchStmt * switchStmt)76 bool Indentation::PreTraverseSwitchStmt(SwitchStmt* switchStmt)
77 {
78     switchStmtBodies.push_back(switchStmt->getBody());
79     return true;
80 }
81 
PostTraverseSwitchStmt(SwitchStmt *,bool)82 bool Indentation::PostTraverseSwitchStmt(SwitchStmt*, bool)
83 {
84     switchStmtBodies.pop_back();
85     return true;
86 }
87 
TraverseSwitchStmt(SwitchStmt * switchStmt)88 bool Indentation::TraverseSwitchStmt(SwitchStmt* switchStmt)
89 {
90     PreTraverseSwitchStmt(switchStmt);
91     auto ret = FilteringPlugin::TraverseSwitchStmt(switchStmt);
92     PostTraverseSwitchStmt(switchStmt, ret);
93     return ret;
94 }
95 
VisitCompoundStmt(CompoundStmt const * compoundStmt)96 bool Indentation::VisitCompoundStmt(CompoundStmt const* compoundStmt)
97 {
98     if (ignoreLocation(compoundStmt))
99         return true;
100     // these are kind of free form
101     if (!switchStmtBodies.empty() && switchStmtBodies.back() == compoundStmt)
102         return true;
103 
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)
112     {
113         auto stmt = *i;
114         auto const actualPrevEnd = prevEnd;
115         prevEnd = compat::getEndLoc(stmt); // compute early, before below `continue`s
116 
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))
130         {
131             if (declStmt->decl_begin() != declStmt->decl_end())
132             {
133                 if (auto const decl = dyn_cast<VarDecl>(*declStmt->decl_begin()))
134                 {
135                     if (decl->hasAttr<UnusedAttr>())
136                     {
137                         continue;
138                     }
139                 }
140             }
141         }
142 #endif
143 
144         auto stmtLoc = compat::getBeginLoc(stmt);
145 
146         StringRef macroName;
147         bool partOfMacro = false;
148         if (SM.isMacroArgExpansion(stmtLoc) || SM.isMacroBodyExpansion(stmtLoc))
149         {
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
167         }
168 
169         // check for comment to the left of the statement
170         {
171             const char* p1 = SM.getCharacterData(stmtLoc);
172             --p1;
173             bool foundComment = false;
174             while (*p1 && *p1 != '\n')
175             {
176                 if (*p1 == '/')
177                 {
178                     foundComment = true;
179                     break;
180                 }
181                 --p1;
182             }
183             if (foundComment)
184                 continue;
185         }
186 
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)
196         {
197             column = tmpColumn;
198             firstStmt = stmt;
199         }
200         else if (curLine == prevLine)
201         {
202             // ignore multiple statements on same line
203         }
204         else if (column != tmpColumn)
205         {
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();
217         }
218 
219         if (!partOfMacro)
220             if (auto ifStmt = dyn_cast<IfStmt>(stmt))
221             {
222                 auto bodyStmt = ifStmt->getThen();
223                 if (bodyStmt && !isa<CompoundStmt>(bodyStmt))
224                 {
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;
232 
233                     if (bodyLine != tmpLine && bodyColumn <= tmpColumn)
234                         report(DiagnosticsEngine::Warning, "if body should be indented", stmtLoc);
235                 }
236 
237                 auto elseStmt = ifStmt->getElse();
238                 if (elseStmt && !isa<CompoundStmt>(elseStmt) && !isa<IfStmt>(elseStmt))
239                 {
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);
249                 }
250                 if (elseStmt && !isa<CompoundStmt>(bodyStmt))
251                 {
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);
261                 }
262             }
263     }
264     return true;
265 }
266 
VisitSwitchStmt(SwitchStmt const * switchStmt)267 bool Indentation::VisitSwitchStmt(SwitchStmt const* switchStmt)
268 {
269     if (ignoreLocation(switchStmt))
270         return true;
271 
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)
282     {
283         Stmt const* caseStmt = dyn_cast<CaseStmt>(*i);
284         if (!caseStmt)
285             caseStmt = dyn_cast<DefaultStmt>(*i);
286         if (!caseStmt)
287             continue;
288 
289         auto stmtLoc = compat::getBeginLoc(caseStmt);
290 
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)
300         {
301             column = tmpColumn;
302             firstStmt = caseStmt;
303         }
304         else if (curLine == prevLine)
305         {
306             // ignore multiple statements on same line
307         }
308         else if (column != tmpColumn)
309         {
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();
318         }
319     }
320     return true;
321 }
322 
323 loplugin::Plugin::Registration<Indentation> indentation("indentation");
324 
325 } // namespace
326 
327 #endif // LO_CLANG_SHARED_PLUGINS
328 
329 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
330