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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.yamllint +16 -0
  3. data/CHANGELOG.md +30 -2
  4. data/README.md +55 -40
  5. data/action.yml +42 -0
  6. data/config/default.yml +33 -0
  7. data/docs/REFERENCE.md +129 -0
  8. data/docs/rules/Electrical/CapacitorVoltageRating.md +5 -0
  9. data/docs/rules/Electrical/FloatingInput.md +11 -0
  10. data/docs/rules/Electrical/GpioOvercurrent.md +18 -0
  11. data/docs/rules/Electrical/I2CAddressConflict.md +5 -0
  12. data/docs/rules/Electrical/I2CPullupMissing.md +5 -0
  13. data/docs/rules/Electrical/LedOvercurrent.md +5 -0
  14. data/docs/rules/Electrical/MinimumResistance.md +14 -0
  15. data/docs/rules/Electrical/MissingBaseResistor.md +5 -0
  16. data/docs/rules/Electrical/MissingDecouplingCapacitor.md +5 -0
  17. data/docs/rules/Electrical/MissingFlybackDiode.md +18 -0
  18. data/docs/rules/Electrical/MissingPullResistor.md +17 -0
  19. data/docs/rules/Electrical/MissingSeriesResistor.md +3 -1
  20. data/docs/rules/Electrical/NoCommonGround.md +1 -0
  21. data/docs/rules/Electrical/RailPolarityMismatch.md +10 -0
  22. data/docs/rules/Electrical/ResistorPowerRating.md +5 -0
  23. data/docs/rules/Electrical/ReversePolarity.md +1 -1
  24. data/docs/rules/Electrical/SupplyOverload.md +11 -0
  25. data/docs/rules/Electrical/SupplyVoltageRange.md +1 -0
  26. data/docs/rules/Electrical/VoltageDomainMismatch.md +6 -0
  27. data/docs/rules/Intent/ConnectionMismatch.md +3 -1
  28. data/docs/rules/Intent/MeasurementUnavailable.md +10 -0
  29. data/docs/rules/Layout/AmbiguousSupplySource.md +5 -0
  30. data/docs/rules/Layout/BodyOverlap.md +11 -0
  31. data/docs/rules/Layout/HoleCovered.md +11 -0
  32. data/docs/rules/Layout/InvalidColor.md +3 -0
  33. data/docs/rules/Layout/InvalidOption.md +3 -0
  34. data/docs/rules/Layout/InvalidRoute.md +3 -0
  35. data/docs/rules/Layout/InvalidValue.md +3 -0
  36. data/docs/rules/Layout/InvalidWireId.md +3 -0
  37. data/docs/rules/Layout/LeadSpan.md +21 -0
  38. data/docs/rules/Layout/PartOverride.md +3 -0
  39. data/docs/rules/Layout/UnknownSupplySource.md +5 -0
  40. data/docs/rules/Layout/UnmatchedParts.md +3 -0
  41. data/docs/rules/Layout/WireOverIC.md +11 -0
  42. data/docs/rules/Lint/RedundantDisable.md +3 -0
  43. data/docs/rules/Lint/UnknownRuleInDisable.md +5 -0
  44. data/lib/breadkit/lint/baseline.rb +38 -0
  45. data/lib/breadkit/lint/rake_task.rb +22 -0
  46. data/lib/breadkit/lint/rules/electrical.rb +580 -30
  47. data/lib/breadkit/lint/rules/intent.rb +69 -4
  48. data/lib/breadkit/lint/rules/layout.rb +148 -0
  49. data/lib/breadkit/lint/rules/style.rb +36 -7
  50. data/lib/breadkit/lint/rules/support.rb +45 -11
  51. data/lib/breadkit/lint/rules.rb +205 -2
  52. data/lib/breadkit/lint/source_editor.rb +138 -0
  53. data/lib/breadkit/lint/version.rb +1 -1
  54. data/lib/breadkit/lint.rb +422 -59
  55. data/lib/guard/breadkit/templates/Guardfile +7 -0
  56. data/lib/guard/breadkit.rb +26 -0
  57. data/locales/en.yml +82 -0
  58. data/locales/ja.yml +130 -4
  59. data/locales/ko.yml +191 -0
  60. data/locales/zh.yml +191 -0
  61. data/package-lock.json +103 -0
  62. data/package.json +2 -1
  63. data/scripts/action-run.sh +51 -0
  64. data/scripts/build-rule-pages.mjs +87 -0
  65. data/scripts/check-rule-pages.mjs +18 -0
  66. data/scripts/pre-commit +24 -0
  67. data/sig/breadkit/lint.rbs +12 -1
  68. data/site/index.html +9 -6
  69. data/site/schemas/lint-v1.json +71 -0
  70. data/site/styles.css +13 -0
  71. metadata +64 -4
