Merge pull request #434 from crystal-ameba/misc-refactors

v1.6.1
This commit is contained in:
Sijawusz Pur Rahnama 2024-01-09 21:12:19 +01:00 committed by GitHub
commit b6bd74e02f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 100 additions and 88 deletions

View file

@ -1,5 +1,5 @@
name: ameba name: ameba
version: 1.6.0 version: 1.6.1
authors: authors:
- Vitalii Elenhaupt <velenhaupt@gmail.com> - Vitalii Elenhaupt <velenhaupt@gmail.com>

View file

@ -57,13 +57,15 @@ module Ameba::AST
end end
end end
CRYSTAL CRYSTAL
scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope) scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope) variable = Variable.new(var_node, scope)
variable.reference nodes.var_nodes.first, scope.inner_scopes.first variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
scope.references?(variable).should be_true scope.references?(variable).should be_true
end end
@ -77,13 +79,15 @@ module Ameba::AST
end end
end end
CRYSTAL CRYSTAL
scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope) scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope) variable = Variable.new(var_node, scope)
variable.reference nodes.var_nodes.first, scope.inner_scopes.first variable.reference(nodes.var_nodes.first, scope.inner_scopes.first)
scope.references?(variable, check_inner_scopes: false).should be_false scope.references?(variable, check_inner_scopes: false).should be_false
end end
@ -98,9 +102,11 @@ module Ameba::AST
end end
end end
CRYSTAL CRYSTAL
scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node
scope = Scope.new nodes.def_nodes.first
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope) scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope) variable = Variable.new(var_node, scope)
@ -120,7 +126,7 @@ module Ameba::AST
describe "#find_variable" do describe "#find_variable" do
it "returns the variable in the scope by name" do it "returns the variable in the scope by name" do
scope = Scope.new as_node("foo = 1") scope = Scope.new as_node("foo = 1")
scope.add_variable Crystal::Var.new "foo" scope.add_variable(Crystal::Var.new "foo")
scope.find_variable("foo").should_not be_nil scope.find_variable("foo").should_not be_nil
end end
@ -133,7 +139,7 @@ module Ameba::AST
describe "#assign_variable" do describe "#assign_variable" do
it "creates a new assignment" do it "creates a new assignment" do
scope = Scope.new as_node("foo = 1") scope = Scope.new as_node("foo = 1")
scope.add_variable Crystal::Var.new "foo" scope.add_variable(Crystal::Var.new "foo")
scope.assign_variable("foo", Crystal::Var.new "foo") scope.assign_variable("foo", Crystal::Var.new "foo")
var = scope.find_variable("foo").should_not be_nil var = scope.find_variable("foo").should_not be_nil
var.assignments.size.should eq 1 var.assignments.size.should eq 1
@ -141,7 +147,7 @@ module Ameba::AST
it "does not create the assignment if variable is wrong" do it "does not create the assignment if variable is wrong" do
scope = Scope.new as_node("foo = 1") scope = Scope.new as_node("foo = 1")
scope.add_variable Crystal::Var.new "foo" scope.add_variable(Crystal::Var.new "foo")
scope.assign_variable("bar", Crystal::Var.new "bar") scope.assign_variable("bar", Crystal::Var.new "bar")
var = scope.find_variable("foo").should_not be_nil var = scope.find_variable("foo").should_not be_nil
var.assignments.size.should eq 0 var.assignments.size.should eq 0

View file

@ -85,13 +85,16 @@ module Ameba::AST
3.times { |i| a = a + i } 3.times { |i| a = a + i }
end end
CRYSTAL CRYSTAL
scope = Scope.new nodes.def_nodes.first
var_node = nodes.var_nodes.first var_node = nodes.var_nodes.first
scope.add_variable var_node
scope = Scope.new(nodes.def_nodes.first)
scope.add_variable(var_node)
scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope) scope.inner_scopes << Scope.new(nodes.block_nodes.first, scope)
variable = Variable.new(var_node, scope) variable = Variable.new(var_node, scope)
variable.reference nodes.var_nodes.last, scope.inner_scopes.last variable.reference(nodes.var_nodes.last, scope.inner_scopes.last)
variable.captured_by_block?.should be_truthy variable.captured_by_block?.should be_truthy
end end
@ -101,8 +104,10 @@ module Ameba::AST
a = 1 a = 1
end end
CRYSTAL CRYSTAL
scope.add_variable Crystal::Var.new "a"
scope.add_variable(Crystal::Var.new "a")
variable = scope.variables.first variable = scope.variables.first
variable.captured_by_block?.should be_falsey variable.captured_by_block?.should be_falsey
end end
end end

