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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 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 = nil
  if node
    @theme_file = node.theme_file
  elsif theme_file
    @theme_file = theme_file
  end

  @markup = if markup
    markup
  else
    node&.markup
  end

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

  @line_number = if line_number
    line_number
  elsif @node
    @node.line_number
  end

  @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?



183
184
185
186
187
188
189
190
# File 'lib/theme_check/offense.rb', line 183

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

#correctObject



142
143
144
145
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
# File 'lib/theme_check/offense.rb', line 142

def correct
  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)


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

def correctable?
  !!correction
end

#docObject



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

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_lineObject



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

def end_line
  @position.end_row
end

#locationObject



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

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

#location_rangeObject



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

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

#severityObject



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

def severity
  check.severity
end

#single_file?Boolean

Returns:

  • (Boolean)


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

def single_file?
  check.single_file?
end

#source_excerptObject



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

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_lineObject



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

def start_line
  @position.start_row
end

#to_hObject



209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/theme_check/offense.rb', line 209

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

#to_sObject



193
194
195
196
197
198
199
# File 'lib/theme_check/offense.rb', line 193

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

#to_s_rangeObject



201
202
203
204
205
206
207
# File 'lib/theme_check/offense.rb', line 201

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

#whole_theme?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'lib/theme_check/offense.rb', line 175

def whole_theme?
  check.whole_theme?
end