From 70d61653c5012736fd75a3b6eb84d9099f6c20c9 Mon Sep 17 00:00:00 2001 From: Ben Sherman Date: Thu, 29 Aug 2024 17:50:22 -0500 Subject: [PATCH] Detect useless code Signed-off-by: Ben Sherman --- .../lsp/services/script/ScriptAstCache.groovy | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/main/groovy/nextflow/lsp/services/script/ScriptAstCache.groovy b/src/main/groovy/nextflow/lsp/services/script/ScriptAstCache.groovy index efa018a..3cd2812 100644 --- a/src/main/groovy/nextflow/lsp/services/script/ScriptAstCache.groovy +++ b/src/main/groovy/nextflow/lsp/services/script/ScriptAstCache.groovy @@ -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 @@ -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 } @@ -248,4 +268,72 @@ class ScriptAstCache extends ASTNodeCache { } } + private static class UselessCodeVisitor extends ClassCodeVisitorSupport implements ScriptVisitor { + + private SourceUnit sourceUnit + + private List 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 getErrors() { + return errors + } + + } + + private static class UselessCodeWarning extends SyntaxWarning { + + public UselessCodeWarning(String message, ASTNode node) { + super(message, node); + } + } + }