blob: 5008863f698739c5f0d18e29bb235e379527df75 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.adobe.ac.pmd.rules.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Logger;
import com.adobe.ac.pmd.IFlexViolation;
import com.adobe.ac.pmd.nodes.IAttribute;
import com.adobe.ac.pmd.nodes.IClass;
import com.adobe.ac.pmd.nodes.IConstant;
import com.adobe.ac.pmd.nodes.IFunction;
import com.adobe.ac.pmd.nodes.INode;
import com.adobe.ac.pmd.nodes.IPackage;
import com.adobe.ac.pmd.parser.IParserNode;
import com.adobe.ac.pmd.parser.NodeKind;
import com.adobe.ac.utils.StackTraceUtils;
/**
* Abstract class for AST-based rule Extends this class if your rule is only
* detectable in an AS script block, which can be converted into an Abstract
* Syntax Tree. Then you will be able to either use the visitor pattern, or to
* iterate from the package node, in order to find your violation(s).
*/
public abstract class AbstractAstFlexRule extends AbstractFlexRule implements IFlexAstRule
{
/**
*
*/
protected enum FunctionType
{
GETTER, NORMAL, SETTER
}
/**
*
*/
protected enum VariableOrConstant
{
CONSTANT, VARIABLE
}
/**
*
*/
protected enum VariableScope
{
IN_CLASS, IN_FUNCTION
}
private interface ExpressionVisitor
{
void visitExpression( final IParserNode ast );
}
private static final Logger LOGGER = Logger.getLogger( AbstractAstFlexRule.class.getName() );
/**
* @param functionNode
* @return
*/
/**
* @param functionNode
* @return
*/
protected static IParserNode getNameFromFunctionDeclaration( final IParserNode functionNode )
{
IParserNode nameChild = null;
for ( final IParserNode child : functionNode.getChildren() )
{
if ( child.is( NodeKind.NAME ) )
{
nameChild = child;
break;
}
}
return nameChild;
}
/**
* @param fieldNode
* @return
*/
protected static IParserNode getTypeFromFieldDeclaration( final IParserNode fieldNode )
{
return fieldNode.getChild( 0 ).getChild( 1 );
}
private final List< IFlexViolation > violations;
/**
*
*/
public AbstractAstFlexRule()
{
super();
violations = new ArrayList< IFlexViolation >();
}
/*
* (non-Javadoc)
* @see
* com.adobe.ac.pmd.rules.core.AbstractFlexRule#isConcernedByTheCurrentFile
* ()
*/
@Override
public boolean isConcernedByTheCurrentFile()
{
return true;
}
/**
* @param function
* @return the added violation positioned on the given function node
*/
protected final IFlexViolation addViolation( final IFunction function )
{
final IParserNode name = getNameFromFunctionDeclaration( function.getInternalNode() );
return addViolation( name,
name,
name.getStringValue() );
}
/**
* @param function
* @param messageToReplace
* @return
*/
protected final IFlexViolation addViolation( final IFunction function,
final String messageToReplace )
{
final IParserNode name = getNameFromFunctionDeclaration( function.getInternalNode() );
return addViolation( name,
name,
messageToReplace );
}
/**
* @param violatingNode
* @return the added violation replacing the threshold value in the message
* if any.
*/
protected final IFlexViolation addViolation( final INode violatingNode )
{
return addViolation( violatingNode.getInternalNode(),
violatingNode.getInternalNode() );
}
/**
* @param violatingNode
* @return the added violation replacing the threshold value in the message
* if any.
*/
protected final IFlexViolation addViolation( final INode violatingNode,
final String... messageToReplace )
{
return addViolation( violatingNode.getInternalNode(),
violatingNode.getInternalNode(),
messageToReplace );
}
/**
* @param violatingNode
* @param endNode
* @return the added violation replacing the threshold value in the message
* if any.
*/
protected final IFlexViolation addViolation( final IParserNode violatingNode )
{
return addViolation( violatingNode,
violatingNode );
}
/**
* @param beginningNode
* @param endNode
* @param messageToReplace
* @return the add violation replacing the {0} token by the specified message
*/
protected final IFlexViolation addViolation( final IParserNode beginningNode,
final IParserNode endNode,
final String... messageToReplace )
{
if ( isAlreadyViolationAdded( beginningNode ) )
{
return null;
}
final IFlexViolation violation = addViolation( ViolationPosition.create( beginningNode.getLine(),
endNode.getLine(),
beginningNode.getColumn(),
endNode.getColumn() ) );
for ( int i = 0; i < messageToReplace.length; i++ )
{
violation.replacePlaceholderInMessage( messageToReplace[ i ],
i );
}
return violation;
}
/**
* @param violatingNode
* @param endNode
* @param messageToReplace
* @return the add violation replacing the {0} token by the specified message
*/
protected final IFlexViolation addViolation( final IParserNode violatingNode,
final String... messageToReplace )
{
return addViolation( violatingNode,
violatingNode,
messageToReplace );
}
/**
* @param violationPosition
* @return the added violation positioned at the given position
*/
protected final IFlexViolation addViolation( final ViolationPosition violationPosition )
{
return addViolation( violations,
violationPosition );
}
/**
* find the violations list from the given class node
*
* @param classNode
*/
protected void findViolations( final IClass classNode )
{
findViolationsFromAttributes( classNode.getAttributes() );
findViolationsFromConstants( classNode.getConstants() );
findViolations( classNode.getFunctions() );
if ( classNode.getConstructor() != null )
{
findViolationsFromConstructor( classNode.getConstructor() );
}
}
/**
* find violations in every function in a class
*
* @param function
*/
protected void findViolations( final IFunction function )
{
}
/**
* Override this method if you need to find violations from the package ( or
* any subsequent node like class or function)
*
* @param packageNode
*/
protected void findViolations( final IPackage packageNode )
{
final IClass classNode = packageNode.getClassNode();
if ( classNode != null )
{
findViolations( classNode );
}
}
/**
* find the violations list from the given functions list
*
* @param functions
*/
protected void findViolations( final List< IFunction > functions )
{
for ( final IFunction function : functions )
{
findViolations( function );
}
}
/**
* find the violations list from the given class variables list
*
* @param variables
*/
protected void findViolationsFromAttributes( final List< IAttribute > variables )
{
}
/**
* find the violations list from the given class constants list
*
* @param constants
*/
protected void findViolationsFromConstants( final List< IConstant > constants )
{
}
/**
* find the violations list from the given class constructor node
*
* @param constructor
*/
protected void findViolationsFromConstructor( final IFunction constructor )
{
}
/**
* Find violations in the current file
*/
@Override
protected final List< IFlexViolation > findViolationsInCurrentFile()
{
try
{
if ( getCurrentPackageNode() != null )
{
visitCompilationUnit( getCurrentPackageNode().getInternalNode() );
findViolations( getCurrentPackageNode() );
}
}
catch ( final Exception e )
{
LOGGER.warning( "on "
+ getCurrentFile().getFilePath() );
LOGGER.warning( StackTraceUtils.print( e ) );
}
final List< IFlexViolation > copy = new ArrayList< IFlexViolation >( violations );
violations.clear();
return copy;
}
/**
* @param statementNode
*/
protected void visitAs( final IParserNode statementNode )
{
}
/**
* @param catchNode
*/
protected void visitCatch( final IParserNode catchNode )
{
visitNameTypeInit( catchNode.getChild( 0 ) );
visitBlock( catchNode.getChild( 1 ) );
}
/**
* @param classNode
*/
protected void visitClass( final IParserNode classNode )
{
IParserNode content = null;
for ( final IParserNode node : classNode.getChildren() )
{
if ( node.is( NodeKind.CONTENT ) )
{
content = node;
break;
}
}
visitClassContent( content );
}
/**
* Visit the condition of a if, while, ...
*
* @param condition
*/
protected void visitCondition( final IParserNode condition )
{
visitExpression( condition );
}
/**
* @param doNode
*/
protected void visitDo( final IParserNode doNode )
{
visitBlock( doNode.getChild( 0 ) );
visitCondition( doNode.getChild( 1 ) );
}
/**
* @param ifNode
*/
protected void visitElse( final IParserNode ifNode )
{
visitBlock( ifNode.getChild( 2 ) );
}
/**
* Visit empty statement
*
* @param statementNode
*/
protected void visitEmptyStatetement( final IParserNode statementNode )
{
}
/**
* @param finallyNode
*/
protected void visitFinally( final IParserNode finallyNode )
{
if ( isNodeNavigable( finallyNode ) )
{
visitBlock( finallyNode.getChild( 0 ) );
}
}
/**
* @param forNode
*/
protected void visitFor( final IParserNode forNode )
{
visitBlock( forNode.getChild( 3 ) );
}
/**
* @param foreachNode
*/
protected void visitForEach( final IParserNode foreachNode )
{
visitBlock( foreachNode.getChild( 2 ) );
}
/**
* @param functionNode
* @param type
*/
protected void visitFunction( final IParserNode functionNode,
final FunctionType type )
{
final Iterator< IParserNode > iterator = functionNode.getChildren().iterator();
IParserNode currentNode = iterator.next();
while ( currentNode.is( NodeKind.META_LIST )
|| currentNode.is( NodeKind.MOD_LIST ) || currentNode.is( NodeKind.AS_DOC )
|| currentNode.is( NodeKind.MULTI_LINE_COMMENT ) )
{
currentNode = iterator.next();
}
currentNode = iterator.next();
visitParameters( currentNode );
currentNode = iterator.next();
visitFunctionReturnType( currentNode );
try
{
// Intrinsic functions in AS2
currentNode = iterator.next();
visitFunctionBody( currentNode );
}
catch ( final NoSuchElementException e )
{
}
}
/**
* @param functionReturnTypeNode
*/
protected void visitFunctionReturnType( final IParserNode functionReturnTypeNode )
{
visitBlock( functionReturnTypeNode );
}
/**
* @param ifNode
*/
protected void visitIf( final IParserNode ifNode )
{
visitCondition( ifNode.getChild( 0 ) );
visitThen( ifNode );
if ( ifNode.numChildren() == 3 )
{
visitElse( ifNode );
}
}
/**
* @param interfaceNode
*/
protected void visitInterface( final IParserNode interfaceNode )
{
}
/**
* @param methodCallNode
*/
protected void visitMethodCall( final IParserNode methodCallNode )
{
final Iterator< IParserNode > iterator = methodCallNode.getChildren().iterator();
visitExpression( iterator.next() );
do
{
visitExpressionList( iterator.next() );
}
while ( iterator.hasNext() );
}
/**
* @param newExpression
*/
protected void visitNewExpression( final IParserNode newExpression )
{
visitExpression( newExpression.getChild( 0 ) );
visitExpressionList( newExpression.getChild( 1 ) );
}
protected void visitOperator( final IParserNode statementNode )
{
}
/**
* @param functionParametersNode
*/
protected void visitParameters( final IParserNode functionParametersNode )
{
if ( isNodeNavigable( functionParametersNode ) )
{
for ( final IParserNode node2 : functionParametersNode.getChildren() )
{
visitNameTypeInit( node2.getChild( 0 ) );
}
}
}
protected void visitRelationalExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.RELATION,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitShiftExpression( ast );
}
} );
}
/**
* @param ast
*/
protected void visitReturn( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
visitExpression( ast.getChild( 0 ) );
}
}
/**
* @param statementNode
*/
protected void visitStatement( final IParserNode statementNode )
{
switch ( statementNode.getId() )
{
case OP:
visitOperator( statementNode );
break;
case AS:
visitAs( statementNode );
break;
case RETURN:
visitReturn( statementNode );
break;
case IF:
visitIf( statementNode );
break;
case FOR:
visitFor( statementNode );
break;
case FOREACH:
visitForEach( statementNode );
break;
case DO:
visitDo( statementNode );
break;
case WHILE:
visitWhile( statementNode );
break;
case SWITCH:
visitSwitch( statementNode );
break;
case TRY:
visitTry( statementNode );
break;
case CATCH:
visitCatch( statementNode );
break;
case FINALLY:
visitFinally( statementNode );
break;
case STMT_EMPTY:
visitEmptyStatetement( statementNode );
break;
case LEFT_CURLY_BRACKET:
visitBlock( statementNode );
break;
default:
visitExpressionList( statementNode );
}
}
/**
* @param switchNode
*/
protected void visitSwitch( final IParserNode switchNode )
{
final Iterator< IParserNode > iterator = switchNode.getChildren().iterator();
visitExpression( iterator.next() );
final IParserNode cases = iterator.next();
for ( final IParserNode caseNode : cases.getChildren() )
{
final IParserNode child = caseNode.getChild( 0 );
if ( child.is( NodeKind.DEFAULT ) )
{
visitSwitchDefaultCase( caseNode.getChild( 1 ) );
}
else
{
visitSwitchCase( caseNode.getChild( 1 ) );
visitExpression( child );
}
}
}
/**
* @param switchCaseNode
*/
protected void visitSwitchCase( final IParserNode switchCaseNode )
{
visitBlock( switchCaseNode );
}
/**
* @param defaultCaseNode
*/
protected void visitSwitchDefaultCase( final IParserNode defaultCaseNode )
{
visitBlock( defaultCaseNode );
}
/**
* @param ifNode
*/
protected void visitThen( final IParserNode ifNode )
{
visitBlock( ifNode.getChild( 1 ) );
}
/**
* @param ast
*/
protected void visitTry( final IParserNode ast )
{
visitBlock( ast.getChild( 0 ) );
}
/**
* @param node
*/
protected void visitVariableInitialization( final IParserNode node )
{
visitExpression( node );
}
/**
* @param variableNode
* @param varOrConst
* @param scope
*/
protected void visitVarOrConstList( final IParserNode variableNode,
final VariableOrConstant varOrConst,
final VariableScope scope )
{
final Iterator< IParserNode > iterator = variableNode.getChildren().iterator();
IParserNode node = iterator.next();
while ( node.is( NodeKind.META_LIST )
|| node.is( NodeKind.MOD_LIST ) )
{
node = iterator.next();
}
while ( node != null )
{
visitNameTypeInit( node );
node = iterator.hasNext() ? iterator.next()
: null;
}
}
/**
* @param whileNode
*/
protected void visitWhile( final IParserNode whileNode )
{
visitCondition( whileNode.getChild( 0 ) );
visitBlock( whileNode.getChild( 1 ) );
}
private boolean isAlreadyViolationAdded( final IParserNode nodeToBeAdded )
{
for ( final IFlexViolation violation : violations )
{
if ( violation.getBeginLine() == nodeToBeAdded.getLine()
&& violation.getBeginColumn() == nodeToBeAdded.getColumn() )
{
return true;
}
}
return false;
}
private boolean isNodeNavigable( final IParserNode node )
{
return node != null
&& node.numChildren() != 0;
}
private void visitAdditiveExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.ADD,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitMultiplicativeExpression( ast );
}
} );
}
private void visitAndExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.AND,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitBitwiseOrExpression( ast );
}
} );
}
private void visitArrayAccessor( final IParserNode ast )
{
final Iterator< IParserNode > iterator = ast.getChildren().iterator();
visitExpression( iterator.next() );
do
{
visitExpression( iterator.next() );
}
while ( iterator.hasNext() );
}
private void visitBitwiseAndExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.B_AND,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitEqualityExpression( ast );
}
} );
}
private void visitBitwiseOrExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.B_OR,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitBitwiseXorExpression( ast );
}
} );
}
private void visitBitwiseXorExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.B_XOR,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitBitwiseAndExpression( ast );
}
} );
}
private void visitBlock( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
for ( final IParserNode node : ast.getChildren() )
{
visitStatement( node );
}
}
else if ( ast != null )
{
visitStatement( ast );
}
}
private void visitClassContent( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
for ( final IParserNode node : ast.getChildren() )
{
if ( node.is( NodeKind.VAR_LIST ) )
{
visitVarOrConstList( node,
VariableOrConstant.VARIABLE,
VariableScope.IN_CLASS );
}
else if ( node.is( NodeKind.CONST_LIST ) )
{
visitVarOrConstList( node,
VariableOrConstant.CONSTANT,
VariableScope.IN_CLASS );
}
}
for ( final IParserNode node : ast.getChildren() )
{
if ( node.is( NodeKind.FUNCTION ) )
{
visitFunction( node,
FunctionType.NORMAL );
}
else if ( node.is( NodeKind.SET ) )
{
visitFunction( node,
FunctionType.SETTER );
}
else if ( node.is( NodeKind.GET ) )
{
visitFunction( node,
FunctionType.GETTER );
}
}
}
}
private void visitCompilationUnit( final IParserNode ast )
{
for ( final IParserNode node : ast.getChildren() )
{
if ( node.is( NodeKind.PACKAGE )
&& node.numChildren() >= 2 )
{
visitPackageContent( node.getChild( 1 ) );
}
if ( !node.is( NodeKind.PACKAGE )
&& node.numChildren() > 0 )
{
visitPackageContent( node );
}
}
}
private void visitConditionalExpression( final IParserNode ast )
{
if ( ast != null )
{
if ( ast.is( NodeKind.CONDITIONAL ) )
{
final Iterator< IParserNode > iterator = ast.getChildren().iterator();
final IParserNode node = iterator.next();
visitOrExpression( node );
while ( iterator.hasNext() )
{
visitExpression( iterator.next() );
visitExpression( iterator.next() );
}
}
else
{
visitOrExpression( ast );
}
}
}
private void visitEqualityExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.EQUALITY,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitRelationalExpression( ast );
}
} );
}
private void visitExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.ASSIGN,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitConditionalExpression( ast );
}
} );
}
private void visitExpression( final IParserNode ast,
final NodeKind kind,
final ExpressionVisitor visitor )
{
if ( ast.is( kind ) )
{
final Iterator< IParserNode > iterator = ast.getChildren().iterator();
final IParserNode node = iterator.next();
visitor.visitExpression( node );
while ( iterator.hasNext() )
{
iterator.next();
visitor.visitExpression( iterator.next() );
}
}
else
{
visitor.visitExpression( ast );
}
}
private void visitExpressionList( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
for ( final IParserNode node : ast.getChildren() )
{
visitExpression( node );
}
}
}
private void visitFunctionBody( final IParserNode node )
{
visitBlock( node );
}
private void visitMultiplicativeExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.MULTIPLICATION,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitUnaryExpression( ast );
}
} );
}
private void visitNameTypeInit( final IParserNode ast )
{
if ( ast != null
&& ast.numChildren() != 0 )
{
final Iterator< IParserNode > iterator = ast.getChildren().iterator();
IParserNode node;
iterator.next();
iterator.next();
if ( iterator.hasNext() )
{
node = iterator.next();
visitVariableInitialization( node );
}
}
}
private void visitObjectInitialization( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
for ( final IParserNode node : ast.getChildren() )
{
visitExpression( node.getChild( 1 ) );
}
}
}
private void visitOrExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.OR,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitAndExpression( ast );
}
} );
}
private void visitPackageContent( final IParserNode ast )
{
if ( isNodeNavigable( ast ) )
{
for ( final IParserNode node : ast.getChildren() )
{
if ( node.is( NodeKind.CLASS ) )
{
visitClass( node );
}
else if ( node.is( NodeKind.INTERFACE ) )
{
visitInterface( node );
}
}
}
}
private void visitPrimaryExpression( final IParserNode ast )
{
if ( ast.is( NodeKind.NEW ) )
{
visitNewExpression( ast );
}
else if ( ast.numChildren() != 0
&& ast.is( NodeKind.ARRAY ) )
{
visitExpressionList( ast );
}
else if ( ast.is( NodeKind.OBJECT ) )
{
visitObjectInitialization( ast );
}
else if ( ast.is( NodeKind.E4X_ATTR ) )
{
final IParserNode node = ast.getChild( 0 );
if ( !node.is( NodeKind.NAME )
&& !node.is( NodeKind.STAR ) )
{
visitExpression( node );
}
}
else
{
visitExpressionList( ast );
}
}
private void visitShiftExpression( final IParserNode ast )
{
visitExpression( ast,
NodeKind.SHIFT,
new ExpressionVisitor()
{
public void visitExpression( final IParserNode ast )
{
visitAdditiveExpression( ast );
}
} );
}
private void visitUnaryExpression( final IParserNode ast )
{
switch ( ast.getId() )
{
case PRE_INC:
case PRE_DEC:
case MINUS:
case PLUS:
visitUnaryExpression( ast.getChild( 0 ) );
break;
default:
visitUnaryExpressionNotPlusMinus( ast );
}
}
private void visitUnaryExpressionNotPlusMinus( final IParserNode ast )
{
switch ( ast.getId() )
{
case DELETE:
case VOID:
case TYPEOF:
case NOT:
case B_NOT:
visitExpression( ast.getChild( 0 ) );
break;
default:
visitUnaryPostfixExpression( ast );
}
}
private void visitUnaryPostfixExpression( final IParserNode ast )
{
switch ( ast.getId() )
{
case ARRAY_ACCESSOR:
visitArrayAccessor( ast );
break;
case DOT:
case E4X_FILTER:
visitExpression( ast.getChild( 0 ) );
visitExpression( ast.getChild( 1 ) );
break;
case POST_INC:
case POST_DEC:
visitPrimaryExpression( ast.getChild( 0 ) );
break;
case CALL:
visitMethodCall( ast );
break;
case E4X_STAR:
visitExpression( ast.getChild( 0 ) );
break;
default:
visitPrimaryExpression( ast );
break;
}
}
}