shard-ameba/src/ameba/source.cr

121 lines
3 KiB
Crystal
Raw Normal View History

2017-10-26 17:47:42 +00:00
module Ameba
2017-10-30 20:00:01 +00:00
# An entity that represents a Crystal source file.
# Has path, lines of code and errors reported by rules.
2017-10-26 17:47:42 +00:00
class Source
include InlineComments
2017-10-30 20:00:01 +00:00
# Represents an error caught by Ameba.
#
# Each error has the rule that created this error,
# location of the issue, message and status.
2017-10-26 21:01:23 +00:00
record Error,
2017-11-07 21:50:25 +00:00
rule : Rule::Base,
location : Crystal::Location?,
message : String,
status : Symbol? do
def disabled?
status == :disabled
end
end
2017-10-26 21:01:23 +00:00
2017-11-15 18:49:09 +00:00
# Path to the source file.
2017-12-18 11:06:19 +00:00
getter path : String
2017-11-15 18:49:09 +00:00
# Crystal code (content of a source file).
getter code : String
2017-10-26 17:47:42 +00:00
2017-11-15 18:49:09 +00:00
# List of errors reported.
getter errors = [] of Error
@lines : Array(String)?
@ast : Crystal::ASTNode?
2017-12-18 11:06:19 +00:00
@fullpath : String?
2017-11-15 18:49:09 +00:00
# Creates a new source by `code` and `path`.
#
# For example:
#
# ```
# path = "./src/source.cr"
# Ameba::Source.new File.read(path), path
# ```
#
2017-12-18 11:06:19 +00:00
def initialize(@code : String, @path = "")
2017-10-30 20:00:01 +00:00
end
# Adds a new error to the list of errors.
2017-11-15 18:49:09 +00:00
#
# ```
# source.error rule, location, "Line too long"
# ```
#
def error(rule : Rule::Base, location, message : String, status = nil)
status ||= :disabled if location_disabled?(location, rule.name)
errors << Error.new rule, location, message, status
2017-10-30 20:00:01 +00:00
end
# Adds a new error to the list of errors using line and column number.
#
# ```
# source.error rule, line_number, column_number, "Bad code"
# ```
#
def error(rule : Rule::Base, l, c, message : String, status = nil)
location = Crystal::Location.new path, l, c
error rule, location, message, status
end
2017-11-15 18:49:09 +00:00
# Indicates whether source is valid or not.
# Returns true if the list or errors empty, false otherwise.
#
# ```
# source = Ameba::Source.new code, path
# source.valid? # => true
# source.error rule, location, message
# source.valid? # => false
# ```
#
2017-10-30 20:00:01 +00:00
def valid?
errors.reject(&.disabled?).empty?
2017-10-26 17:47:42 +00:00
end
2017-10-31 18:29:30 +00:00
2017-11-15 18:49:09 +00:00
# Returns lines of code splitted by new line character.
# Since `code` is immutable and can't be changed, this
# method caches lines in an instance variable, so calling
# it second time will not perform a split, but will return
# lines instantly.
#
# ```
# source = Ameba::Source.new "a = 1\nb = 2", path
# source.lines # => ["a = 1", "b = 2"]
# ```
#
def lines
@lines ||= @code.split("\n")
end
2017-11-15 18:49:09 +00:00
# Returns AST nodes constructed by `Crystal::Parser`.
#
# ```
# source = Ameba::Source.new code, path
# source.ast
# ```
#
def ast
@ast ||=
Crystal::Parser.new(code)
.tap { |parser| parser.filename = @path }
.parse
end
2017-12-18 11:06:19 +00:00
def fullpath
@fullpath ||= File.expand_path @path
end
2018-05-29 10:19:00 +00:00
# Returns true if *filepath* matches the source's path, false if it does not.
def matches_path?(filepath)
path == filepath || path == File.expand_path(filepath)
end
2017-10-26 17:47:42 +00:00
end
end