/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "clang/AST/Attr.h"

#include "check.hxx"
#include "compat.hxx"
#include "plugin.hxx"

/*
  Look for member functions that can be static
*/
namespace {

class StaticMethods:
    public RecursiveASTVisitor<StaticMethods>, public loplugin::Plugin
{
private:
    bool bVisitedThis;
public:
    explicit StaticMethods(InstantiationData const & data): Plugin(data), bVisitedThis(false) {}

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

    bool TraverseCXXMethodDecl(const CXXMethodDecl * decl);

    bool VisitCXXThisExpr(const CXXThisExpr *) { bVisitedThis = true; return true; }
    // these two indicate that we hit something that makes our analysis unreliable
    bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) { bVisitedThis = true; return true; }
    bool VisitCXXDependentScopeMemberExpr(const CXXDependentScopeMemberExpr *) { bVisitedThis = true; return true; }
private:
    std::string getFilename(SourceLocation loc);
};

bool BaseCheckNotTestFixtureSubclass(
    const CXXRecordDecl *BaseDefinition
#if CLANG_VERSION < 30800
    , void *
#endif
    )
{
    if (BaseDefinition->getQualifiedNameAsString().compare("CppUnit::TestFixture") == 0) {
        return false;
    }
    return true;
}

bool isDerivedFromTestFixture(const CXXRecordDecl *decl) {
    if (!decl->hasDefinition())
        return false;
    if (// not sure what hasAnyDependentBases() does,
        // but it avoids classes we don't want, e.g. WeakAggComponentImplHelper1
        !decl->hasAnyDependentBases() &&
        !compat::forallBases(*decl, BaseCheckNotTestFixtureSubclass, nullptr, true)) {
        return true;
    }
    return false;
}

std::string StaticMethods::getFilename(SourceLocation loc)
{
    SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(loc);
    return compiler.getSourceManager().getFilename(spellingLocation);
}

bool startsWith(const std::string& rStr, const char* pSubStr) {
    return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
}