View file

@ -85,7 +85,7 @@ module Ameba
end end
it "raises when custom config file doesn't exist" do it "raises when custom config file doesn't exist" do
expect_raises(Exception, "Unable to load config file: Config file does not exist foo.yml") do expect_raises(Exception, "Unable to load config file: Config file does not exist") do
Config.load "foo.yml" Config.load "foo.yml"
end end
end end

View file

@ -10,6 +10,8 @@ module Ameba::Rule::Lint
["foo"] === [foo] ["foo"] === [foo]
"foo" == foo "foo" == foo
"foo" != foo "foo" != foo
"foo" == FOO
FOO == "foo"
foo == "foo" foo == "foo"
foo != "foo" foo != "foo"
CRYSTAL CRYSTAL

View file

@ -34,9 +34,8 @@ module Ameba::AST
# The actual AST node that represents a current scope. # The actual AST node that represents a current scope.
getter node : Crystal::ASTNode getter node : Crystal::ASTNode
delegate to_s, to: node delegate location, end_location, to_s,
delegate location, to: node to: @node
delegate end_location, to: node
def_equals_and_hash node, location def_equals_and_hash node, location

View file

@ -19,9 +19,8 @@ module Ameba::AST
# Variable of this argument (may be the same node) # Variable of this argument (may be the same node)
getter variable : Variable getter variable : Variable
delegate location, to: @node delegate location, end_location, to_s,
delegate end_location, to: @node to: @node
delegate to_s, to: @node
# Creates a new argument. # Creates a new argument.
# #

View file

@ -19,9 +19,8 @@ module Ameba::AST
# A scope assignment belongs to # A scope assignment belongs to
getter scope : Scope getter scope : Scope
delegate to_s, to: @node delegate location, end_location, to_s,
delegate location, to: @node to: @node
delegate end_location, to: @node
# Creates a new assignment. # Creates a new assignment.
# #

View file

@ -2,10 +2,8 @@ module Ameba::AST
class InstanceVariable class InstanceVariable
getter node : Crystal::InstanceVar getter node : Crystal::InstanceVar
delegate location, to: @node delegate location, end_location, name, to_s,
delegate end_location, to: @node to: @node
delegate name, to: @node
delegate to_s, to: @node
def initialize(@node) def initialize(@node)
end end

View file

@ -2,9 +2,8 @@ module Ameba::AST
class TypeDecVariable class TypeDecVariable
getter node : Crystal::TypeDeclaration getter node : Crystal::TypeDeclaration
delegate location, to: @node delegate location, end_location, to_s,
delegate end_location, to: @node to: @node
delegate to_s, to: @node
def initialize(@node) def initialize(@node)
end end

View file

@ -17,10 +17,8 @@ module Ameba::AST
# Node of the first assignment which can be available before any reference. # Node of the first assignment which can be available before any reference.
getter assign_before_reference : Crystal::ASTNode? getter assign_before_reference : Crystal::ASTNode?
delegate location, to: @node delegate location, end_location, name, to_s,
delegate end_location, to: @node to: @node
delegate name, to: @node
delegate to_s, to: @node
# Creates a new variable(in the scope). # Creates a new variable(in the scope).
# #
@ -54,7 +52,7 @@ module Ameba::AST
# #
# ``` # ```
# variable = Variable.new(node, scope) # variable = Variable.new(node, scope)
# variable.reference(var_node) # variable.reference(var_node, some_scope)
# variable.referenced? # => true # variable.referenced? # => true
# ``` # ```
def referenced? def referenced?
@ -74,6 +72,11 @@ module Ameba::AST
end end
end end
# :ditto:
def reference(scope : Scope)
reference(node, scope)
end
# Reference variable's assignments. # Reference variable's assignments.
# #
# ``` # ```
@ -208,9 +211,9 @@ module Ameba::AST
return if references.size > assignments.size return if references.size > assignments.size
return if assignments.any?(&.op_assign?) return if assignments.any?(&.op_assign?)
@assign_before_reference = assignments.find { |ass| @assign_before_reference = assignments
!ass.in_branch? .find(&.in_branch?.!)
}.try &.node .try(&.node)
end end
end end
end end