@@ -0,0 +1,10 @@
1
+ # Intent/MeasurementUnavailable
2
+
3
+ An `expect_voltage` or `expect_current` declaration cannot be checked because
4
+ the DC model has no value for that net or component. The warning includes the
5
+ reason, such as an active GPIO output with an unknown drive state or a floating
6
+ voltage reference.
7
+
8
+ Connect a grounded source and supported passive parts, or check the circuit
9
+ with a simulator that models the unsupported device. This warning does not
10
+ mean the declared range passed.
@@ -0,0 +1,5 @@
1
+ # Layout/AmbiguousSupplySource
2
+
3
+ `supply from:` refers to a pin that matches more than one provided power output. Give each output a distinct positive pin in the part definition so the source has one matching return pin and voltage.
4
+
5
+ This error prevents dependent electrical checks from running because the intended source cannot be determined.
@@ -0,0 +1,11 @@
1
+ # Layout/BodyOverlap
2
+
3
+ Two component bodies intersect even though their leads use different holes. This rule checks rectangular modules with `render.size_mm`, DIP bodies whose complete pin rows establish their position, and circular 5 mm LED bodies (`led_5mm` or `rgb_led_5mm`). Touching edges do not count as an overlap.
4
+
5
+ ```ruby
6
+ board :half
7
+ led :D1, anode: "a10", cathode: "a11"
8
+ led :D2, anode: "b11", cathode: "b12"
9
+ ```
10
+
11
+ Move one part until its body clears the other. The DIP rectangle follows the rendered body between its pin rows; incomplete or misaligned DIP placements are skipped. Other component shapes without reliable body dimensions are not checked. Module comparisons are skipped when the installed Breadkit version cannot provide body bounds.
@@ -0,0 +1,11 @@
1
+ # Layout/HoleCovered
2
+
3
+ A module or DIP body covers a hole used by another component lead or an electrical wire endpoint. The check uses the module's `render.size_mm`, or a complete DIP pin arrangement, and physical hole positions. Move the lead or wire endpoint to a visible hole; a hole on the same conductive strip is fine if it is outside the body.
4
+
5
+ ```ruby
6
+ board :half
7
+ part :U1, :pico, at: "d1"
8
+ resistor :R1, "330", pins: %w[e10 a25] # e10 is under U1
9
+ ```
10
+
11
+ The owner's own pins, holes on the body boundary, and visual wires (`electrical: false`) are ignored. A standard DIP sits in the breadboard ravine, so it normally covers no holes. Module checks are skipped when an older Breadkit version cannot provide body geometry.
@@ -0,0 +1,3 @@
1
+ # Layout/InvalidColor
2
+
3
+ A component or wire color is not a valid CSS color. Use a named color, hex, `rgb()`, or `hsl()`.
@@ -0,0 +1,3 @@
1
+ # Layout/InvalidOption
2
+
3
+ A component attribute has a value outside its part definition. Use an allowed value.
@@ -0,0 +1,3 @@
1
+ # Layout/InvalidRoute
2
+
3
+ A wire route is not supported. Use a route accepted by the breadkit DSL.
@@ -0,0 +1,3 @@
1
+ # Layout/InvalidValue
2
+
3
+ A component value cannot be parsed for its part category. Check digits and unit suffixes.
@@ -0,0 +1,3 @@
1
+ # Layout/InvalidWireId
2
+
3
+ A wire ID looks like a board hole and makes references ambiguous. Choose a distinct ID such as `W20`.
@@ -0,0 +1,21 @@
1
+ # Layout/LeadSpan
2
+
3
+ A two-lead part is placed farther apart than its declared `max_lead_span_mm`. The rule measures the straight-line distance between the two board holes using the board's 2.54 mm pitch.
4
+
5
+ ```yaml
6
+ id: limited_resistor
7
+ category: passive
8
+ placement: leads
9
+ pins:
10
+ - {num: 1, name: "1"}
11
+ - {num: 2, name: "2"}
12
+ max_lead_span_mm: 7.62
13
+ ```
14
+
15
+ ```ruby
16
+ board :half
17
+ use_parts "limited_resistor.yml"
18
+ part :R1, :limited_resistor, pins: %w[a10 a14] # 10.16 mm exceeds 7.62 mm
19
+ ```
20
+
21
+ Move the holes closer together or choose a part with a suitable lead span. Parts without declared `max_lead_span_mm` and incomplete placements are skipped; the rule does not assume a generic lead length.
@@ -0,0 +1,3 @@
1
+ # Layout/PartOverride
2
+
3
+ A custom part replaces a built-in definition. Verify its pinout and attributes before using it.
@@ -0,0 +1,5 @@
1
+ # Layout/UnknownSupplySource
2
+
3
+ `supply from:` refers to a pin that is not a provided power output of an offboard component. Check the component reference and choose one of its declared power outputs, such as `UNO.5V`.
4
+
5
+ This error prevents dependent electrical checks from running because the intended source cannot be connected.
@@ -0,0 +1,3 @@
1
+ # Layout/UnmatchedParts
2
+
3
+ The `use_parts` path matches no files. Check its spelling and location relative to the circuit file.
@@ -0,0 +1,11 @@
1
+ # Layout/WireOverIC
2
+
3
+ A straight electrical jumper crosses a DIP IC body in the layout. This informational finding helps when planning a build where the jumper should not pass above the chip. Route the wire around the chip if clearance matters.
4
+
5
+ ```ruby
6
+ board :half
7
+ ic :U1, :ne555, at: "e10"
8
+ wire "a11", "j11" # Crosses U1
9
+ ```
10
+
11
+ The rule estimates the standard DIP body from all placed pins: it requires two pin rows separated by three hole pitches. It checks only straight electrical wires whose endpoints are board holes. Curved and edge routes, visual alternatives, incomplete ICs, and non-DIP packages are outside this check.
@@ -0,0 +1,3 @@
1
+ # Lint/RedundantDisable
2
+
3
+ A `lint_disable` declaration suppresses no offense. Remove it or correct its rule and target.
@@ -0,0 +1,5 @@
1
+ # Lint/UnknownRuleInDisable
2
+
3
+ A `lint_disable` declaration names a rule that Breadkit Lint does not know.
4
+ Check the rule name with `bklint --list-rules`; unknown names do not suppress
5
+ any findings.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadkit
4
+ module Lint
5
+ class Baseline
6
+ def initialize(path)
7
+ @path = File.expand_path(path)
8
+ end
9
+
10
+ def write(files)
11
+ raise Error, "cannot baseline fatal errors" if files.any? { |file| file[:offenses].any? { |item| item.rule.start_with?("Fatal/") } }
12
+
13
+ entries = files.flat_map { |file| file[:offenses].map { |item| entry(file, item) } }.uniq
14
+ File.write(@path, JSON.pretty_generate(schema_version: 1, entries: entries) + "\n", encoding: "UTF-8")
15
+ entries.length
16
+ end
17
+
18
+ def filter(files)
19
+ data = JSON.parse(File.read(@path, encoding: "UTF-8"))
20
+ raise Error, "invalid baseline: expected schema_version 1 and entries" unless data.is_a?(Hash) && data["schema_version"] == 1 && data["entries"].is_a?(Array) && data["entries"].all? { |item| item.is_a?(Hash) }
21
+
22
+ known = data["entries"].to_set
23
+ files.map { |file| file.merge(offenses: file[:offenses].reject { |item| !item.rule.start_with?("Fatal/") && known.include?(entry(file, item)) }) }
24
+ end
25
+
26
+ private
27
+
28
+ def entry(file, item)
29
+ path = item.location&.path || file[:path]
30
+ relative = Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(File.dirname(@path))).to_s.tr("\\", "/")
31
+ targets = item.targets.to_h.sort_by { |key, _value| key.to_s }.to_h.transform_keys(&:to_s)
32
+ .transform_values { |value| Array(value).map(&:to_s).sort }
33
+ { "path" => relative, "rule" => item.rule, "severity" => item.severity,
34
+ "message" => item.message, "state" => item.state, "targets" => targets }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rake/tasklib"
4
+ require "breadkit/lint"
5
+
6
+ module Breadkit
7
+ class RakeTask < Rake::TaskLib
8
+ attr_accessor :files, :options
9
+
10
+ def initialize(name = :breadkit)
11
+ super()
12
+ @files = []
13
+ @options = []
14
+ yield self if block_given?
15
+ desc "Lint Breadkit circuits"
16
+ task name do
17
+ status = Breadkit::Lint::CLI.new.run(Array(options) + Array(files))
18
+ raise Breadkit::Lint::Error, "bklint failed with status #{status}" unless status.zero?
19
+ end
20
+ end
21
+ end
22
+ end