Source#content -> Source#code, Source::Error#pos -> Source::Error#location

This commit is contained in:
Vitalii Elenhaupt 2017-11-07 22:02:51 +02:00
parent 9036a7ca71
commit e718c90f16
No known key found for this signature in database
GPG key ID: 7558EF3A4056C706
39 changed files with 91 additions and 90 deletions

View file

@ -84,7 +84,7 @@ struct DebuggerStatement < Rule
# to remove a debugger statement. # to remove a debugger statement.
return unless node.name == "debugger" && node.args.empty? && node.obj.nil? return unless node.name == "debugger" && node.args.empty? && node.obj.nil?
source.error self, node.location.try &.line_number, source.error self, node.location,
"Possible forgotten debugger statement detected" "Possible forgotten debugger statement detected"
end end
end end

View file

@ -20,7 +20,7 @@ module Ameba::Formatter
it "writes invalid source" do it "writes invalid source" do
s = Source.new "" s = Source.new ""
s.error DummyRule.new, 3, "message" s.error DummyRule.new, nil, "message"
subject.source_finished s subject.source_finished s
output.to_s.should contain "F" output.to_s.should contain "F"
end end

View file

@ -63,12 +63,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
source = Source.new "a != true" source = Source.new "a != true", "source.cr"
subject.catch(source) subject.catch(source)
error = source.errors.first error = source.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Comparison to a boolean is pointless" error.message.should eq "Comparison to a boolean is pointless"
end end
end end
@ -100,12 +100,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
source = Source.new "true != a" source = Source.new "true != a", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first error = source.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Comparison to a boolean is pointless" error.message.should eq "Comparison to a boolean is pointless"
end end
end end

View file