View file

@ -153,7 +153,7 @@ module Ameba::AST
# :nodoc: # :nodoc:
def visit(node : Crystal::Var) def visit(node : Crystal::Var)
variable = @current_scope.find_variable node.name variable = @current_scope.find_variable(node.name)
case case
when @current_scope.arg?(node) # node is an argument when @current_scope.arg?(node) # node is an argument
@ -161,7 +161,7 @@ module Ameba::AST
when variable.nil? && @current_assign # node is a variable when variable.nil? && @current_assign # node is a variable
@current_scope.add_variable(node) @current_scope.add_variable(node)
when variable # node is a reference when variable # node is a reference
reference = variable.reference node, @current_scope reference = variable.reference(node, @current_scope)
if @current_assign.is_a?(Crystal::OpAssign) || !reference.target_of?(@current_assign) if @current_assign.is_a?(Crystal::OpAssign) || !reference.target_of?(@current_assign)
variable.reference_assignments! variable.reference_assignments!
end end
@ -178,9 +178,7 @@ module Ameba::AST
when scope.type_definition? && accessor_macro?(node) then return false when scope.type_definition? && accessor_macro?(node) then return false
when scope.def? && special_node?(node) when scope.def? && special_node?(node)
scope.arguments.each do |arg| scope.arguments.each do |arg|
variable = arg.variable ref = arg.variable.reference(scope)
ref = variable.reference(variable.node, scope)
ref.explicit = false ref.explicit = false
end end
end end

View file

@ -97,8 +97,9 @@ class Ameba::Config
@excluded = load_array_section(config, "Excluded") @excluded = load_array_section(config, "Excluded")
@globs = load_array_section(config, "Globs", DEFAULT_GLOBS) @globs = load_array_section(config, "Globs", DEFAULT_GLOBS)
return unless formatter_name = load_formatter_name(config) if formatter_name = load_formatter_name(config)
self.formatter = formatter_name self.formatter = formatter_name
end
end end
# Loads YAML configuration file by `path`. # Loads YAML configuration file by `path`.
@ -120,8 +121,8 @@ class Ameba::Config
protected def self.read_config(path = nil) protected def self.read_config(path = nil)
if path if path
raise ArgumentError.new("Config file does not exist #{path}") unless File.exists?(path) return File.read(path) if File.exists?(path)
return File.read(path) raise "Config file does not exist"
end end
each_config_path do |config_path| each_config_path do |config_path|
return File.read(config_path) if File.exists?(config_path) return File.read(config_path) if File.exists?(config_path)

View file

@ -17,13 +17,13 @@ module Ameba::Formatter
# A list of sources to inspect is passed as an argument. # A list of sources to inspect is passed as an argument.
def started(sources); end def started(sources); end
# Callback that indicates when source inspection is finished. # Callback that indicates when source inspection is started.
# A corresponding source is passed as an argument. # A corresponding source is passed as an argument.
def source_finished(source : Source); end def source_started(source : Source); end
# Callback that indicates when source inspection is finished. # Callback that indicates when source inspection is finished.
# A corresponding source is passed as an argument. # A corresponding source is passed as an argument.
def source_started(source : Source); end def source_finished(source : Source); end
# Callback that indicates when inspection is finished. # Callback that indicates when inspection is finished.
# A list of inspected sources is passed as an argument. # A list of inspected sources is passed as an argument.

View file

