breadkit-lint 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yamllint +16 -0
- data/CHANGELOG.md +30 -2
- data/README.md +55 -40
- data/action.yml +42 -0
- data/config/default.yml +33 -0
- data/docs/REFERENCE.md +129 -0
- data/docs/rules/Electrical/CapacitorVoltageRating.md +5 -0
- data/docs/rules/Electrical/FloatingInput.md +11 -0
- data/docs/rules/Electrical/GpioOvercurrent.md +18 -0
- data/docs/rules/Electrical/I2CAddressConflict.md +5 -0
- data/docs/rules/Electrical/I2CPullupMissing.md +5 -0
- data/docs/rules/Electrical/LedOvercurrent.md +5 -0
- data/docs/rules/Electrical/MinimumResistance.md +14 -0
- data/docs/rules/Electrical/MissingBaseResistor.md +5 -0
- data/docs/rules/Electrical/MissingDecouplingCapacitor.md +5 -0
- data/docs/rules/Electrical/MissingFlybackDiode.md +18 -0
- data/docs/rules/Electrical/MissingPullResistor.md +17 -0
- data/docs/rules/Electrical/MissingSeriesResistor.md +3 -1
- data/docs/rules/Electrical/NoCommonGround.md +1 -0
- data/docs/rules/Electrical/RailPolarityMismatch.md +10 -0
- data/docs/rules/Electrical/ResistorPowerRating.md +5 -0
- data/docs/rules/Electrical/ReversePolarity.md +1 -1
- data/docs/rules/Electrical/SupplyOverload.md +11 -0
- data/docs/rules/Electrical/SupplyVoltageRange.md +1 -0
- data/docs/rules/Electrical/VoltageDomainMismatch.md +6 -0
- data/docs/rules/Intent/ConnectionMismatch.md +3 -1
- data/docs/rules/Intent/MeasurementUnavailable.md +10 -0
- data/docs/rules/Layout/AmbiguousSupplySource.md +5 -0
- data/docs/rules/Layout/BodyOverlap.md +11 -0
- data/docs/rules/Layout/HoleCovered.md +11 -0
- data/docs/rules/Layout/InvalidColor.md +3 -0
- data/docs/rules/Layout/InvalidOption.md +3 -0
- data/docs/rules/Layout/InvalidRoute.md +3 -0
- data/docs/rules/Layout/InvalidValue.md +3 -0
- data/docs/rules/Layout/InvalidWireId.md +3 -0
- data/docs/rules/Layout/LeadSpan.md +21 -0
- data/docs/rules/Layout/PartOverride.md +3 -0
- data/docs/rules/Layout/UnknownSupplySource.md +5 -0
- data/docs/rules/Layout/UnmatchedParts.md +3 -0
- data/docs/rules/Layout/WireOverIC.md +11 -0
- data/docs/rules/Lint/RedundantDisable.md +3 -0
- data/docs/rules/Lint/UnknownRuleInDisable.md +5 -0
- data/lib/breadkit/lint/baseline.rb +38 -0
- data/lib/breadkit/lint/rake_task.rb +22 -0
- data/lib/breadkit/lint/rules/electrical.rb +580 -30
- data/lib/breadkit/lint/rules/intent.rb +69 -4
- data/lib/breadkit/lint/rules/layout.rb +148 -0
- data/lib/breadkit/lint/rules/style.rb +36 -7
- data/lib/breadkit/lint/rules/support.rb +45 -11
- data/lib/breadkit/lint/rules.rb +205 -2
- data/lib/breadkit/lint/source_editor.rb +138 -0
- data/lib/breadkit/lint/version.rb +1 -1
- data/lib/breadkit/lint.rb +422 -59
- data/lib/guard/breadkit/templates/Guardfile +7 -0
- data/lib/guard/breadkit.rb +26 -0
- data/locales/en.yml +82 -0
- data/locales/ja.yml +130 -4
- data/locales/ko.yml +191 -0
- data/locales/zh.yml +191 -0
- data/package-lock.json +103 -0
- data/package.json +2 -1
- data/scripts/action-run.sh +51 -0
- data/scripts/build-rule-pages.mjs +87 -0
- data/scripts/check-rule-pages.mjs +18 -0
- data/scripts/pre-commit +24 -0
- data/sig/breadkit/lint.rbs +12 -1
- data/site/index.html +9 -6
- data/site/schemas/lint-v1.json +71 -0
- data/site/styles.css +13 -0
- metadata +64 -4
data/lib/breadkit/lint/rules.rb
CHANGED
|
@@ -24,6 +24,20 @@ module Breadkit
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
module Rules
|
|
27
|
+
module Lint
|
|
28
|
+
class UnknownRuleInDisable < BuiltinRule
|
|
29
|
+
rule "Lint/UnknownRuleInDisable", severity: :error, description: "A lint_disable names an unknown rule"
|
|
30
|
+
|
|
31
|
+
def check(_context); end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class RedundantDisable < BuiltinRule
|
|
35
|
+
rule "Lint/RedundantDisable", severity: :warning, description: "A lint_disable matches no offense"
|
|
36
|
+
|
|
37
|
+
def check(_context); end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
27
41
|
module Layout
|
|
28
42
|
class InvalidHole < DiagnosticRule
|
|
29
43
|
rule "Layout/InvalidHole", severity: :error, description: "Unknown board hole"
|
|
@@ -55,12 +69,66 @@ module Breadkit
|
|
|
55
69
|
def check(context) = emit_diagnostics(context)
|
|
56
70
|
end
|
|
57
71
|
|
|
72
|
+
class UnknownSupplySource < DiagnosticRule
|
|
73
|
+
rule "Layout/UnknownSupplySource", severity: :error, description: "Supply source is not a provided power output"
|
|
74
|
+
|
|
75
|
+
def check(context) = emit_diagnostics(context)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class AmbiguousSupplySource < DiagnosticRule
|
|
79
|
+
rule "Layout/AmbiguousSupplySource", severity: :error, description: "Supply source matches multiple power outputs"
|
|
80
|
+
|
|
81
|
+
def check(context) = emit_diagnostics(context)
|
|
82
|
+
end
|
|
83
|
+
|
|
58
84
|
class UnknownOption < DiagnosticRule
|
|
59
85
|
rule "Layout/UnknownOption", severity: :error, description: "Unknown component option"
|
|
60
86
|
|
|
61
87
|
def check(context) = emit_diagnostics(context)
|
|
62
88
|
end
|
|
63
89
|
|
|
90
|
+
class InvalidOption < DiagnosticRule
|
|
91
|
+
rule "Layout/InvalidOption", severity: :error, description: "Invalid component option value"
|
|
92
|
+
|
|
93
|
+
def check(context) = emit_diagnostics(context)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class InvalidValue < DiagnosticRule
|
|
97
|
+
rule "Layout/InvalidValue", severity: :error, description: "Invalid component value"
|
|
98
|
+
|
|
99
|
+
def check(context) = emit_diagnostics(context)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
class InvalidColor < DiagnosticRule
|
|
103
|
+
rule "Layout/InvalidColor", severity: :error, description: "Invalid component or wire color"
|
|
104
|
+
|
|
105
|
+
def check(context) = emit_diagnostics(context)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class InvalidRoute < DiagnosticRule
|
|
109
|
+
rule "Layout/InvalidRoute", severity: :error, description: "Invalid wire route"
|
|
110
|
+
|
|
111
|
+
def check(context) = emit_diagnostics(context)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
class InvalidWireId < DiagnosticRule
|
|
115
|
+
rule "Layout/InvalidWireId", severity: :error, description: "Wire ID conflicts with a board hole"
|
|
116
|
+
|
|
117
|
+
def check(context) = emit_diagnostics(context)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class UnmatchedParts < DiagnosticRule
|
|
121
|
+
rule "Layout/UnmatchedParts", severity: :warning, description: "Part glob matches no files"
|
|
122
|
+
|
|
123
|
+
def check(context) = emit_diagnostics(context)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class PartOverride < DiagnosticRule
|
|
127
|
+
rule "Layout/PartOverride", severity: :warning, description: "Custom part overrides a built-in part"
|
|
128
|
+
|
|
129
|
+
def check(context) = emit_diagnostics(context)
|
|
130
|
+
end
|
|
131
|
+
|
|
64
132
|
class UnplacedPin < DiagnosticRule
|
|
65
133
|
rule "Layout/UnplacedPin", severity: :error, description: "A component pin has no board hole"
|
|
66
134
|
|
|
@@ -85,6 +153,30 @@ module Breadkit
|
|
|
85
153
|
def check(context) = emit_diagnostics(context)
|
|
86
154
|
end
|
|
87
155
|
|
|
156
|
+
class HoleCovered < BuiltinRule
|
|
157
|
+
rule "Layout/HoleCovered", severity: :error, description: "A module or DIP body covers another lead or wire endpoint"
|
|
158
|
+
|
|
159
|
+
def check(context) = emit(context, :hole_covered)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class BodyOverlap < BuiltinRule
|
|
163
|
+
rule "Layout/BodyOverlap", severity: :error, description: "Two component bodies overlap"
|
|
164
|
+
|
|
165
|
+
def check(context) = emit(context, :body_overlap)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class WireOverIC < BuiltinRule
|
|
169
|
+
rule "Layout/WireOverIC", severity: :info, description: "A straight jumper crosses a DIP IC body"
|
|
170
|
+
|
|
171
|
+
def check(context) = emit(context, :wire_over_ic)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
class LeadSpan < BuiltinRule
|
|
175
|
+
rule "Layout/LeadSpan", severity: :error, description: "A component's leads cannot span the placed holes"
|
|
176
|
+
|
|
177
|
+
def check(context) = emit(context, :lead_span)
|
|
178
|
+
end
|
|
179
|
+
|
|
88
180
|
class NoFreeHole < DiagnosticRule
|
|
89
181
|
rule "Layout/NoFreeHole", severity: :error, description: "No free hole is available"
|
|
90
182
|
|
|
@@ -124,6 +216,20 @@ module Breadkit
|
|
|
124
216
|
def check(context) = emit(context, :floating_pins)
|
|
125
217
|
end
|
|
126
218
|
|
|
219
|
+
class FloatingInput < BuiltinRule
|
|
220
|
+
rule "Electrical/FloatingInput", severity: :info,
|
|
221
|
+
description: "An input net has no modeled drive or pull resistor", state_sensitive: true
|
|
222
|
+
|
|
223
|
+
def check(context) = emit(context, :floating_inputs)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class MissingPullResistor < BuiltinRule
|
|
227
|
+
rule "Electrical/MissingPullResistor", severity: :info,
|
|
228
|
+
description: "A supply-switched input has no modeled pull resistor"
|
|
229
|
+
|
|
230
|
+
def check(context) = emit(context, :missing_pull_resistors)
|
|
231
|
+
end
|
|
232
|
+
|
|
127
233
|
class DanglingWire < BuiltinRule
|
|
128
234
|
rule "Electrical/DanglingWire", severity: :warning, description: "A wire end has no other connection"
|
|
129
235
|
|
|
@@ -142,6 +248,20 @@ module Breadkit
|
|
|
142
248
|
def check(context) = emit(context, :missing_series_resistors)
|
|
143
249
|
end
|
|
144
250
|
|
|
251
|
+
class MinimumResistance < BuiltinRule
|
|
252
|
+
rule "Electrical/MinimumResistance", severity: :warning,
|
|
253
|
+
description: "A potentiometer can reach zero resistance in an LED current path", state_sensitive: true
|
|
254
|
+
|
|
255
|
+
def check(context) = emit(context, :minimum_resistances)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
class RailPolarityMismatch < BuiltinRule
|
|
259
|
+
rule "Electrical/RailPolarityMismatch", severity: :warning,
|
|
260
|
+
description: "A source's positive and negative terminals drive oppositely marked rails"
|
|
261
|
+
|
|
262
|
+
def check(context) = emit(context, :rail_polarity_mismatch)
|
|
263
|
+
end
|
|
264
|
+
|
|
145
265
|
class ReversePolarity < BuiltinRule
|
|
146
266
|
rule "Electrical/ReversePolarity", severity: :error, description: "A polarized part is connected backwards", state_sensitive: true
|
|
147
267
|
|
|
@@ -172,17 +292,94 @@ module Breadkit
|
|
|
172
292
|
def check(context) = emit(context, :label_conflicts)
|
|
173
293
|
end
|
|
174
294
|
|
|
295
|
+
class VoltageDomainMismatch < BuiltinRule
|
|
296
|
+
rule "Electrical/VoltageDomainMismatch", severity: :error,
|
|
297
|
+
description: "A signal exceeds a pin's voltage limit", state_sensitive: true
|
|
298
|
+
|
|
299
|
+
def check(context) = emit(context, :voltage_domain_mismatches)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
class ResistorPowerRating < BuiltinRule
|
|
303
|
+
rule "Electrical/ResistorPowerRating", severity: :error,
|
|
304
|
+
description: "A resistor exceeds its declared power rating", state_sensitive: true
|
|
305
|
+
|
|
306
|
+
def check(context) = emit(context, :resistor_power_ratings)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
class CapacitorVoltageRating < BuiltinRule
|
|
310
|
+
rule "Electrical/CapacitorVoltageRating", severity: :error,
|
|
311
|
+
description: "An electrolytic exceeds its declared voltage rating", state_sensitive: true
|
|
312
|
+
|
|
313
|
+
def check(context) = emit(context, :capacitor_voltage_ratings)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
class LedOvercurrent < BuiltinRule
|
|
317
|
+
rule "Electrical/LedOvercurrent", severity: :error,
|
|
318
|
+
description: "An LED exceeds its declared current limit", state_sensitive: true
|
|
319
|
+
|
|
320
|
+
def check(context) = emit(context, :led_overcurrent)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
class GpioOvercurrent < BuiltinRule
|
|
324
|
+
rule "Electrical/GpioOvercurrent", severity: :error,
|
|
325
|
+
description: "A modeled output current exceeds a pin's declared limit", state_sensitive: true
|
|
326
|
+
|
|
327
|
+
def check(context) = emit(context, :gpio_overcurrent)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
class SupplyOverload < BuiltinRule
|
|
331
|
+
rule "Electrical/SupplyOverload", severity: :error,
|
|
332
|
+
description: "A modeled load exceeds its supply current limit", state_sensitive: true
|
|
333
|
+
|
|
334
|
+
def check(context) = emit(context, :supply_overloads)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
class I2CAddressConflict < BuiltinRule
|
|
338
|
+
rule "Electrical/I2CAddressConflict", severity: :error,
|
|
339
|
+
description: "I2C devices on one bus use the same address", state_sensitive: true
|
|
340
|
+
|
|
341
|
+
def check(context) = emit(context, :i2c_address_conflicts)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
class I2CPullupMissing < BuiltinRule
|
|
345
|
+
rule "Electrical/I2CPullupMissing", severity: :info,
|
|
346
|
+
description: "An I2C bus has no modeled pull-up resistor", state_sensitive: true
|
|
347
|
+
|
|
348
|
+
def check(context) = emit(context, :i2c_pullup_missing)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
class MissingDecouplingCapacitor < BuiltinRule
|
|
352
|
+
rule "Electrical/MissingDecouplingCapacitor", severity: :info,
|
|
353
|
+
description: "A powered IC has no modeled capacitor across its supply pins", state_sensitive: true
|
|
354
|
+
|
|
355
|
+
def check(context) = emit(context, :missing_decoupling_capacitors)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
class MissingBaseResistor < BuiltinRule
|
|
359
|
+
rule "Electrical/MissingBaseResistor", severity: :info,
|
|
360
|
+
description: "A declared output drives a transistor base without a series resistor", state_sensitive: true
|
|
361
|
+
|
|
362
|
+
def check(context) = emit(context, :missing_base_resistors)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
class MissingFlybackDiode < BuiltinRule
|
|
366
|
+
rule "Electrical/MissingFlybackDiode", severity: :warning,
|
|
367
|
+
description: "An explicitly inductive load has no reverse flyback diode", state_sensitive: true
|
|
368
|
+
|
|
369
|
+
def check(context) = emit(context, :missing_flyback_diodes)
|
|
370
|
+
end
|
|
371
|
+
|
|
175
372
|
end
|
|
176
373
|
|
|
177
374
|
module Intent
|
|
178
375
|
class ConnectionMismatch < BuiltinRule
|
|
179
|
-
rule "Intent/ConnectionMismatch", severity: :error, description: "Wiring differs from declared expectations"
|
|
376
|
+
rule "Intent/ConnectionMismatch", severity: :error, description: "Wiring differs from declared expectations", state_sensitive: true
|
|
180
377
|
|
|
181
378
|
def check(context) = emit(context, :expectations)
|
|
182
379
|
end
|
|
183
380
|
|
|
184
381
|
class UnknownNet < BuiltinRule
|
|
185
|
-
rule "Intent/UnknownNet", severity: :error, description: "An expectation refers to an unknown net"
|
|
382
|
+
rule "Intent/UnknownNet", severity: :error, description: "An expectation refers to an unknown net", state_sensitive: true
|
|
186
383
|
|
|
187
384
|
def check(context)
|
|
188
385
|
emit_diagnostics(context)
|
|
@@ -190,6 +387,12 @@ module Breadkit
|
|
|
190
387
|
end
|
|
191
388
|
end
|
|
192
389
|
|
|
390
|
+
class MeasurementUnavailable < BuiltinRule
|
|
391
|
+
rule "Intent/MeasurementUnavailable", severity: :warning, description: "A requested DC value cannot be calculated", state_sensitive: true
|
|
392
|
+
|
|
393
|
+
def check(context) = emit(context, :expectations)
|
|
394
|
+
end
|
|
395
|
+
|
|
193
396
|
end
|
|
194
397
|
|
|
195
398
|
module Style
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "did_you_mean"
|
|
6
|
+
|
|
7
|
+
module Breadkit
|
|
8
|
+
module Lint
|
|
9
|
+
class SourceEditor
|
|
10
|
+
Edit = Struct.new(:start, :finish, :replacement, :description, :line, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def initialize(path, source: nil)
|
|
13
|
+
@path = path
|
|
14
|
+
@source = source || File.binread(path)
|
|
15
|
+
parsed = Prism.parse(@source)
|
|
16
|
+
@valid = parsed.errors.empty?
|
|
17
|
+
@calls = []
|
|
18
|
+
collect(parsed.value) if @valid
|
|
19
|
+
@calls_by_line = @calls.group_by { |call| call.location.start_line }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def annotate(offenses)
|
|
23
|
+
return unless @valid
|
|
24
|
+
|
|
25
|
+
offenses.each do |offense|
|
|
26
|
+
call = call_for(offense)
|
|
27
|
+
next unless call
|
|
28
|
+
node = offense.rule == "Layout/InvalidColor" && wire_color_node(call) || call
|
|
29
|
+
offense.column = node.location.start_character_column + 1
|
|
30
|
+
if node != call && node.location.start_line != offense.location.line
|
|
31
|
+
offense.location = Breadkit::SourceLocation.new(path: offense.location.path, line: node.location.start_line)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def plan(offenses)
|
|
37
|
+
return [] unless @valid
|
|
38
|
+
|
|
39
|
+
offenses.filter_map do |offense|
|
|
40
|
+
case offense.rule
|
|
41
|
+
when "Layout/InvalidColor" then color_edit(offense)
|
|
42
|
+
when "Lint/RedundantDisable" then disable_edit(offense)
|
|
43
|
+
end
|
|
44
|
+
end.uniq { |edit| [edit.start, edit.finish, edit.replacement] }.sort_by(&:start)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def apply(edits)
|
|
48
|
+
return false if edits.empty? || File.symlink?(@path)
|
|
49
|
+
raise Error, "source changed while fixing #{@path}" unless File.binread(@path) == @source
|
|
50
|
+
|
|
51
|
+
updated = @source.dup
|
|
52
|
+
edits.reverse_each { |edit| updated[edit.start...edit.finish] = edit.replacement }
|
|
53
|
+
mode = File.stat(@path).mode & 0o7777
|
|
54
|
+
Tempfile.create([".bklint-fix-", ".tmp"], File.dirname(@path)) do |temp|
|
|
55
|
+
temp.binmode
|
|
56
|
+
temp.chmod(mode)
|
|
57
|
+
temp.write(updated)
|
|
58
|
+
temp.flush
|
|
59
|
+
temp.fsync
|
|
60
|
+
temp.close
|
|
61
|
+
raise Error, "source changed while fixing #{@path}" unless File.binread(@path) == @source
|
|
62
|
+
File.rename(temp.path, @path)
|
|
63
|
+
end
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def collect(node)
|
|
70
|
+
return unless node
|
|
71
|
+
@calls << node if node.is_a?(Prism::CallNode) && node.receiver.nil?
|
|
72
|
+
node.compact_child_nodes.each { |child| collect(child) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def call_for(offense, name = nil)
|
|
76
|
+
location = offense.location
|
|
77
|
+
return unless location&.line && location.path && File.expand_path(location.path) == File.expand_path(@path)
|
|
78
|
+
|
|
79
|
+
calls = @calls_by_line[location.line] || @calls.select do |call|
|
|
80
|
+
call.location.start_line < location.line && location.line <= call.location.end_line
|
|
81
|
+
end
|
|
82
|
+
return unless calls.one?
|
|
83
|
+
return if name && calls.first.name != name
|
|
84
|
+
|
|
85
|
+
calls.first
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def wire_color_node(call)
|
|
89
|
+
return unless call.name == :wire
|
|
90
|
+
hashes = Array(call.arguments&.arguments).grep(Prism::KeywordHashNode)
|
|
91
|
+
entries = hashes.flat_map(&:elements).grep(Prism::AssocNode).select do |entry|
|
|
92
|
+
entry.key.is_a?(Prism::SymbolNode) && entry.key.value == "color"
|
|
93
|
+
end
|
|
94
|
+
entries.first.value if entries.one?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def color_edit(offense)
|
|
98
|
+
return if Array(offense.targets[:wires]).empty?
|
|
99
|
+
call = call_for(offense, :wire)
|
|
100
|
+
return unless call
|
|
101
|
+
node = wire_color_node(call)
|
|
102
|
+
return unless node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
|
|
103
|
+
|
|
104
|
+
color = node.is_a?(Prism::StringNode) ? node.unescaped : node.value
|
|
105
|
+
return unless color.match?(/\A[a-z]+\z/i) && !Breadkit::Color.valid?(color)
|
|
106
|
+
candidates = Breadkit::Color::NAMES.select { |name| DidYouMean::Levenshtein.distance(color.downcase, name) == 1 }
|
|
107
|
+
return unless candidates.one?
|
|
108
|
+
|
|
109
|
+
original = node.location.slice
|
|
110
|
+
corrected = if node.is_a?(Prism::SymbolNode) && original == ":#{color}"
|
|
111
|
+
":#{candidates.first}"
|
|
112
|
+
elsif node.is_a?(Prism::StringNode) && original.match?(/\A(["'])[a-z]+\1\z/i)
|
|
113
|
+
"#{original[0]}#{candidates.first}#{original[0]}"
|
|
114
|
+
end
|
|
115
|
+
return unless corrected
|
|
116
|
+
|
|
117
|
+
Edit.new(start: node.location.start_offset, finish: node.location.end_offset, replacement: corrected,
|
|
118
|
+
description: "replace wire color #{original} with #{corrected}", line: offense.location.line)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def disable_edit(offense)
|
|
122
|
+
call = call_for(offense, :lint_disable)
|
|
123
|
+
return unless call && call.location.start_line == call.location.end_line
|
|
124
|
+
|
|
125
|
+
line_start = @source.lines.take(call.location.start_line - 1).sum(&:bytesize)
|
|
126
|
+
newline = @source.index("\n", line_start)
|
|
127
|
+
line_end = newline ? newline + 1 : @source.bytesize
|
|
128
|
+
content_end = newline && @source.getbyte(newline - 1) == 13 ? newline - 1 : (newline || line_end)
|
|
129
|
+
before = @source.byteslice(line_start...call.location.start_offset)
|
|
130
|
+
after = @source.byteslice(call.location.end_offset...content_end)
|
|
131
|
+
return unless before.match?(/\A[ \t]*\z/) && after.match?(/\A[ \t]*\z/)
|
|
132
|
+
|
|
133
|
+
Edit.new(start: line_start, finish: line_end, replacement: "",
|
|
134
|
+
description: "remove unused lint_disable", line: offense.location.line)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|