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.
|
2018-06-10 21:15:12 +00:00
|
|
|
# Has path, lines of code and issues reported by rules.
|
2017-10-26 17:47:42 +00:00
|
|
|
class Source
|
2018-01-29 22:25:36 +00:00
|
|
|
include InlineComments
|
2018-06-10 21:15:12 +00:00
|
|
|
include Reportable
|
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
|
|
|
# Creates a new source by `code` and `path`.
|
|
|
|
#
|
|
|
|
# For example:
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# path = "./src/source.cr"
|
|
|
|
# Ameba::Source.new File.read(path), path
|
|
|
|
# ```
|
2021-01-11 18:13:58 +00:00
|
|
|
def initialize(@code, @path = "")
|
2017-10-30 20:00:01 +00:00
|
|
|
end
|
|
|
|
|
2021-10-27 04:03:30 +00:00
|
|
|
# Corrects any correctable issues and updates `code`.
|
|
|
|
# Returns `false` if no issues were corrected.
|
2022-12-20 13:53:23 +00:00
|
|
|
def correct?
|
2021-10-25 22:09:39 +00:00
|
|
|
corrector = Corrector.new(code)
|
|
|
|
issues.each(&.correct(corrector))
|
2022-12-19 23:34:11 +00:00
|
|
|
|
2021-10-25 22:09:39 +00:00
|
|
|
corrected_code = corrector.process
|
|
|
|
return false if code == corrected_code
|
|
|
|
|
|
|
|
@code = corrected_code
|
|
|
|
@lines = nil
|
|
|
|
@ast = nil
|
2022-11-26 01:32:11 +00:00
|
|
|
|
2021-10-25 22:09:39 +00:00
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2022-11-14 00:24:29 +00:00
|
|
|
# Returns lines of code split by new line character.
|
2017-11-15 18:49:09 +00:00
|
|
|
# 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"]
|
|
|
|
# ```
|
2021-10-27 17:08:36 +00:00
|
|
|
getter lines : Array(String) { code.split('\n') }
|
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
|
|
|
|
# ```
|
2021-10-27 17:08:36 +00:00
|
|
|
getter ast : Crystal::ASTNode do
|
|
|
|
Crystal::Parser.new(code)
|
|
|
|
.tap(&.wants_doc = true)
|
|
|
|
.tap(&.filename = path)
|
|
|
|
.parse
|
2017-11-06 18:54:58 +00:00
|
|
|
end
|
2017-11-07 20:02:51 +00:00
|
|
|
|
2021-01-11 18:13:58 +00:00
|
|
|
getter fullpath : String do
|
|
|
|
File.expand_path(path)
|
2017-12-18 11:06:19 +00:00
|
|
|
end
|
2018-05-29 10:19:00 +00:00
|
|
|
|
2021-02-03 15:14:03 +00:00
|
|
|
# Returns `true` if the source is a spec file, `false` otherwise.
|
|
|
|
def spec?
|
|
|
|
path.ends_with?("_spec.cr")
|
|
|
|
end
|
|
|
|
|
2022-11-26 01:32:11 +00:00
|
|
|
# Returns `true` if *filepath* matches the source's path, `false` otherwise.
|
2018-05-29 10:19:00 +00:00
|
|
|
def matches_path?(filepath)
|
2022-11-26 01:32:11 +00:00
|
|
|
path.in?(filepath, File.expand_path(filepath))
|
2018-05-29 10:19:00 +00:00
|
|
|
end
|
2022-12-19 14:27:20 +00:00
|
|
|
|
|
|
|
# Converts an AST location to a string position.
|
|
|
|
def pos(location : Crystal::Location, end end_pos = false) : Int32
|
|
|
|
line, column = location.line_number, location.column_number
|
|
|
|
pos = lines[0...line - 1].sum(&.size) + line + column - 2
|
|
|
|
pos += 1 if end_pos
|
|
|
|
pos
|
|
|
|
end
|
2017-10-26 17:47:42 +00:00
|
|
|
end
|
|
|
|
end
|