@ -26,16 +26,21 @@ module Ameba::Formatter
end end
private def generate_todo_config(issues) private def generate_todo_config(issues)
file = File.new(@config_path, mode: "w") File.open(@config_path, mode: "w") do |file|
file << header file << header
rule_issues_map(issues).each do |rule, rule_issues|
file << "\n# Problems found: #{rule_issues.size}" rule_issues_map(issues).each do |rule, rule_issues|
file << "\n# Run `ameba --only #{rule.name}` for details" rule_todo = rule_todo(rule, rule_issues)
file << rule_todo(rule, rule_issues).gsub("---", "") rule_todo =
{rule_todo.name => rule_todo}
.to_yaml.gsub("---", "")
file << "\n# Problems found: #{rule_issues.size}"
file << "\n# Run `ameba --only #{rule.name}` for details"
file << rule_todo
end
file
end end
file
ensure
file.close if file
end end
private def rule_issues_map(issues) private def rule_issues_map(issues)
@ -60,11 +65,11 @@ module Ameba::Formatter
end end
private def rule_todo(rule, issues) private def rule_todo(rule, issues)
rule.excluded = issues rule.dup.tap do |rule_todo|
.compact_map(&.location.try &.filename.try &.to_s) rule_todo.excluded = issues
.uniq! .compact_map(&.location.try &.filename.try &.to_s)
.uniq!
{rule.name => rule}.to_yaml end
end end
end end
end end

View file

@ -32,14 +32,15 @@ module Ameba::Rule
# This method is designed to test the source passed in. If source has issues # This method is designed to test the source passed in. If source has issues
# that are tested by this rule, it should add an issue. # that are tested by this rule, it should add an issue.
# #
# Be default it uses a node visitor to traverse all the nodes in the source. # By default it uses a node visitor to traverse all the nodes in the source.
#
# NOTE: Must be overridden for other type of rules. # NOTE: Must be overridden for other type of rules.
def test(source : Source) def test(source : Source)
AST::NodeVisitor.new self, source AST::NodeVisitor.new self, source
end end
# NOTE: Can't be abstract
def test(source : Source, node : Crystal::ASTNode, *opts) def test(source : Source, node : Crystal::ASTNode, *opts)
# can't be abstract
end end
# A convenient addition to `#test` method that does the same # A convenient addition to `#test` method that does the same

View file

@ -41,7 +41,7 @@ module Ameba::Rule::Naming
end end
private def valid_name?(name) private def valid_name?(name)
return true if name.blank? # happens with compound names like `(arg1, arg2)` return true if name.blank? # TODO: handle unpacked variables
return true if name.in?(allowed_names) return true if name.in?(allowed_names)
return false if name.in?(forbidden_names) return false if name.in?(forbidden_names)

View file

@ -110,23 +110,22 @@ module Ameba::Rule::Style
i i
end end
protected def args_to_s(io : IO, node : Crystal::Call, short_block = nil, skip_last_arg = false) protected def args_to_s(io : IO, node : Crystal::Call, short_block = nil, skip_last_arg = false) : Nil
node.args.dup.tap do |args| args = node.args.dup
args.pop? if skip_last_arg args.pop? if skip_last_arg
args.join io, ", " args.join io, ", "
named_args = node.named_args named_args = node.named_args
if named_args if named_args
io << ", " unless args.empty? || named_args.empty? io << ", " unless args.empty? || named_args.empty?
named_args.join io, ", " do |arg, inner_io| named_args.join io, ", " do |arg, inner_io|
inner_io << arg.name << ": " << arg.value inner_io << arg.name << ": " << arg.value
end
end end
end
if short_block if short_block
io << ", " unless args.empty? && (named_args.nil? || named_args.empty?) io << ", " unless args.empty? && (named_args.nil? || named_args.empty?)
io << short_block io << short_block
end
end end
end end
@ -164,9 +163,7 @@ module Ameba::Rule::Style
return unless block_end_location = block.body.end_location return unless block_end_location = block.body.end_location
block_code = source_between(block_location, block_end_location, source.lines) block_code = source_between(block_location, block_end_location, source.lines)
return unless block_code.try(&.starts_with?("&.")) block_code if block_code.try(&.starts_with?("&."))
block_code
end end
protected def call_code(source, call, body) protected def call_code(source, call, body)
@ -230,7 +227,7 @@ module Ameba::Rule::Style
# we filter out the blocks that are of call type - `i.to_i64.odd?` # we filter out the blocks that are of call type - `i.to_i64.odd?`
return unless (body = block.body).is_a?(Crystal::Call) return unless (body = block.body).is_a?(Crystal::Call)
# we need to "unwind" the chain calls, so the final receiver object # we need to "unwind" the call chain, so the final receiver object
# ends up being a variable - `i` # ends up being a variable - `i`
obj = body.obj obj = body.obj
while obj.is_a?(Crystal::Call) while obj.is_a?(Crystal::Call)