Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect useless code #20

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/main/groovy/nextflow/lsp/services/script/ScriptAstCache.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.syntax.SyntaxException

import nextflow.lsp.compiler.SyntaxWarning
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassCodeVisitorSupport
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.Expression
import org.codehaus.groovy.ast.expr.MethodCall
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.stmt.Statement
import org.codehaus.groovy.control.messages.SyntaxErrorMessage
import org.codehaus.groovy.syntax.Types

/**
*
* @author Ben Sherman <[email protected]>
Expand Down Expand Up @@ -68,6 +80,14 @@ class ScriptAstCache extends ASTNodeCache {
errorsByUri[uri].addAll(visitor.getErrors())
}

for( final uri : changedUris ) {
final sourceUnit = getSourceUnit(uri)
final visitor = new UselessCodeVisitor(sourceUnit)
visitor.visit()
errorsByUri[uri].removeIf((error) -> error instanceof UselessCodeWarning)
errorsByUri[uri].addAll(visitor.getErrors())
}

return errorsByUri
}

Expand Down Expand Up @@ -248,4 +268,72 @@ class ScriptAstCache extends ASTNodeCache {
}
}

private static class UselessCodeVisitor extends ClassCodeVisitorSupport implements ScriptVisitor {

private SourceUnit sourceUnit

private List<SyntaxException> errors = []

public UselessCodeVisitor(SourceUnit sourceUnit) {
this.sourceUnit = sourceUnit
}

@Override
protected SourceUnit getSourceUnit() {
return sourceUnit
}

void visit() {
final moduleNode = sourceUnit.getAST()
if( moduleNode !instanceof ScriptNode )
return
visit((ScriptNode) moduleNode)
}

private Statement currentImplicitReturn

@Override
public void visitBlockStatement(BlockStatement node) {
// TODO: implicit return is only for function or closure
final cir = currentImplicitReturn
currentImplicitReturn = node.statements.size() > 0 ? node.statements.last() : null
super.visitBlockStatement(node)
currentImplicitReturn = cir
}

@Override
public void visitExpressionStatement(ExpressionStatement node) {
if( node != currentImplicitReturn && !isEffectful(node.expression) )
addWarning("Statement has no effect", node)
super.visitExpressionStatement(node)
}

protected boolean isEffectful(Expression node) {
if( node instanceof MethodCall )
return true
if( node instanceof BinaryExpression ) {
// TODO: << or >> on a string, collection, or file
// TODO: property or pipe chain ending in set/subscribe/view
return node.getOperation().isA(Types.ASSIGNMENT_OPERATOR)
}
return false
}

protected void addWarning(String message, ASTNode node) {
errors.add(new UselessCodeWarning(message, node))
}

List<SyntaxException> getErrors() {
return errors
}

}

private static class UselessCodeWarning extends SyntaxWarning {

public UselessCodeWarning(String message, ASTNode node) {
super(message, node);
}
}

}