@ -3,9 +3,9 @@ require "../../spec_helper"
module Ameba module Ameba
subject = Rules::ConstantNames.new subject = Rules::ConstantNames.new
private def it_reports_constant(content, expected) private def it_reports_constant(code, expected)
it "reports constant name #{expected}" do it "reports constant name #{expected}" do
s = Source.new content s = Source.new code
Rules::ConstantNames.new.catch(s).should_not be_valid Rules::ConstantNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
@ -38,11 +38,11 @@ module Ameba
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new %( s = Source.new %(
Const = 1 Const = 1
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq( error.message.should eq(
"Constant name should be screaming-cased: CONST, not Const" "Constant name should be screaming-cased: CONST, not Const"
) )

View file

@ -32,12 +32,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new "debugger" s = Source.new "debugger", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Possible forgotten debugger statement detected" error.message.should eq "Possible forgotten debugger statement detected"
end end
end end

View file

@ -70,15 +70,15 @@ module Ameba
end end
) )
it "reports rule, pos and message" do it "reports rule, location and message" do
s = Source.new %( s = Source.new %(
if () if ()
end end
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:12"
error.message.should eq "Avoid empty expression '()'" error.message.should eq "Avoid empty expression '()'"
end end
end end

View file

@ -107,11 +107,11 @@ module Ameba
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new %q( s = Source.new %q(
1200000 1200000
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:10"
error.message.should match /1_200_000/ error.message.should match /1_200_000/
end end
end end

View file

@ -21,13 +21,13 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
source = Source.new long_line source = Source.new long_line, "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first error = source.errors.first
error.rule.should eq subject error.rule.should eq subject
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:81"
error.message.should eq "Line too long (81 symbols)" error.message.should eq "Line too long"
end end
end end
end end

View file

@ -59,13 +59,13 @@ module Ameba::Rules
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new %( s = Source.new %(
puts "hello" if true puts "hello" if true
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
s.errors.size.should eq 1 s.errors.size.should eq 1
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Literal value found in conditional" error.message.should eq "Literal value found in conditional"
end end
end end

View file

@ -29,12 +29,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new %q("#{4}") s = Source.new %q("#{4}"), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Literal value found in interpolation" error.message.should eq "Literal value found in interpolation"
end end
end end

View file

@ -3,9 +3,9 @@ require "../../spec_helper"
module Ameba module Ameba
subject = Rules::MethodNames.new subject = Rules::MethodNames.new
private def it_reports_method_name(content, expected) private def it_reports_method_name(code, expected)
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new content s = Source.new code
Rules::MethodNames.new.catch(s).should_not be_valid Rules::MethodNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
@ -42,11 +42,11 @@ module Ameba
s = Source.new %( s = Source.new %(
def bad_Name def bad_Name
end end
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq( error.message.should eq(
"Method name should be underscore-cased: bad_name, not bad_Name" "Method name should be underscore-cased: bad_name, not bad_Name"
) )

View file

@ -56,12 +56,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new ":nok unless !s.empty?" s = Source.new ":nok unless !s.empty?", "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 1 error.location.to_s.should eq "source.cr:1:1"
error.message.should eq "Avoid negated conditions in unless blocks" error.message.should eq "Avoid negated conditions in unless blocks"
end end
end end

View file

@ -32,12 +32,12 @@ module Ameba::Rules
true true
end end
end end
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 3 error.location.to_s.should eq "source.cr:3:11"
error.message.should eq( error.message.should eq(
"Favour method name 'picture?' over 'has_picture?'") "Favour method name 'picture?' over 'has_picture?'")
end end

View file

@ -25,12 +25,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
source = Source.new "a = 1\n\n " source = Source.new "a = 1\n\n ", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first error = source.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 3 error.location.to_s.should eq "source.cr:3:1"
error.message.should eq "Blank lines detected at the end of the file" error.message.should eq "Blank lines detected at the end of the file"
end end
end end

View file

@ -15,12 +15,12 @@ module Ameba::Rules
end end
it "reports rule, pos and message" do it "reports rule, pos and message" do
source = Source.new "a = 1\n b = 2 " source = Source.new "a = 1\n b = 2 ", "source.cr"
subject.catch(source).should_not be_valid subject.catch(source).should_not be_valid
error = source.errors.first error = source.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:7"
error.message.should eq "Trailing whitespace detected" error.message.should eq "Trailing whitespace detected"
end end
end end

View file

@ -3,9 +3,9 @@ require "../../spec_helper"
module Ameba module Ameba
subject = Rules::TypeNames.new subject = Rules::TypeNames.new
private def it_reports_name(content, expected) private def it_reports_name(code, expected)
it "reports type name #{expected}" do it "reports type name #{expected}" do
s = Source.new content s = Source.new code
Rules::TypeNames.new.catch(s).should_not be_valid Rules::TypeNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
@ -47,11 +47,11 @@ module Ameba
s = Source.new %( s = Source.new %(
class My_class class My_class
end end
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq( error.message.should eq(
"Type name should be camelcased: MyClass, but it was My_class" "Type name should be camelcased: MyClass, but it was My_class"
) )

View file

@ -31,13 +31,13 @@ module Ameba::Rules
else else
:two :two
end end
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.should_not be_nil error.should_not be_nil
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq "Favour if over unless with else" error.message.should eq "Favour if over unless with else"
end end
end end

View file

@ -3,9 +3,9 @@ require "../../spec_helper"
module Ameba module Ameba
subject = Rules::VariableNames.new subject = Rules::VariableNames.new
private def it_reports_var_name(content, expected) private def it_reports_var_name(code, expected)
it "reports method name #{expected}" do it "reports method name #{expected}" do
s = Source.new content s = Source.new code
Rules::VariableNames.new.catch(s).should_not be_valid Rules::VariableNames.new.catch(s).should_not be_valid
s.errors.first.message.should contain expected s.errors.first.message.should contain expected
end end
@ -48,11 +48,11 @@ module Ameba
it "reports rule, pos and message" do it "reports rule, pos and message" do
s = Source.new %( s = Source.new %(
badName = "Yeah" badName = "Yeah"
) ), "source.cr"
subject.catch(s).should_not be_valid subject.catch(s).should_not be_valid
error = s.errors.first error = s.errors.first
error.rule.should_not be_nil error.rule.should_not be_nil
error.pos.should eq 2 error.location.to_s.should eq "source.cr:2:9"
error.message.should eq( error.message.should eq(
"Var name should be underscore-cased: bad_name, not badName" "Var name should be underscore-cased: bad_name, not badName"
) )

View file

@ -3,22 +3,24 @@ require "../spec_helper"
module Ameba module Ameba
describe Source do describe Source do
describe ".new" do describe ".new" do
it "allows to create a source by content and path" do it "allows to create a source by code and path" do
s = Source.new("content", "path") s = Source.new("code", "path")
s.path.should eq "path" s.path.should eq "path"
s.content.should eq "content" s.code.should eq "code"
s.lines.should eq ["content"] s.lines.should eq ["code"]
end end
end end
describe "#error" do describe "#error" do
it "adds and error" do it "adds and error" do
s = Source.new "" s = Source.new "", "source.cr"
s.error(DummyRule.new, 23, "Error!") s.error(DummyRule.new, s.location(23, 2), "Error!")
s.errors.size.should eq 1 s.should_not be_valid
s.errors.first.rule.should_not be_nil
s.errors.first.pos.should eq 23 error = s.errors.first
s.errors.first.message.should eq "Error!" error.rule.should_not be_nil
error.location.to_s.should eq "source.cr:23:2"
error.message.should eq "Error!"
end end
end end
end end

View file

@ -16,7 +16,7 @@ module Ameba
String.build do |str| String.build do |str|
str << "Source expected to be valid, but there are errors:\n\n" str << "Source expected to be valid, but there are errors:\n\n"
source.errors.each do |e| source.errors.each do |e|
str << " * #{e.rule.name}:#{e.pos} #{e.message}\n" str << " * #{e.rule.name}: #{e.message}\n"
end end
end end
end end

View file

@ -20,7 +20,7 @@ module Ameba::Formatter
failed_sources.each do |source| failed_sources.each do |source|
source.errors.each do |error| source.errors.each do |error|
output << "#{source.path}:#{error.pos}\n".colorize(:cyan) output << "#{error.location}\n".colorize(:cyan)
output << "#{error.rule.name}: #{error.message}\n\n".colorize(:red) output << "#{error.rule.name}: #{error.message}\n\n".colorize(:red)
end end
end end

View file

@ -22,8 +22,7 @@ module Ameba::Rules
node.args.first?.try &.is_a?(Crystal::BoolLiteral) || node.args.first?.try &.is_a?(Crystal::BoolLiteral) ||
node.obj.is_a?(Crystal::BoolLiteral) node.obj.is_a?(Crystal::BoolLiteral)
) )
source.error self, node.location.try &.line_number, source.error self, node.location, "Comparison to a boolean is pointless"
"Comparison to a boolean is pointless"
end end
end end
end end

View file

@ -25,7 +25,7 @@ module Ameba::Rules
name = target.names.first name = target.names.first
return if (expected = name.upcase) == name return if (expected = name.upcase) == name
source.error self, node.location.try &.line_number, source.error self, node.location,
"Constant name should be screaming-cased: #{expected}, not #{name}" "Constant name should be screaming-cased: #{expected}, not #{name}"
end end
end end

View file

@ -14,7 +14,7 @@ module Ameba::Rules
node.args.empty? && node.args.empty? &&
node.obj.nil? node.obj.nil?
source.error self, node.location.try &.line_number, source.error self, node.location,
"Possible forgotten debugger statement detected" "Possible forgotten debugger statement detected"
end end
end end

View file

@ -33,8 +33,7 @@ module Ameba::Rules
return if exp.nil? || exp == "nil" return if exp.nil? || exp == "nil"
source.error self, node.location.try &.line_number, source.error self, node.location, "Avoid empty expression '#{exp}'"
"Avoid empty expression '#{exp}'"
end end
end end
end end

View file

@ -25,7 +25,7 @@ module Ameba::Rules
next unless token.type == :NUMBER && decimal?(token.raw) next unless token.type == :NUMBER && decimal?(token.raw)
if (expected = underscored token.raw) != token.raw if (expected = underscored token.raw) != token.raw
source.error self, token.line_number, source.error self, token.location,
"Large numbers should be written with underscores: #{expected}" "Large numbers should be written with underscores: #{expected}"
end end
end end

View file

@ -5,8 +5,9 @@ module Ameba::Rules
def test(source) def test(source)
source.lines.each_with_index do |line, index| source.lines.each_with_index do |line, index|
next unless line.size > 80 next unless line.size > 80
source.error self, index + 1,
"Line too long (#{line.size} symbols)" source.error self, source.location(index + 1, line.size),
"Line too long"
end end
end end
end end

View file

@ -22,8 +22,7 @@ module Ameba::Rules
def check_node(source, node) def check_node(source, node)
return unless literal?(node.cond) return unless literal?(node.cond)
source.error self, node.location.try &.line_number, source.error self, node.location, "Literal value found in conditional"
"Literal value found in conditional"
end end
def test(source, node : Crystal::If) def test(source, node : Crystal::If)

View file

@ -19,8 +19,7 @@ module Ameba::Rules
def test(source, node : Crystal::StringInterpolation) def test(source, node : Crystal::StringInterpolation)
found = node.expressions.any? { |e| !string_literal?(e) && literal?(e) } found = node.expressions.any? { |e| !string_literal?(e) && literal?(e) }
return unless found return unless found
source.error self, node.location.try &.line_number, source.error self, node.location, "Literal value found in interpolation"
"Literal value found in interpolation"
end end
end end
end end

View file

@ -38,7 +38,7 @@ module Ameba::Rules
def test(source, node : Crystal::Def) def test(source, node : Crystal::Def)
return if (expected = node.name.underscore) == node.name return if (expected = node.name.underscore) == node.name
source.error self, node.location.try &.line_number, source.error self, node.location,
"Method name should be underscore-cased: #{expected}, not #{node.name}" "Method name should be underscore-cased: #{expected}, not #{node.name}"
end end
end end

View file

@ -27,7 +27,7 @@ module Ameba::Rules
def test(source, node : Crystal::Unless) def test(source, node : Crystal::Unless)
return unless negated_condition? node.cond return unless negated_condition? node.cond
source.error self, node.location.try &.line_number, source.error self, node.location,
"Avoid negated conditions in unless blocks" "Avoid negated conditions in unless blocks"
end end

View file

@ -29,7 +29,7 @@ module Ameba::Rules
def test(source, node : Crystal::Def) def test(source, node : Crystal::Def)
if node.name =~ /(is|has)_(\w+)\?/ if node.name =~ /(is|has)_(\w+)\?/
source.error self, node.location.try &.line_number, source.error self, node.location,
"Favour method name '#{$2}?' over '#{node.name}'" "Favour method name '#{$2}?' over '#{node.name}'"
end end
end end

View file

@ -4,7 +4,7 @@ module Ameba::Rules
struct TrailingBlankLines < Rule struct TrailingBlankLines < Rule
def test(source) def test(source)
if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty? if source.lines.size > 1 && source.lines[-2, 2].join.strip.empty?
source.error self, source.lines.size, source.error self, source.location(source.lines.size, 1),
"Blank lines detected at the end of the file" "Blank lines detected at the end of the file"
end end
end end

View file

@ -5,7 +5,7 @@ module Ameba::Rules
def test(source) def test(source)
source.lines.each_with_index do |line, index| source.lines.each_with_index do |line, index|
next unless line =~ /\s$/ next unless line =~ /\s$/
source.error self, index + 1, source.error self, source.location(index + 1, line.size),
"Trailing whitespace detected" "Trailing whitespace detected"
end end
end end

View file

@ -55,7 +55,7 @@ module Ameba::Rules
expected = name.camelcase expected = name.camelcase
return if expected == name return if expected == name
source.error self, node.location.try &.line_number, source.error self, node.location,
"Type name should be camelcased: #{expected}, but it was #{name}" "Type name should be camelcased: #{expected}, but it was #{name}"
end end

View file

@ -42,10 +42,8 @@ module Ameba::Rules
end end
def test(source, node : Crystal::Unless) def test(source, node : Crystal::Unless)
unless node.else.is_a?(Crystal::Nop) return if node.else.nop?
source.error self, node.location.try &.line_number, source.error self, node.location, "Favour if over unless with else"
"Favour if over unless with else"
end
end end
end end
end end

View file

@ -32,7 +32,7 @@ module Ameba::Rules
private def check_node(source, node) private def check_node(source, node)
return if (expected = node.name.underscore) == node.name return if (expected = node.name.underscore) == node.name
source.error self, node.location.try &.line_number, source.error self, node.location,
"Var name should be underscore-cased: #{expected}, not #{node.name}" "Var name should be underscore-cased: #{expected}, not #{node.name}"
end end

View file

@ -8,20 +8,20 @@ module Ameba
# position of the error and a message. # position of the error and a message.
record Error, record Error,
rule : Rule, rule : Rule,
pos : Int32?, location : Crystal::Location?,
message : String message : String
getter lines : Array(String)? getter lines : Array(String)?
getter errors = [] of Error getter errors = [] of Error
getter path : String? getter path : String?
getter content : String getter code : String
getter ast : Crystal::ASTNode? getter ast : Crystal::ASTNode?
def initialize(@content : String, @path = nil) def initialize(@code : String, @path = nil)
end end
def error(rule : Rule, line_number : Int32?, message : String) def error(rule : Rule, location, message : String)
errors << Error.new rule, line_number, message errors << Error.new rule, location, message
end end
def valid? def valid?
@ -29,14 +29,18 @@ module Ameba
end end
def lines def lines
@lines ||= @content.split("\n") @lines ||= @code.split("\n")
end end
def ast def ast
@ast ||= @ast ||=
Crystal::Parser.new(content) Crystal::Parser.new(code)
.tap { |parser| parser.filename = @path } .tap { |parser| parser.filename = @path }
.parse .parse
end end
def location(l, c)
Crystal::Location.new path, l, c
end
end end
end end

View file

@ -3,7 +3,7 @@ require "compiler/crystal/syntax/*"
module Ameba module Ameba
class Tokenizer class Tokenizer
def initialize(source) def initialize(source)
@lexer = Crystal::Lexer.new source.content @lexer = Crystal::Lexer.new source.code
@lexer.count_whitespace = true @lexer.count_whitespace = true
@lexer.comments_enabled = true @lexer.comments_enabled = true
@lexer.wants_raw = true @lexer.wants_raw = true