Class: RuboCop::DirectiveComment

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocop/directive_comment.rb

Overview

This class wraps the Parser::Source::Comment object that represents a special rubocop:disable and rubocop:enable comment and exposes what cops it contains.

Constant Summary collapse

LINT_DEPARTMENT =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'Lint'
LINT_REDUNDANT_DIRECTIVE_COP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"#{LINT_DEPARTMENT}/RedundantCopDisableDirective"
LINT_SYNTAX_COP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"#{LINT_DEPARTMENT}/Syntax"
STYLE_DISABLE_COPS_DIRECTIVE_COP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'Style/DisableCopsWithinSourceCodeDirective'
COP_NAME_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'([A-Za-z]\w+/)*(?:[A-Za-z]\w+)'
COP_NAME_PATTERN_NC =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'(?:[A-Za-z]\w+/)*[A-Za-z]\w+'
COP_NAMES_PATTERN_NC =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"(?:#{COP_NAME_PATTERN_NC} , )*#{COP_NAME_PATTERN_NC}"
COP_NAMES_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"(?:#{COP_NAME_PATTERN} , )*#{COP_NAME_PATTERN}"
COPS_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"(all|#{COP_NAMES_PATTERN})"
PUSH_POP_ARGS_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"([+\\-]#{COP_NAME_PATTERN_NC}(?:\\s+[+\\-]#{COP_NAME_PATTERN_NC})*)"
AVAILABLE_MODES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%w[disable enable todo push pop disable-next todo-next enable-next
next].freeze
MODES_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Longest first, so a -next mode is not matched as its prefix (- is a word boundary).

AVAILABLE_MODES.sort_by { |mode| -mode.length }.join('|').freeze
DIRECTIVE_MARKER_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'# rubocop : '
DIRECTIVE_MARKER_REGEXP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Regexp.new(DIRECTIVE_MARKER_PATTERN.gsub(' ', '\s*'))
DIRECTIVE_HEADER_PATTERN =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

"#{DIRECTIVE_MARKER_PATTERN}((?:#{MODES_PATTERN}))\\b"
DIRECTIVE_COMMENT_REGEXP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Regexp.new(
  "#{DIRECTIVE_HEADER_PATTERN}(?:\\s+#{COPS_PATTERN}|\\s+#{PUSH_POP_ARGS_PATTERN})?"
    .gsub(' ', '\s*')
)
SIGNED_OPERATIONS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%w[+ -].freeze
TRAILING_COMMENT_MARKER =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'--'
MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Regexp.new(
  "\\A#{DIRECTIVE_HEADER_PATTERN}\\s*\\z".gsub(' ', '\s*')
)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(comment, cop_registry = Cop::Registry.global) ⇒ DirectiveComment

Returns a new instance of DirectiveComment.



61
62
63
64
65
66
67
# File 'lib/rubocop/directive_comment.rb', line 61

def initialize(comment, cop_registry = Cop::Registry.global)
  @comment = comment
  @cop_registry = cop_registry
  match_data = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
  @match_data = match_data&.pre_match&.match?(/\A#\s*\z/) ? nil : match_data
  @mode, @cops = match_captures
end

Instance Attribute Details

#comment ⇒ Object (readonly)

Returns the value of attribute comment.



59
60
61
# File 'lib/rubocop/directive_comment.rb', line 59

def comment
  @comment
end

#cop_registry ⇒ Object (readonly)

Returns the value of attribute cop_registry.



59
60
61
# File 'lib/rubocop/directive_comment.rb', line 59

def cop_registry
  @cop_registry
end

#cops ⇒ Object (readonly)

Returns the value of attribute cops.



59
60
61
# File 'lib/rubocop/directive_comment.rb', line 59

def cops
  @cops
end

#mode ⇒ Object (readonly)

Returns the value of attribute mode.



59
60
61
# File 'lib/rubocop/directive_comment.rb', line 59

def mode
  @mode
end

Class Method Details

.before_comment(line) ⇒ Object



55
56
57
# File 'lib/rubocop/directive_comment.rb', line 55

def self.before_comment(line)
  line.split(DIRECTIVE_COMMENT_REGEXP).first
end

Instance Method Details

#all_cops? ⇒ Boolean

Checks if all cops specified in this directive

Returns:

  • (Boolean)


207
208
209
# File 'lib/rubocop/directive_comment.rb', line 207

def all_cops?
  cops == 'all'
end

#cop_names ⇒ Object

Returns array of specified in this directive cop names



212
213
214
# File 'lib/rubocop/directive_comment.rb', line 212

def cop_names
  @cop_names ||= all_cops? ? all_cop_names : parsed_cop_names
end

#department_names ⇒ Object

Returns array of specified in this directive department names when all department disabled



223
224
225
# File 'lib/rubocop/directive_comment.rb', line 223

def department_names
  raw_cop_names.select { |cop| department?(cop) }
end

#directive_count ⇒ Object



237
238
239
240
241
# File 'lib/rubocop/directive_comment.rb', line 237

def directive_count
  return signed_args.values.sum(&:count) if push? || next?

  raw_cop_names.count
end

#disable_next? ⇒ Boolean

Checks if this directive disables cops for the next statement only

Returns:

  • (Boolean)


160
161
162
# File 'lib/rubocop/directive_comment.rb', line 160

def disable_next?
  %w[disable-next todo-next].include?(mode)
end

#disabled? ⇒ Boolean

Checks if this directive disables cops

Returns:

  • (Boolean)


155
156
157
# File 'lib/rubocop/directive_comment.rb', line 155

def disabled?
  %w[disable todo].include?(mode) || disable_next?
end

#disabled_all? ⇒ Boolean

Checks if this directive disables all cops

Returns:

  • (Boolean)


202
203
204
# File 'lib/rubocop/directive_comment.rb', line 202

def disabled_all?
  disabled? && all_cops?
end

#enable_next? ⇒ Boolean

Checks if this directive enables cops for the next statement only

Returns:

  • (Boolean)


170
171
172
# File 'lib/rubocop/directive_comment.rb', line 170

def enable_next?
  mode == 'enable-next'
end

#enabled? ⇒ Boolean

Checks if this directive enables cops

Returns:

  • (Boolean)


165
166
167
# File 'lib/rubocop/directive_comment.rb', line 165

def enabled?
  mode == 'enable' || enable_next?
end

#enabled_all? ⇒ Boolean

Checks if this directive enables all cops

Returns:

  • (Boolean)


197
198
199
# File 'lib/rubocop/directive_comment.rb', line 197

def enabled_all?
  !disabled? && all_cops?
end

#in_directive_department?(cop) ⇒ Boolean

Checks if directive departments include cop

Returns:

  • (Boolean)


228
229
230
# File 'lib/rubocop/directive_comment.rb', line 228

def in_directive_department?(cop)
  department_names.any? { |department| cop.start_with?(department) }
end

#invalid_signed_args? ⇒ Boolean

push and next arguments must be +/- prefixed cop names, and pop takes no arguments at all.

Returns:

  • (Boolean)


92
93
94
95
96
97
98
# File 'lib/rubocop/directive_comment.rb', line 92

def invalid_signed_args?
  return cops ? !cops.empty? : false if pop?
  return false unless push? || next?
  return false unless cops

  cops.split.any? { |cop_spec| !cop_spec.start_with?('+', '-') }
end

#line_number ⇒ Object

Returns line number for directive



244
245
246
# File 'lib/rubocop/directive_comment.rb', line 244

def line_number
  comment.source_range.line
end

#malformed? ⇒ Boolean

Checks if the comment is malformed as a # rubocop: directive

Returns:

  • (Boolean)


75
76
77
78
79
80
81
# File 'lib/rubocop/directive_comment.rb', line 75

def malformed?
  return true if !start_with_marker? || @match_data.nil?
  return true if missing_cop_name? || invalid_signed_args?

  tail = @match_data.post_match.lstrip
  !(tail.empty? || tail.start_with?(TRAILING_COMMENT_MARKER))
end

#match?(cop_names) ⇒ Boolean

Checks if this directive contains all the given cop names

Returns:

  • (Boolean)


118
119
120
# File 'lib/rubocop/directive_comment.rb', line 118

def match?(cop_names)
  parsed_cop_names.uniq.sort == cop_names.uniq.sort
end

#match_captures ⇒ Object

Returns match captures to directive comment pattern



144
145
146
147
148
149
150
151
152
# File 'lib/rubocop/directive_comment.rb', line 144

def match_captures
  @match_captures ||= @match_data && begin
    captures = @match_data.captures
    mode = captures[0]
    # COPS_PATTERN is at captures[1], PUSH_POP_ARGS_PATTERN is at captures[4]
    cops = captures[1] || captures[4]
    [mode, cops]
  end
end

#missing_cop_name? ⇒ Boolean

Checks if the directive comment is missing a cop name

Returns:

  • (Boolean)


84
85
86
87
88
# File 'lib/rubocop/directive_comment.rb', line 84

def missing_cop_name?
  return false if push? || pop?

  MALFORMED_DIRECTIVE_WITHOUT_COP_NAME_REGEXP.match?(comment.text)
end

#next? ⇒ Boolean

Checks if this directive toggles cops for the next statement only

Returns:

  • (Boolean)


185
186
187
# File 'lib/rubocop/directive_comment.rb', line 185

def next?
  mode == 'next'
end

#overridden_by_department?(cop) ⇒ Boolean

Checks if cop department has already used in directive comment

Returns:

  • (Boolean)


233
234
235
# File 'lib/rubocop/directive_comment.rb', line 233

def overridden_by_department?(cop)
  in_directive_department?(cop) && raw_cop_names.include?(cop)
end

#pop? ⇒ Boolean

Checks if this directive is a pop

Returns:

  • (Boolean)


180
181
182
# File 'lib/rubocop/directive_comment.rb', line 180

def pop?
  mode == 'pop'
end

#push? ⇒ Boolean

Checks if this directive is a push

Returns:

  • (Boolean)


175
176
177
# File 'lib/rubocop/directive_comment.rb', line 175

def push?
  mode == 'push'
end

#range ⇒ Object



122
123
124
125
126
127
128
# File 'lib/rubocop/directive_comment.rb', line 122

def range
  match = comment.text.match(DIRECTIVE_COMMENT_REGEXP)
  begin_pos = comment.source_range.begin_pos
  Parser::Source::Range.new(
    comment.source_range.source_buffer, begin_pos + match.begin(0), begin_pos + match.end(0)
  )
end

#range_with_reason ⇒ Object

#range stops at the cop list, so a -- reason sits outside it. The reason documents the directive and means nothing once the directive is gone, so removal has to cover both. Any other trailing text is an ordinary comment and is left alone.



133
134
135
136
137
138
139
140
141
# File 'lib/rubocop/directive_comment.rb', line 133

def range_with_reason
  directive_range = range
  trailing = Parser::Source::Range.new(
    comment.source_range.source_buffer, directive_range.end_pos, comment.source_range.end_pos
  )
  return directive_range unless trailing.source.lstrip.start_with?(TRAILING_COMMENT_MARKER)

  directive_range.with(end_pos: comment.source_range.end_pos)
end

#raw_cop_names ⇒ Object

Returns an array of cops for this directive comment, without resolving departments



217
218
219
# File 'lib/rubocop/directive_comment.rb', line 217

def raw_cop_names
  @raw_cop_names ||= (cops || '').split(/,\s*/)
end

#reason ⇒ Object

The text of the directive's optional -- trailing comment, or nil when there is none.



102
103
104
105
106
107
108
109
110
# File 'lib/rubocop/directive_comment.rb', line 102

def reason
  return unless @match_data

  tail = @match_data.post_match.lstrip
  return unless tail.start_with?(TRAILING_COMMENT_MARKER)

  reason = tail.delete_prefix(TRAILING_COMMENT_MARKER).strip
  reason unless reason.empty?
end

#signed_args ⇒ Object Also known as: push_args

Returns the +/- arguments of a push or next directive as a hash of operations to cop names



191
192
193
# File 'lib/rubocop/directive_comment.rb', line 191

def signed_args
  @signed_args ||= parse_signed_args
end

#single_line? ⇒ Boolean

Checks if this directive relates to single line

Returns:

  • (Boolean)


113
114
115
# File 'lib/rubocop/directive_comment.rb', line 113

def single_line?
  !comment.text.start_with?(DIRECTIVE_COMMENT_REGEXP)
end

#start_with_marker? ⇒ Boolean

Checks if the comment starts with # rubocop: marker

Returns:

  • (Boolean)


70
71
72
# File 'lib/rubocop/directive_comment.rb', line 70

def start_with_marker?
  comment.text.start_with?(DIRECTIVE_MARKER_REGEXP)
end