bool StaticMethods::TraverseCXXMethodDecl(const CXXMethodDecl * pCXXMethodDecl) {
    if (ignoreLocation(pCXXMethodDecl)) {
        return true;
    }
    if (!pCXXMethodDecl->isInstance() || pCXXMethodDecl->isVirtual() || !pCXXMethodDecl->hasBody()) {
        return true;
    }
    if (pCXXMethodDecl->getOverloadedOperator() != OverloadedOperatorKind::OO_None || pCXXMethodDecl->hasAttr<OverrideAttr>()) {
        return true;
    }
    if (isa<CXXConstructorDecl>(pCXXMethodDecl) || isa<CXXDestructorDecl>(pCXXMethodDecl) || isa<CXXConversionDecl>(pCXXMethodDecl)) {
        return true;
    }
    if (isInUnoIncludeFile(pCXXMethodDecl)) {
        return true;
    }
    if ( pCXXMethodDecl != pCXXMethodDecl->getCanonicalDecl() ) {
        return true;
    }

    // the CppUnit stuff uses macros and methods that can't be changed
    if (isDerivedFromTestFixture(pCXXMethodDecl->getParent())) {
        return true;
    }
    // don't mess with the backwards compatibility stuff
    if (getFilename(pCXXMethodDecl->getLocStart()) == SRCDIR "/cppuhelper/source/compat.cxx") {
        return true;
    }
    // the DDE has a dummy implementation on Linux and a real one on Windows
    std::string aFilename = getFilename(pCXXMethodDecl->getCanonicalDecl()->getLocStart());
    if (aFilename == SRCDIR "/include/svl/svdde.hxx") {
        return true;
    }
    auto cdc = loplugin::DeclCheck(pCXXMethodDecl->getParent());
    // special case having something to do with static initialisation
    // sal/osl/all/utility.cxx
    if (cdc.Class("OGlobalTimer").Namespace("osl").GlobalNamespace()) {
        return true;
    }
    // leave the TopLeft() method alone for consistency with the other "corner" methods
    if (cdc.Class("BitmapInfoAccess").GlobalNamespace()) {
        return true;
    }
    // in this case, the code is taking the address of the member function
    // shell/source/unix/sysshell/recently_used_file_handler.cxx
    if (cdc.Struct("recently_used_item").AnonymousNamespace().GlobalNamespace())
    {
        return true;
    }
    // the unotools and svl config code stuff is doing weird stuff with a reference-counted statically allocated pImpl class
    if (startsWith(aFilename, SRCDIR "/include/unotools")) {
        return true;
    }
    if (startsWith(aFilename, SRCDIR "/include/svl")) {
        return true;
    }
    if (startsWith(aFilename, SRCDIR "/include/framework") || startsWith(aFilename, SRCDIR "/framework")) {
        return true;
    }
    // there is some odd stuff happening here I don't fully understand, leave it for now
    if (startsWith(aFilename, SRCDIR "/include/canvas") || startsWith(aFilename, SRCDIR "/canvas")) {
        return true;
    }
    // classes that have static data and some kind of weird reference-counting trick in its constructor
    if (cdc.Class("LinguOptions").GlobalNamespace()
        || (cdc.Class("EditableExtendedColorConfig").Namespace("svtools")
            .GlobalNamespace())
        || (cdc.Class("ExtendedColorConfig").Namespace("svtools")
            .GlobalNamespace())
        || cdc.Class("SvtMiscOptions").GlobalNamespace()
        || cdc.Class("SvtAccessibilityOptions").GlobalNamespace()
        || cdc.Class("ColorConfig").Namespace("svtools").GlobalNamespace()
        || cdc.Class("SvtOptionsDrawinglayer").GlobalNamespace()
        || cdc.Class("SvtMenuOptions").GlobalNamespace()
        || cdc.Class("SvtToolPanelOptions").GlobalNamespace()
        || cdc.Class("SvtSlideSorterBarOptions").GlobalNamespace()
        || (cdc.Class("SharedResources").Namespace("connectivity")
            .GlobalNamespace())
        || (cdc.Class("OParseContextClient").Namespace("svxform")
            .GlobalNamespace())
        || cdc.Class("OLimitedFormats").Namespace("frm").GlobalNamespace())
    {
        return true;
    }
    auto fdc = loplugin::DeclCheck(pCXXMethodDecl);
    // only empty on Linux, not on windows
    if ((fdc.Function("GetVisualRepresentationInNativeFormat_Impl")
         .Class("OleEmbeddedObject").GlobalNamespace())
        || (fdc.Function("GetRidOfComponent").Class("OleEmbeddedObject")
            .GlobalNamespace())
        || (fdc.Function("isProfileLocked").Class("ProfileAccess")
            .Namespace("mozab").Namespace("connectivity").GlobalNamespace())
        || cdc.Class("SbxDecimal").GlobalNamespace()
        || fdc.Function("Call").Class("SbiDllMgr").GlobalNamespace()
        || fdc.Function("FreeDll").Class("SbiDllMgr").GlobalNamespace()
        || (fdc.Function("InitializeDde").Class("SfxApplication")
            .GlobalNamespace())
        || (fdc.Function("RemoveDdeTopic").Class("SfxApplication")
            .GlobalNamespace())
        || (fdc.Function("ReleaseData").Class("ScannerManager")
            .GlobalNamespace()))
    {
        return true;
    }
    // debugging stuff
    if (fdc.Function("dump").Class("InternalData").Namespace("chart")
        .GlobalNamespace())
    {
        return true;
    }
    // used in a function-pointer-table
    if ((cdc.Class("SbiRuntime").GlobalNamespace()
         && startsWith(pCXXMethodDecl->getNameAsString(), "Step"))
        || (cdc.Class("OoxFormulaParserImpl").Namespace("xls").Namespace("oox")
            .GlobalNamespace())
        || cdc.Class("SwTableFormula").GlobalNamespace()
        || (cdc.Class("BiffFormulaParserImpl").Namespace("xls").Namespace("oox")
            .GlobalNamespace())
        || (fdc.Function("Read_F_Shape").Class("SwWW8ImplReader")
            .GlobalNamespace())
        || (fdc.Function("Read_Majority").Class("SwWW8ImplReader")
            .GlobalNamespace())
        || fdc.Function("Ignore").Class("SwWrtShell").GlobalNamespace())
    {
        return true;
    }
    // have no idea why this can't be static, but 'make check' fails with it so...
    if (fdc.Function("resolveRelationshipsOfTypeFromOfficeDoc").Class("Shape")
        .Namespace("drawingml").Namespace("oox").GlobalNamespace())
    {
        return true;
    }
    // template magic
    if (fdc.Function("getValue").Class("ColumnBatch").GlobalNamespace()
        || cdc.Class("TitleImpl").GlobalNamespace()
        || (fdc.Function("getDefaultPropertyName").Class("DefaultReturnHelper")
            .Namespace("vba").Namespace("ooo").GlobalNamespace()))
    {
        return true;
    }
    // depends on config options
    if ((fdc.Function("autoInstallFontLangSupport").Class("PrintFontManager")
         .Namespace("psp").GlobalNamespace())
        || fdc.Function("AllocateFrame").Class("GtkSalFrame").GlobalNamespace()
        || (fdc.Function("TriggerPaintEvent").Class("GtkSalFrame")
            .GlobalNamespace()))
    {
        return true;
    }

    bVisitedThis = false;
    TraverseStmt(pCXXMethodDecl->getBody());
    if (bVisitedThis) {
        return true;
    }

    report(
        DiagnosticsEngine::Warning,
        "this member function can be declared static",
        pCXXMethodDecl->getCanonicalDecl()->getLocation())
      << pCXXMethodDecl->getCanonicalDecl()->getSourceRange();
    FunctionDecl const * def;
    if (pCXXMethodDecl->isDefined(def)
        && def != pCXXMethodDecl->getCanonicalDecl())
    {
        report(DiagnosticsEngine::Note, "defined here:", def->getLocation())
            << def->getSourceRange();
    }
    return true;
}

loplugin::Plugin::Registration<StaticMethods> X("staticmethods");

}

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