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

Add Lint/UnusedGenericOrUnion #537

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
80 changes: 80 additions & 0 deletions spec/ameba/rule/lint/unused_type_or_constant_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
require "../../../spec_helper"

module Ameba::Rule::Lint
subject = UnusedTypeOrConstant.new

describe UnusedTypeOrConstant do
it "passes if paths and generic types are used" do
expect_no_issues subject, <<-CRYSTAL
MyConst = 1

my_var : String? = EMPTY_STRING

my_var.as(String)

puts StaticArray(Int32, 10)

class MyClass < MySuperClass
include MyModule
extend MyModule
end

a : Int32 = 10

klass = String?

alias MyType = Float64 | StaticArray(Float64, 10)

def size : Float64
0.1
end

lib MyLib
type MyType = Void*

struct MyStruct
field1, field2 : Float64
end
end

fun fun_name : Int32
1234
end

Int32 | "Float64"

MyClass.run
CRYSTAL
end
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved

it "fails if paths and generic types are top-level" do
expect_issue subject, <<-CRYSTAL
Int32
# ^^^ error: Path or generic type is not used
String?
# ^^^^^ error: Path or generic type is not used
Int32 | Float64 | Nil
# ^^^^^^^^^^^^^^^^^^^ error: Path or generic type is not used
StaticArray(Int32, 10)
# ^^^^^^^^^^^^^^^^^^^^ error: Path or generic type is not used

def hello
Float64
# ^^^^^^^ error: Path or generic type is not used
0.1
end

fun fun_name : Int32
Int32
# ^^^^^ error: Path or generic type is not used
1234
end

class MyClass
MyModule
# ^^^^^^^^ error: Path or generic type is not used
end
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
CRYSTAL
end
end
end
74 changes: 67 additions & 7 deletions src/ameba/ast/visitors/implicit_return_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,8 @@ module Ameba::AST
def visit(node : Crystal::Call) : Bool
@rule.test(@source, node, @stack > 0)

if node.block || !node.args.empty?
incr_stack { node.obj.try &.accept(self) }
else
node.obj.try &.accept(self)
end

incr_stack do
node.obj.try &.accept(self)
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved
node.args.each &.accept(self)
node.named_args.try &.each &.accept(self)
node.block_arg.try &.accept(self)
Expand Down Expand Up @@ -133,6 +128,48 @@ module Ameba::AST
false
end

def visit(node : Crystal::ClassDef) : Bool
incr_stack do
node.name.accept(self)
node.superclass.try &.accept(self)
end

node.body.accept(self)

false
end

def visit(node : Crystal::FunDef) : Bool
incr_stack do
node.args.each &.accept(self)
node.return_type.try &.accept(self)
node.body.try &.accept(self)
end

false
end

def visit(node : Crystal::ExternalVar) : Bool
incr_stack { node.type_spec.accept(self) }

false
end

def visit(node : Crystal::Include | Crystal::Extend) : Bool
incr_stack do
node.name.accept(self)
end

false
end

def visit(node : Crystal::Cast | Crystal::NilableCast) : Bool
node.obj.accept(self)
incr_stack { node.to.accept(self) }

false
end

def visit(node : Crystal::Annotation) : Bool
@rule.test(@source, node, @stack > 0)

Expand Down Expand Up @@ -282,7 +319,30 @@ module Ameba::AST
false
end

def visit(node : Crystal::Generic) : Bool
def visit(node : Crystal::Generic | Crystal::Path | Crystal::Union) : Bool
@rule.test(@source, node, @stack > 0)

false
end

def visit(node : Crystal::Alias)
@rule.test(@source, node, @stack > 0)

incr_stack do
node.name.accept(self)
node.value.accept(self)
end

false
end
nobodywasishere marked this conversation as resolved.
Show resolved Hide resolved

def visit(node : Crystal::TypeDef)
@rule.test(@source, node, @stack > 0)

incr_stack do
node.type_spec.accept(self)
end

false
end

Expand Down
85 changes: 85 additions & 0 deletions src/ameba/rule/lint/unused_type_or_constant.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module Ameba::Rule::Lint
# A rule that disallows unused constants, generics, or unions (`Int32`, `String?`, `StaticArray(Int32, 10)`, etc).
#
# For example, these are considered invalid:
#
# ```
# Int32
#
# String?
#
# Float64 | StaticArray(Float64, 10)
#
# def size
# Float64
# 0.1
# end
# ```
#
# And these are considered valid:
#
# ```
# a : Int32 = 10
#
# klass = String?
#
# alias MyType = Float64 | StaticArray(Float64, 10)
#
# def size : Float64
# 0.1
# end
# ```
#
# YAML configuration example:
#
# ```
# Lint/UnusedTypeOrConstant:
# Enabled: true
# ```
class UnusedTypeOrConstant < Base
properties do
since_version "1.7.0"
description "Disallows unused literal values"
end

MSG = "Path or generic type is not used"

def test(source : Source)
AST::ImplicitReturnVisitor.new(self, source)
end

def test(source, node : Crystal::Call, last_is_used : Bool)
return if last_is_used || !path_or_generic_union?(node)

issue_for node, MSG
end

def test(source, node : Crystal::Path | Crystal::Generic | Crystal::Union, last_is_used : Bool)
issue_for node, MSG unless last_is_used
end

def path_or_generic_union?(node : Crystal::Call) : Bool
return false unless node.name == "|" && node.args.size == 1

case lhs = node.obj
when Crystal::Path, Crystal::Generic
# Okay
when Crystal::Call
return false unless path_or_generic_union?(lhs)
else
return false
end

case rhs = node.args.first?
when Crystal::Path, Crystal::Generic
# Okay
when Crystal::Call
return false unless path_or_generic_union?(rhs)
else
return false
end

true
end
end
end
Loading