2018-01-29 22:25:36 +00:00
|
|
|
module Ameba
|
2018-01-31 17:46:44 +00:00
|
|
|
# A module that utilizes inline comments parsing and processing logic.
|
2018-01-29 22:25:36 +00:00
|
|
|
module InlineComments
|
2022-11-14 00:24:29 +00:00
|
|
|
COMMENT_DIRECTIVE_REGEX =
|
|
|
|
/# ameba:(?<action>\w+) (?<rules>\w+(?:\/\w+)?(?:,? \w+(?:\/\w+)?)*)/
|
2018-06-23 13:52:48 +00:00
|
|
|
|
|
|
|
# Available actions in the inline comments
|
|
|
|
enum Action
|
|
|
|
Disable
|
|
|
|
Enable
|
|
|
|
end
|
2018-01-29 22:25:36 +00:00
|
|
|
|
2022-12-09 23:20:20 +00:00
|
|
|
# Returns `true` if current location is disabled for a particular rule,
|
|
|
|
# `false` otherwise.
|
2018-01-29 22:25:36 +00:00
|
|
|
#
|
|
|
|
# Location is disabled in two cases:
|
|
|
|
# 1. The line of the location ends with a comment directive.
|
|
|
|
# 2. The line above the location is a comment directive.
|
|
|
|
#
|
2018-01-30 13:46:11 +00:00
|
|
|
# For example, here are two examples of disabled location:
|
2018-01-29 22:25:36 +00:00
|
|
|
#
|
|
|
|
# ```
|
2020-04-06 12:10:34 +00:00
|
|
|
# # ameba:disable Style/LargeNumbers
|
2018-01-29 22:25:36 +00:00
|
|
|
# Time.epoch(1483859302)
|
|
|
|
#
|
2020-04-06 12:10:34 +00:00
|
|
|
# Time.epoch(1483859302) # ameba:disable Style/LargeNumbers
|
2018-01-29 22:25:36 +00:00
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# But here are examples which are not considered as disabled location:
|
|
|
|
#
|
|
|
|
# ```
|
2020-04-06 12:10:34 +00:00
|
|
|
# # ameba:disable Style/LargeNumbers
|
2018-01-29 22:25:36 +00:00
|
|
|
# #
|
|
|
|
# Time.epoch(1483859302)
|
|
|
|
#
|
2020-04-06 12:10:34 +00:00
|
|
|
# if use_epoch? # ameba:disable Style/LargeNumbers
|
2018-01-29 22:25:36 +00:00
|
|
|
# Time.epoch(1483859302)
|
|
|
|
# end
|
|
|
|
# ```
|
2022-08-01 21:50:30 +00:00
|
|
|
def location_disabled?(location : Crystal::Location?, rule)
|
2021-01-17 17:12:10 +00:00
|
|
|
return false if rule.name.in?(Rule::SPECIAL)
|
2018-01-29 22:25:36 +00:00
|
|
|
return false unless line_number = location.try &.line_number.try &.- 1
|
|
|
|
return false unless line = lines[line_number]?
|
|
|
|
|
|
|
|
line_disabled?(line, rule) ||
|
|
|
|
(line_number > 0 &&
|
|
|
|
(prev_line = lines[line_number - 1]) &&
|
|
|
|
comment?(prev_line) &&
|
|
|
|
line_disabled?(prev_line, rule))
|
|
|
|
end
|
|
|
|
|
2018-01-31 17:46:44 +00:00
|
|
|
# Parses inline comment directive. Returns a tuple that consists of
|
|
|
|
# an action and parsed rules if directive found, nil otherwise.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# line = "# ameba:disable Rule1, Rule2"
|
|
|
|
# directive = parse_inline_directive(line)
|
|
|
|
# directive[:action] # => "disable"
|
|
|
|
# directive[:rules] # => ["Rule1", "Rule2"]
|
|
|
|
# ```
|
|
|
|
#
|
|
|
|
# It ignores the directive if it is commented out.
|
|
|
|
#
|
|
|
|
# ```
|
|
|
|
# line = "# # ameba:disable Rule1, Rule2"
|
|
|
|
# parse_inline_directive(line) # => nil
|
|
|
|
# ```
|
|
|
|
def parse_inline_directive(line)
|
2021-12-09 20:33:47 +00:00
|
|
|
return unless directive = COMMENT_DIRECTIVE_REGEX.match(line)
|
|
|
|
return if commented_out?(line.gsub(directive[0], ""))
|
|
|
|
{
|
|
|
|
action: directive["action"],
|
|
|
|
rules: directive["rules"].split(/[\s,]/, remove_empty: true),
|
|
|
|
}
|
2018-01-31 17:46:44 +00:00
|
|
|
end
|
|
|
|
|
2022-12-09 23:20:20 +00:00
|
|
|
# Returns `true` if the line at the given `line_number` is a comment.
|
2018-05-08 21:14:46 +00:00
|
|
|
def comment?(line_number : Int32)
|
2021-12-09 20:33:47 +00:00
|
|
|
return unless line = lines[line_number]?
|
|
|
|
comment?(line)
|
2018-05-08 21:14:46 +00:00
|
|
|
end
|
|
|
|
|
2020-04-11 06:55:41 +00:00
|
|
|
private def comment?(line : String)
|
|
|
|
line.lstrip.starts_with? '#'
|
2018-01-29 22:25:36 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private def line_disabled?(line, rule)
|
2018-01-31 17:46:44 +00:00
|
|
|
return false unless directive = parse_inline_directive(line)
|
2021-01-11 18:13:58 +00:00
|
|
|
return false unless Action.parse?(directive[:action]).try(&.disable?)
|
|
|
|
|
2022-11-14 00:24:29 +00:00
|
|
|
rules = directive[:rules]
|
|
|
|
rules.includes?(rule.name) || rules.includes?(rule.group)
|
2018-01-29 22:25:36 +00:00
|
|
|
end
|
|
|
|
|
2018-01-31 17:46:44 +00:00
|
|
|
private def commented_out?(line)
|
2019-06-06 16:10:26 +00:00
|
|
|
commented = false
|
2018-01-31 17:46:44 +00:00
|
|
|
|
2020-04-11 06:55:41 +00:00
|
|
|
lexer = Crystal::Lexer.new(line).tap(&.comments_enabled = true)
|
2022-11-14 00:24:29 +00:00
|
|
|
Tokenizer.new(lexer).run do |token|
|
|
|
|
commented = true if token.type.comment?
|
|
|
|
end
|
2019-06-06 16:10:26 +00:00
|
|
|
commented
|
2018-01-29 22:25:36 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|