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
|
2018-01-29 22:25:36 +00:00
|
|
|
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,
|
2017-11-07 21:50:25 +00:00
|
|
|
# location of the issue and a message.
|
2017-10-26 21:01:23 +00:00
|
|
|
record Error,
|
2017-11-07 21:50:25 +00:00
|
|
|
rule : Rule::Base,
|
2017-11-07 20:02:51 +00:00
|
|
|
location : Crystal::Location?,
|
2018-01-29 22:25:36 +00:00
|
|
|
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).
|
2017-11-07 20:02:51 +00:00
|
|
|
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
|
|
|
|
|
2018-01-28 18:06:04 +00:00
|
|
|
# Adds new error to the list of errors.
|
2017-11-15 18:49:09 +00:00
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# source.error rule, location, "Line too long"
|
|
|
|
# ```
|
|
|
|
#
|
2018-01-29 22:25:36 +00:00
|
|
|
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
|
|
|
|
|
2018-01-28 18:06:04 +00:00
|
|
|
# Adds new error to the list of errors using line and column number.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# source.error rule, line_number, column_number, "Bad code"
|
|
|
|
# ```
|
|
|
|
#
|
2018-01-29 22:25:36 +00:00
|
|
|
def error(rule : Rule::Base, l, c, message : String, status = nil)
|
2018-01-28 18:06:04 +00:00
|
|
|
location = Crystal::Location.new path, l, c
|
2018-01-29 22:25:36 +00:00
|
|
|
error rule, location, message, status
|
2018-01-28 18:06:04 +00:00
|
|
|
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?
|
2018-01-29 22:25:36 +00:00
|
|
|
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"]
|
|
|
|
# ```
|
|
|
|
#
|
2017-10-31 22:47:29 +00:00
|
|
|
def lines
|
2017-11-07 20:02:51 +00:00
|
|
|
@lines ||= @code.split("\n")
|
2017-10-31 22:47:29 +00:00
|
|
|
end
|
2017-11-06 18:54:58 +00:00
|
|
|
|
2017-11-15 18:49:09 +00:00
|
|
|
# Returns AST nodes constructed by `Crystal::Parser`.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# source = Ameba::Source.new code, path
|
|
|
|
# source.ast
|
|
|
|
# ```
|
|
|
|
#
|
2017-11-06 18:54:58 +00:00
|
|
|
def ast
|
|
|
|
@ast ||=
|
2017-11-07 20:02:51 +00:00
|
|
|
Crystal::Parser.new(code)
|
2017-11-06 18:54:58 +00:00
|
|
|
.tap { |parser| parser.filename = @path }
|
|
|
|
.parse
|
|
|
|
end
|
2017-11-07 20:02:51 +00:00
|
|
|
|
2017-12-18 11:06:19 +00:00
|
|
|
def fullpath
|
|
|
|
@fullpath ||= File.expand_path @path
|
|
|
|
end
|
2017-10-26 17:47:42 +00:00
|
|
|
end
|
|
|
|
end
|