mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Merge pull request #381 from crystal-ameba/add-typos-rule
Add `Lint/Typos` rule
This commit is contained in:
commit
0c6745781e
4 changed files with 136 additions and 0 deletions
|
@ -1,3 +1,7 @@
|
||||||
Lint/DocumentationAdmonition:
|
Lint/DocumentationAdmonition:
|
||||||
Timezone: UTC
|
Timezone: UTC
|
||||||
Admonitions: [FIXME, BUG]
|
Admonitions: [FIXME, BUG]
|
||||||
|
|
||||||
|
Lint/Typos:
|
||||||
|
Excluded:
|
||||||
|
- spec/ameba/rule/lint/typos_spec.cr
|
||||||
|
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -32,6 +32,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: shards install
|
run: shards install
|
||||||
|
|
||||||
|
- name: Install typos-cli
|
||||||
|
if: matrix.os == 'macos-latest'
|
||||||
|
run: brew install typos-cli
|
||||||
|
|
||||||
- name: Run specs
|
- name: Run specs
|
||||||
run: crystal spec
|
run: crystal spec
|
||||||
|
|
||||||
|
|
32
spec/ameba/rule/lint/typos_spec.cr
Normal file
32
spec/ameba/rule/lint/typos_spec.cr
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
require "../../../spec_helper"
|
||||||
|
|
||||||
|
private def check_typos_bin!
|
||||||
|
unless Ameba::Rule::Lint::Typos::BIN_PATH
|
||||||
|
pending! "`typos` executable is not available"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Ameba::Rule::Lint
|
||||||
|
subject = Typos.new
|
||||||
|
.tap(&.fail_on_error = true)
|
||||||
|
|
||||||
|
describe Typos do
|
||||||
|
it "reports typos" do
|
||||||
|
check_typos_bin!
|
||||||
|
|
||||||
|
source = expect_issue subject, <<-CRYSTAL
|
||||||
|
# method with no arugments
|
||||||
|
# ^^^^^^^^^ error: Typo found: arugments -> arguments
|
||||||
|
def tpos
|
||||||
|
# ^^^^ error: Typo found: tpos -> typos
|
||||||
|
end
|
||||||
|
CRYSTAL
|
||||||
|
|
||||||
|
expect_correction source, <<-CRYSTAL
|
||||||
|
# method with no arguments
|
||||||
|
def typos
|
||||||
|
end
|
||||||
|
CRYSTAL
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
96
src/ameba/rule/lint/typos.cr
Normal file
96
src/ameba/rule/lint/typos.cr
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
MSG = "Typo found: %s -> %s"
|
||||||
|
|
||||||
|
BIN_PATH = Process.find_executable("typos")
|
||||||
|
|
||||||
|
def bin_path : String?
|
||||||
|
@bin_path || BIN_PATH
|
||||||
|
end
|
||||||
|
|
||||||
|
def test(source : Source)
|
||||||
|
typos = typos_from(source)
|
||||||
|
typos.try &.each do |typo|
|
||||||
|
corrections = typo.corrections
|
||||||
|
message = MSG % {
|
||||||
|
typo.typo, corrections.join(" | "),
|
||||||
|
}
|
||||||
|
if corrections.size == 1
|
||||||
|
issue_for typo.location, typo.end_location, message do |corrector|
|
||||||
|
corrector.replace(typo.location, typo.end_location, corrections.first)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
issue_for typo.location, typo.end_location, message
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue ex
|
||||||
|
raise ex if fail_on_error?
|
||||||
|
end
|
||||||
|
|
||||||
|
private record Typo,
|
||||||
|
path : String,
|
||||||
|
typo : String,
|
||||||
|
corrections : Array(String),
|
||||||
|
location : {Int32, Int32},
|
||||||
|
end_location : {Int32, Int32} do
|
||||||
|
def self.parse(str) : self?
|
||||||
|
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
|
||||||
|
|
||||||
|
protected def typos_from(source : Source) : Array(Typo)?
|
||||||
|
unless bin_path = self.bin_path
|
||||||
|
if fail_on_error?
|
||||||
|
raise RuntimeError.new "Could not find `typos` executable"
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
status = Process.run(bin_path, args: %w[--format json -],
|
||||||
|
input: IO::Memory.new(source.code),
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue