summaryrefslogtreecommitdiffstats
path: root/compilerplugins/clang/namespaceindentation.cxx
blob: 1398efc86adf5fd3af31849930672ddfab25118c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * Based on LLVM/Clang.
 *
 * This file is distributed under the University of Illinois Open Source
 * License. See LICENSE.TXT for details.
 *
 */
#ifndef LO_CLANG_SHARED_PLUGINS

#include <cassert>
#include <string>
#include <iostream>
#include <locale>
#include <fstream>
#include <set>
#include "plugin.hxx"

/*
*/

namespace
{
class NamespaceIndentation : public loplugin::FilteringPlugin<NamespaceIndentation>
{
public:
    explicit NamespaceIndentation(loplugin::InstantiationData const& data)
        : FilteringPlugin(data)
    {
    }

    virtual bool preRun() override { return true; }

    virtual void run() override
    {
        if (preRun())
            TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
    }

    bool VisitNamespaceDecl(NamespaceDecl const*);

private:
    std::string GetFullNamespace(const NamespaceDecl* nsDecl);
};

void trim(std::string& str)
{
    // right trim
    auto it1 = std::find_if(str.rbegin(), str.rend(), [](char ch) {
        return !std::isspace<char>(ch, std::locale::classic());
    });
    str.erase(it1.base(), str.end());
    // left trim
    auto it2 = std::find_if(str.begin(), str.end(), [](char ch) {
        return !std::isspace<char>(ch, std::locale::classic());
    });
    str.erase(str.begin(), it2);
}

bool NamespaceIndentation::VisitNamespaceDecl(NamespaceDecl const* nsDecl)
{
    if (ignoreLocation(nsDecl))
        return true;
    if (nsDecl->isAnonymousNamespace())
        return true;
    if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(nsDecl->getLocation())))
        return true;

    // right now, just fixing up the fallout from clang-tidy-modernize-namespaces, which
    // does not touch header files
    if (!compiler.getSourceManager().isInMainFile(nsDecl->getLocation()))
        return true;

    auto& SM = compiler.getSourceManager();

    // if we have a combined ns (.e.g namespace aaa::bbb), this appears in the AST
    // as two nested namespace sharing the same source locations, so ignore the outer decls
    if (!nsDecl->decls_empty())
    {
        auto child = dyn_cast_or_null<NamespaceDecl>(*nsDecl->decls_begin());
        if (child)
        {
            bool invalid1 = false;
            bool invalid2 = false;
            unsigned line1 = SM.getPresumedLineNumber(nsDecl->getBeginLoc(), &invalid1);
            unsigned line2 = SM.getPresumedLineNumber(child->getBeginLoc(), &invalid2);
            if (line1 == line2)
                return true;
        }
    }

    // Truly hacky way to find the actual beginning of an xxx::yyy namespace declaration
    // if we are inside the yyy NameSpaceDecl of
    //      namespace xxx::yyy
    // the beginLoc is just between the "xxx" and the "::"
    auto nsDeclBeginLoc = nsDecl->getBeginLoc();
    bool foundMultiple = false;
    {
        constexpr int BACKSCAN = 32;
        auto beginLoc = nsDecl->getBeginLoc().getLocWithOffset(-BACKSCAN);
        auto endLoc = nsDecl->getBeginLoc().getLocWithOffset(3);
        const char* p1 = SM.getCharacterData(beginLoc);
        const char* p2 = SM.getCharacterData(endLoc);
        unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
        if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
            return true;
        auto s = std::string(p1, p2 - p1);
        auto idx1 = s.rfind(" "); // find the space preceding the namespace token
        if (idx1 != std::string::npos)
        {
            auto namespaceToken = s.substr(idx1);
            if (namespaceToken.find("::") != std::string::npos)
            {
                auto idx = s.rfind("\n");
                nsDeclBeginLoc = nsDecl->getBeginLoc().getLocWithOffset(idx - BACKSCAN + 1);
                foundMultiple = true;
            }
        }
    }

    // for now, I am only interested in fixing the fallout from clang-tidy-modernize-namespace, not
    // anything else
    if (!foundMultiple)
        return true;

    bool invalid1 = false;
    bool invalid2 = false;
    unsigned col1 = SM.getPresumedColumnNumber(nsDeclBeginLoc, &invalid1);
    unsigned col2 = SM.getPresumedColumnNumber(nsDecl->getRBraceLoc(), &invalid2);
    unsigned line1 = SM.getPresumedLineNumber(nsDeclBeginLoc, &invalid1);
    unsigned line2 = SM.getPresumedLineNumber(nsDecl->getRBraceLoc(), &invalid2);
    if (invalid1 || invalid2)
        return true;
    if (line1 == line2) // single line declaration
        return true;
    if (col1 != col2)
        report(DiagnosticsEngine::Warning, "statement right brace mis-aligned",
               nsDecl->getRBraceLoc());

    // no easy way to get the position of the left brace
    auto endLoc = nsDecl->getBeginLoc().getLocWithOffset(256);
    const char* p1 = SM.getCharacterData(SM.getExpansionLoc(nsDecl->getBeginLoc()));
    const char* p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
    unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
    if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
        return true;
    auto s = std::string(p1, p2 - p1 + n);
    auto idx1 = s.find("\n");
    auto idx2 = s.find("{");
    if (idx1 != std::string::npos && idx2 != std::string::npos)
        if (idx1 < idx2)
        {
            auto col3 = idx2 - idx1;
            if (col1 != col3)
                report(DiagnosticsEngine::Warning, "statement left brace mis-aligned",
                       nsDecl->getBeginLoc());
        }

    // extract the comment following the end brace
    auto beginLoc = nsDecl->getRBraceLoc();
    endLoc = beginLoc.getLocWithOffset(128);
    p1 = SM.getCharacterData(SM.getExpansionLoc(beginLoc));
    p2 = SM.getCharacterData(SM.getExpansionLoc(endLoc));
    n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
    if (p2 < p1 || n > 128 || (p2 - p1 + n) > 2048)
        return true;
    s = std::string(p1, p2 - p1 + n);
    idx1 = s.find("//");
    idx2 = s.find("\n");
    if (idx1 != std::string::npos && idx2 != std::string::npos && idx1 < idx2)
    {
        idx1 += 2;
        s = s.substr(idx1, idx2 - idx1);
        trim(s);
        std::string fullNamespace = GetFullNamespace(nsDecl);
        if (!(s == fullNamespace || s == (fullNamespace + " namespace") || s == "namespace"
              || s == ("namespace " + fullNamespace) || s == ("namespace ::" + fullNamespace)
              || s == ("end " + fullNamespace) || s == "end namespace"
              || s == ("end namespace " + fullNamespace)
              || s == ("end " + fullNamespace + " namespace") || s == "end of namespace"
              || s == ("end of namespace " + fullNamespace)
              || s == ("end of namespace ::" + fullNamespace)
              || s == ("eof of namespace " + fullNamespace)))
        {
            report(DiagnosticsEngine::Warning, "incorrect comment at end of namespace %0",
                   nsDecl->getRBraceLoc())
                << fullNamespace;
        }
    }

    return true;
}

std::string NamespaceIndentation::GetFullNamespace(const NamespaceDecl* nsDecl)
{
    std::vector<llvm::StringRef> names;
    auto ns = nsDecl;
    while (ns)
    {
        names.push_back(ns->getName());
        ns = dyn_cast<NamespaceDecl>(ns->getParent());
    }
    std::string fullNamespace;
    for (auto it = names.rbegin(); it != names.rend(); ++it)
        fullNamespace += "::" + it->str();
    fullNamespace = fullNamespace.substr(2);
    return fullNamespace;
}

// leave this off by default, so as not to annoy people
loplugin::Plugin::Registration<NamespaceIndentation> namespaceindentation("namespaceindentation",
                                                                          false);

} // namespace

#endif // LO_CLANG_SHARED_PLUGINS

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */