Class: ThemeCheck::Offense

Inherits:
Object
  • Object
show all
Includes:
PositionHelper
Defined in:
lib/theme_check/offense.rb

Constant Summary collapse

MAX_SOURCE_EXCERPT_SIZE =
120

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from PositionHelper

#bounded, #from_index_to_row_column, #from_row_column_to_index

Constructor Details

#initialize(check:, message: nil, theme_file: nil, node: nil, markup: nil, line_number: nil, node_markup_offset: 0, correction: nil) ⇒ Offense

Returns a new instance of Offense.

Raises:

  • (ArgumentError)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/theme_check/offense.rb', line 10

def initialize(
  check:, # instance of a ThemeCheck::Check
  message: nil, # error message for the offense
  theme_file: nil, # ThemeFile
  node: nil, # Node
  markup: nil, # string
  line_number: nil, # line number of the error (1-indexed)
  # node_markup_offset is the index inside node.markup to start
  # looking for markup :mindblow:.
  # This is so we can accurately highlight node substrings.
  # e.g. if we have the following scenario in which we
  # want to highlight the middle comma
  #   * node.markup == "replace ',',', '"
  #   * markup == ","
  # Then we need some way of telling our Position class to start
  # looking for the second comma. This is done with node_markup_offset.
  # More context can be found in #376.
  node_markup_offset: 0,
  correction: nil # block
)
  @check = check
  @correction = correction

  if message
    @message = message
  elsif defined?(check.class::MESSAGE)
    @message = check.class::MESSAGE
  else
    raise ArgumentError, "message required"
  end

  @node = node
  @theme_file = node&.theme_file || theme_file
  @markup = markup || node&.markup

  raise ArgumentError, "Offense markup cannot be an empty string" if @markup.is_a?(String) && @markup.empty?

  @line_number = line_number || @node&.line_number

  @position = Position.new(
    @markup,
    @theme_file&.source,
    line_number_1_indexed: @line_number,
    node_markup_offset: node_markup_offset,
    node_markup: node&.markup
  )
end

Instance Attribute Details

#checkObject (readonly)

Returns the value of attribute check.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def check
  @check
end

#correctionObject (readonly)

Returns the value of attribute correction.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def correction
  @correction
end

#line_numberObject (readonly)

Returns the value of attribute line_number.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def line_number
  @line_number
end

#markupObject (readonly)

Returns the value of attribute markup.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def markup
  @markup
end

#messageObject (readonly)

Returns the value of attribute message.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def message
  @message
end

#nodeObject (readonly)

Returns the value of attribute node.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def node
  @node
end

#theme_fileObject (readonly)

Returns the value of attribute theme_file.



8
9
10
# File 'lib/theme_check/offense.rb', line 8

def theme_file
  @theme_file
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



187
188
189
190
191
192
193
194
# File 'lib/theme_check/offense.rb', line 187

def ==(other)
  other.is_a?(Offense) &&
    code_name == other.code_name &&
    message == other.message &&
    location == other.location &&
    start_index == other.start_index &&
    end_index == other.end_index
end

#check_nameObject



120
121
122
# File 'lib/theme_check/offense.rb', line 120

def check_name
  StringHelpers.demodulize(check.class.name)
end

#code_nameObject



108
109
110
# File 'lib/theme_check/offense.rb', line 108

def code_name
  check.code_name
end

#correct(corrector = nil) ⇒ Object



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/theme_check/offense.rb', line 146

def correct(corrector = nil)
  if correctable?
    corrector ||= Corrector.new(theme_file: theme_file)
    correction.call(corrector)
  end
rescue => e
  ThemeCheck.bug(<<~EOS)
    Exception while running `Offense#correct`:
    ```
    #{e.class}: #{e.message}
      #{e.backtrace.join("\n  ")}
    ```

    Offense:
    ```
    #{JSON.pretty_generate(to_h)}
    ```
    Check options:
    ```
    #{check.options.pretty_inspect}
    ```
    Markup:
    ```
    #{markup}
    ```
    Node.Markup:
    ```
    #{node&.markup}
    ```
  EOS
  exit(2)
end

#correctable?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'lib/theme_check/offense.rb', line 142

def correctable?
  !!correction
end

#docObject



128
129
130
# File 'lib/theme_check/offense.rb', line 128

def doc
  check.doc
end

#end_columnObject



104
105
106
# File 'lib/theme_check/offense.rb', line 104

def end_column
  @position.end_column
end

#end_indexObject



96
97
98
# File 'lib/theme_check/offense.rb', line 96

def end_index
  @position.end_index
end

#end_rowObject



100
101
102
# File 'lib/theme_check/offense.rb', line 100

def end_row
  @position.end_row
end

#in_range?(other_range) ⇒ Boolean

Returns:

  • (Boolean)


70
71
72
73
74
# File 'lib/theme_check/offense.rb', line 70

def in_range?(other_range)
  # Zero length ranges are OK and considered the same as size 1 ranges
  other_range = other_range.first..other_range.end if other_range.size == 0 # rubocop:disable Style/ZeroLengthPredicate
  range.cover?(other_range) || other_range.cover?(range)
end

#locationObject



132
133
134
135
# File 'lib/theme_check/offense.rb', line 132

def location
  tokens = [theme_file&.relative_path, line_number].compact
  tokens.join(":") if tokens.any?
end

#location_rangeObject



137
138
139
140
# File 'lib/theme_check/offense.rb', line 137

def location_range
  tokens = [theme_file&.relative_path, start_index, end_index].compact
  tokens.join(":") if tokens.any?
end

#markup_start_in_excerptObject



112
113
114
# File 'lib/theme_check/offense.rb', line 112

def markup_start_in_excerpt
  source_excerpt.index(markup) if markup
end

#rangeObject



76
77
78
79
80
81
82
# File 'lib/theme_check/offense.rb', line 76

def range
  @range ||= if start_index == end_index
    (start_index..end_index)
  else
    (start_index...end_index) # end_index is excluded
  end
end

#severityObject



116
117
118
# File 'lib/theme_check/offense.rb', line 116

def severity
  check.severity
end

#single_file?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'lib/theme_check/offense.rb', line 183

def single_file?
  check.single_file?
end

#source_excerptObject



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/theme_check/offense.rb', line 58

def source_excerpt
  return unless line_number
  @source_excerpt ||= begin
    excerpt = theme_file.source_excerpt(line_number)
    if excerpt.size > MAX_SOURCE_EXCERPT_SIZE
      excerpt[0, MAX_SOURCE_EXCERPT_SIZE - 3] + '...'
    else
      excerpt
    end
  end
end

#start_columnObject



92
93
94
# File 'lib/theme_check/offense.rb', line 92

def start_column
  @position.start_column
end

#start_indexObject



84
85
86
# File 'lib/theme_check/offense.rb', line 84

def start_index
  @position.start_index
end

#start_rowObject



88
89
90
# File 'lib/theme_check/offense.rb', line 88

def start_row
  @position.start_row
end

#to_hObject



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/theme_check/offense.rb', line 213

def to_h
  {
    check: check.code_name,
    path: theme_file&.relative_path,
    severity: check.severity_value,
    start_row: start_row,
    start_column: start_column,
    end_row: end_row,
    end_column: end_column,
    message: message,
  }
end

#to_sObject



197
198
199
200
201
202
203
# File 'lib/theme_check/offense.rb', line 197

def to_s
  if theme_file
    "#{message} at #{location}"
  else
    message
  end
end

#to_s_rangeObject



205
206
207
208
209
210
211
# File 'lib/theme_check/offense.rb', line 205

def to_s_range
  if theme_file
    "#{message} at #{location_range}"
  else
    message
  end
end

#versionObject



124
125
126
# File 'lib/theme_check/offense.rb', line 124

def version
  theme_file&.version
end

#whole_theme?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'lib/theme_check/offense.rb', line 179

def whole_theme?
  check.whole_theme?
end