Compare commits
1 Commits
891cad2610
...
f2f7ac95df
Author | SHA1 | Date |
---|---|---|
Sijawusz Pur Rahnama | f2f7ac95df |
|
@ -0,0 +1,97 @@
|
|||
module Ameba::Rule::Lint
|
||||
# A rule that reports typos found in source files.
|
||||
#
|
||||
# NOTE: Needs [typos](https://github.com/crate-ci/typos) CLI tool.
|
||||
# NOTE: See the chapter on [false positives](https://github.com/crate-ci/typos#false-positives).
|
||||
#
|
||||
# YAML configuration example:
|
||||
#
|
||||
# ```
|
||||
# Lint/Typos:
|
||||
# Enabled: true
|
||||
# BinPath: ~
|
||||
# FailOnError: false
|
||||
# ```
|
||||
class Typos < Base
|
||||
properties do
|
||||
description "Reports typos found in source files"
|
||||
bin_path nil.as(String?)
|
||||
fail_on_error false
|
||||
end
|
||||
|
||||
record Typo,
|
||||
path : String,
|
||||
typo : String,
|
||||
corrections : Array(String),
|
||||
location : {Int32, Int32},
|
||||
end_location : {Int32, Int32} do
|
||||
def self.parse(str)
|
||||
issue = JSON.parse(str)
|
||||
|
||||
return unless issue["type"] == "typo"
|
||||
|
||||
typo = issue["typo"].as_s
|
||||
corrections = issue["corrections"].as_a.map(&.as_s)
|
||||
|
||||
return if typo.empty? || corrections.empty?
|
||||
|
||||
path = issue["path"].as_s
|
||||
line_no = issue["line_num"].as_i
|
||||
col_no = issue["byte_offset"].as_i + 1
|
||||
end_col_no = col_no + typo.size - 1
|
||||
|
||||
new(path, typo, corrections,
|
||||
{line_no, col_no}, {line_no, end_col_no})
|
||||
end
|
||||
end
|
||||
|
||||
MSG = "Typo found: %s -> %s"
|
||||
|
||||
protected class_getter bin_path : String? {
|
||||
Process.find_executable("typos")
|
||||
}
|
||||
|
||||
@[YAML::Field(ignore: true)]
|
||||
protected getter bin_path : String {
|
||||
@bin_path ||
|
||||
self.class.bin_path ||
|
||||
raise RuntimeError.new "Could not find `typos` executable"
|
||||
}
|
||||
|
||||
protected def typos_from(source : Source)
|
||||
status = Process.run(bin_path, args: ["--format", "json", source.path],
|
||||
output: output = IO::Memory.new,
|
||||
)
|
||||
return if status.success?
|
||||
|
||||
([] of Typo).tap do |typos|
|
||||
# NOTE: `--format json` is actually JSON Lines (`jsonl`)
|
||||
output.to_s.each_line do |line|
|
||||
Typo.parse(line).try { |typo| typos << typo }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test(source : Source)
|
||||
typos = typos_from(source)
|
||||
typos.try &.each do |typo|
|
||||
message = MSG % {
|
||||
typo.typo, typo.corrections.join(" / "),
|
||||
}
|
||||
|
||||
# FIXME
|
||||
# if typo.corrections.size == 1
|
||||
# issue_for typo.location, typo.end_location, message do |corrector|
|
||||
# corrector.replace(typo.location, typo.end_location, typo.corrections.first)
|
||||
# end
|
||||
# else
|
||||
# issue_for typo.location, typo.end_location, message
|
||||
# end
|
||||
|
||||
issue_for typo.location, typo.end_location, message
|
||||
end
|
||||
rescue ex
|
||||
raise ex if fail_on_error?
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue