mirror of
https://gitea.invidious.io/iv-org/shard-ameba.git
synced 2024-08-15 00:53:29 +00:00
Add Lint/DocumentationAdmonition
rule
This commit is contained in:
parent
ddb6e3c38f
commit
23c61e04c0
2 changed files with 209 additions and 0 deletions
113
spec/ameba/rule/lint/documentation_admonition_spec.cr
Normal file
113
spec/ameba/rule/lint/documentation_admonition_spec.cr
Normal file
|
@ -0,0 +1,113 @@
|
|||
require "../../../spec_helper"
|
||||
|
||||
module Ameba::Rule::Lint
|
||||
subject = DocumentationAdmonition.new
|
||||
|
||||
describe DocumentationAdmonition do
|
||||
it "passes for comments with admonition mid-word/sentence" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
# Mentioning #{admonition} mid-sentence
|
||||
# x#{admonition}x
|
||||
# x#{admonition}
|
||||
# #{admonition}x
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for comments with admonition" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}: Single-line comment
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
CRYSTAL
|
||||
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# Text before ...
|
||||
# #{admonition}(some context): Part of multi-line comment
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
# Text after ...
|
||||
CRYSTAL
|
||||
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
if rand > 0.5
|
||||
end
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
context "with date" do
|
||||
it "passes for admonitions with future date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
future_date = (Time.utc + 21.days).to_s(format: "%F")
|
||||
expect_no_issues subject, <<-CRYSTAL
|
||||
# #{admonition}(#{future_date}): sth in the future
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with past date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
past_date = (Time.utc - 21.days).to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{past_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (21 days past)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with yesterday's date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
yesterday_date = (Time.utc - 1.day).to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{yesterday_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (1 day past)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with today's date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
today_date = Time.utc.to_s(format: "%F")
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(#{today_date}): sth in the past
|
||||
# ^{} error: Found a #{admonition} admonition in a comment (today is the day!)
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
|
||||
it "fails for admonitions with invalid date" do
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_issue subject, <<-CRYSTAL
|
||||
# #{admonition}(0000-00-00): sth wrong
|
||||
# ^{} error: #{admonition} admonition error: Invalid time: "0000-00-00"
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "properties" do
|
||||
describe "#admonitions" do
|
||||
it "lets setting custom admonitions" do
|
||||
rule = DocumentationAdmonition.new
|
||||
rule.admonitions = %w[FOO BAR]
|
||||
|
||||
rule.admonitions.each do |admonition|
|
||||
expect_issue rule, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
# ^{} error: Found a #{admonition} admonition in a comment
|
||||
CRYSTAL
|
||||
end
|
||||
|
||||
subject.admonitions.each do |admonition|
|
||||
expect_no_issues rule, <<-CRYSTAL
|
||||
# #{admonition}
|
||||
CRYSTAL
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
96
src/ameba/rule/lint/documentation_admonition.cr
Normal file
96
src/ameba/rule/lint/documentation_admonition.cr
Normal file
|
@ -0,0 +1,96 @@
|
|||
module Ameba::Rule::Lint
|
||||
# A rule that reports documentation admonitions.
|
||||
#
|
||||
# Optionally, these can fail at an appropriate time.
|
||||
#
|
||||
# ```
|
||||
# def get_user(id)
|
||||
# # TODO(2024-04-24) Fix this hack when the database migration is complete
|
||||
# if id < 1_000_000
|
||||
# v1_api_call(id)
|
||||
# else
|
||||
# v2_api_call(id)
|
||||
# end
|
||||
# end
|
||||
# ```
|
||||
#
|
||||
# `TODO` comments are used to remind yourself of source code related things.
|
||||
#
|
||||
# The premise here is that `TODO` should be dealt with in the near future
|
||||
# and are therefore reported by Ameba.
|
||||
#
|
||||
# `FIXME` comments are used to indicate places where source code needs fixing.
|
||||
#
|
||||
# The premise here is that `FIXME` should indeed be fixed as soon as possible
|
||||
# and are therefore reported by Ameba.
|
||||
#
|
||||
# YAML configuration example:
|
||||
#
|
||||
# ```
|
||||
# Lint/DocumentationAdmonition:
|
||||
# Enabled: true
|
||||
# Admonitions: [TODO, FIXME, BUG]
|
||||
# Timezone: UTC
|
||||
# ```
|
||||
class DocumentationAdmonition < Base
|
||||
properties do
|
||||
description "Reports documentation admonitions"
|
||||
admonitions %w[TODO FIXME BUG]
|
||||
timezone "UTC"
|
||||
end
|
||||
|
||||
MSG = "Found a %s admonition in a comment"
|
||||
MSG_LATE = "Found a %s admonition in a comment (%s)"
|
||||
MSG_ERR = "%s admonition error: %s"
|
||||
|
||||
@[YAML::Field(ignore: true)]
|
||||
private getter location : Time::Location {
|
||||
Time::Location.load(self.timezone)
|
||||
}
|
||||
|
||||
def test(source)
|
||||
Tokenizer.new(source).run do |token|
|
||||
next unless token.type.comment?
|
||||
next unless doc = token.value.to_s
|
||||
|
||||
pattern =
|
||||
/^#\s*(?<admonition>#{Regex.union(admonitions)})(?:\((?<context>.+?)\))?(?:\W+|$)/m
|
||||
|
||||
matches = doc.scan(pattern)
|
||||
matches.each do |match|
|
||||
admonition = match["admonition"]
|
||||
begin
|
||||
case expr = match["context"]?.presence
|
||||
when /\A\d{4}-\d{2}-\d{2}\Z/ # date
|
||||
# ameba:disable Lint/NotNil
|
||||
date = Time.parse(expr.not_nil!, "%F", location)
|
||||
issue_for_date source, token, admonition, date
|
||||
when /\A\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?\Z/ # date + time (no tz)
|
||||
# ameba:disable Lint/NotNil
|
||||
date = Time.parse(expr.not_nil!, "%F #{$1?.presence ? "%T" : "%R"}", location)
|
||||
issue_for_date source, token, admonition, date
|
||||
else
|
||||
issue_for token, MSG % admonition
|
||||
end
|
||||
rescue ex
|
||||
issue_for token, MSG_ERR % {admonition, "#{ex}: #{expr.inspect}"}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private def issue_for_date(source, node, admonition, date)
|
||||
diff = Time.utc - date.to_utc
|
||||
|
||||
return if diff.negative?
|
||||
|
||||
past = case diff
|
||||
when 0.seconds..1.day then "today is the day!"
|
||||
when 1.day..2.days then "1 day past"
|
||||
else "#{diff.total_days.to_i} days past"
|
||||
end
|
||||
|
||||
issue_for node, MSG_LATE % {admonition, past}
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue