/*
 * 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.
 *
 */

#include "bodynotinblock.hxx"

#include <clang/AST/ASTContext.h>
#include <clang/Basic/SourceManager.h>

namespace loplugin
{

/*
This is a compile check.

Check for two statements that are both indented to look like a body of if/while/for
but are not inside a compound statement and thus the second one is unrelated.
*/

BodyNotInBlock::BodyNotInBlock( ASTContext& context )
    : Plugin( context )
    {
    }

void BodyNotInBlock::run()
    {
    TraverseDecl( context.getTranslationUnitDecl());
    }

bool BodyNotInBlock::VisitFunctionDecl( FunctionDecl* declaration )
    {
    if( ignoreLocation( declaration ))
        return true;
    if( !declaration->doesThisDeclarationHaveABody())
        return true;
    StmtParents parents;
    traverseStatement( declaration->getBody(), parents );
    return true;
    }

void BodyNotInBlock::traverseStatement( const Stmt* stmt, StmtParents& parents )
    {
    parents.push_back( stmt );
    for( ConstStmtIterator it = stmt->child_begin();
         it != stmt->child_end();
         ++it )
        {
        if( *it != NULL ) // some children can be apparently NULL
            {
            traverseStatement( *it, parents ); // substatements first
            parents.push_back( *it );
            if( const IfStmt* ifstmt = dyn_cast< IfStmt >( *it ))
                {
                checkBody( ifstmt->getThen(), ifstmt->getIfLoc(), parents, 0, ifstmt->getElse() != NULL );
                checkBody( ifstmt->getElse(), ifstmt->getElseLoc(), parents, 0 );
                }
            else if( const WhileStmt* whilestmt = dyn_cast< WhileStmt >( *it ))
                checkBody( whilestmt->getBody(), whilestmt->getWhileLoc(), parents, 1 );
            else if( const ForStmt* forstmt = dyn_cast< ForStmt >( *it ))
                checkBody( forstmt->getBody(), forstmt->getForLoc(), parents, 2 );
            else if( const CXXForRangeStmt* forstmt = dyn_cast< CXXForRangeStmt >( *it ))
                checkBody( forstmt->getBody(), forstmt->getForLoc(), parents, 2 );
            parents.pop_back();
            }
        }
    assert( parents.back() == stmt );
    parents.pop_back();
    }

void BodyNotInBlock::checkBody( const Stmt* body, SourceLocation stmtLocation, const StmtParents& parents,
    int stmtType, bool dontGoUp )
    {
    if( body == NULL || parents.size() < 2 )
        return;
    // TODO: If the if/else/while/for comes from a macro expansion, ignore it completely for
    // now. The code below could assume everything is in the same place (and thus also column)
    // and give a false warning. Moreover some macros are rather lousily written and would
    // result in poor formatting. To be evaluated later, maybe this could be handled
    // including macro expansion.
    if( stmtLocation.isMacroID())
        return;
    if( dyn_cast< CompoundStmt >( body ))
        return; // if body is a compound statement, then it is in {}
    // Find the next statement (in source position) after 'body'.
    for( int parent_pos = parents.size() - 2; // start from grandparent
         parent_pos >= 0;
         --parent_pos )
        {
        for( ConstStmtIterator it = parents[ parent_pos ]->child_begin();
             it != parents[ parent_pos ]->child_end();
             )
            {
            if( *it == parents[ parent_pos + 1 ] ) // found grand(grand...)parent
                {
                // get next statement after our (grand...)parent
                ++it;
                while( it != parents[ parent_pos ]->child_end() && *it == NULL )
                    ++it; // skip empty ones (missing 'else' bodies for example)
                if( it != parents[ parent_pos ]->child_end())
                    {
                    bool invalid1, invalid2;
                    unsigned bodyColumn = context.getSourceManager()
                        .getPresumedColumnNumber( body->getLocStart(), &invalid1 );
                    unsigned nextStatementColumn = context.getSourceManager()
                        .getPresumedColumnNumber( (*it)->getLocStart(), &invalid2 );
                    if( invalid1 || invalid2 )
                        return;
                    if( bodyColumn == nextStatementColumn )
                        {
                        report( DiagnosticsEngine::Warning,
                            "statement aligned as second statement in %select{if|while|for}0 body but not in a statement block [loplugin]",
                            (*it)->getLocStart()) << stmtType;
                        report( DiagnosticsEngine::Note,
                            "%select{if|while|for}0 body statement is here [loplugin]",
                            body->getLocStart()) << stmtType;
                        }
                    return;
                    }
                // else we need to go higher to find the next statement
                }
            else
                ++it;
            }
        // If going up would mean leaving a {} block, stop, because the } should
        // make it visible the two statements are not in the same body.
        if( dyn_cast< CompoundStmt >( parents[ parent_pos ] ))
            return;
        // If the body to be checked is a body of an if statement that has also
        // an else part, don't go up, the else is after the body and should make
        // it clear the body does not continue there.
        if( dontGoUp )
            return;
        }
    }

} // namespace
