Class: MaquinaComponents::Doctor

Inherits:
Object
  • Object
show all
Defined in:
lib/maquina_components/doctor.rb

Overview

Migration scanner behind rake maquina:doctor.

Run inside a consuming app, it looks for the app's own CSS/view/JS patterns that 0.6.0 makes redundant or outright breaks, and prints file:line plus a suggested replacement for each. Advisory only: it never edits anything and never fails a build.

Plain Ruby on purpose - no Rails, no extra gems - so it can also be run against a directory from the engine's own repo.

Defined Under Namespace

Classes: Finding

Constant Summary collapse

SEVERITIES =
{
  breaking: "BREAKING     - stops working in 0.6.0",
  review: "REVIEW       - still works, but 0.6.0 gives you a token for it",
  cleanup: "CLEANUP      - probably unnecessary in 0.6.0"
}.freeze
CSS_GLOBS =
[
  "app/assets/stylesheets/**/*.css",
  "app/assets/tailwind/**/*.css",
  "app/assets/**/*.css",
  "app/javascript/**/*.css",
  "app/views/**/*.css"
].freeze
VIEW_GLOBS =
[
  "app/views/**/*.erb",
  "app/views/**/*.html",
  "app/components/**/*.erb",
  "app/helpers/**/*.rb"
].freeze
JS_GLOBS =
[
  "app/javascript/**/*.js"
].freeze
EXCLUDED =
%r{/(node_modules|tmp|vendor|coverage)/|/app/assets/builds/}
COMPONENT_SELECTOR =

A component-owned selector: data-component or any data-*-part hook.

/\[data-(?:component|[a-z]+(?:-[a-z]+)*-part)\s*[~|^$*]?=/
UNIVERSAL_SELECTOR =

A rule whose every selector is the universal one, e.g. * or *, ::before, ::after — the shape of a Tailwind-v3-style preflight shim.

/\A\*(\s*,\s*(\*|::?[a-z-]+))*\z/
PRESENCE_ACTIVE =
/\[data-active\](?!\s*[~|^$*]?=)/
TAILWIND_ACTIVE_VARIANT =
/data-\[active\]/
SVG_DATA_URI =
/data:image\/svg\+xml/
MARK_TOKEN_ASSIGNMENT =

Assigning a data URI to one of the five mark properties IS the 0.6.0 pattern, so it must not be reported as a restated rule.

/--(?:checkbox-mark|checkbox-indeterminate|radio-mark|switch-thumb|select-chevron)-image\s*:/
RADIUS_DECL =
/(?:border-radius\s*:|@apply[^;{}]*\brounded(?:-[a-z0-9\[\].\/-]+)?\b)/
SHADOW_DECL =
/(?:box-shadow\s*:|@apply[^;{}]*\b(?:shadow|ring)(?:-[a-z0-9\[\].\/-]+)?\b)/
FOCUS_SELECTOR =
/:focus(-visible|-within)?\b/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(root) ⇒ Doctor

Returns a new instance of Doctor.



60
61
62
63
64
# File 'lib/maquina_components/doctor.rb', line 60

def initialize(root)
  @root = File.expand_path(root.to_s)
  @findings = []
  @scanned_files = 0
end

Instance Attribute Details

#findingsObject (readonly)

Returns the value of attribute findings.



58
59
60
# File 'lib/maquina_components/doctor.rb', line 58

def findings
  @findings
end

#rootObject (readonly)

Returns the value of attribute root.



58
59
60
# File 'lib/maquina_components/doctor.rb', line 58

def root
  @root
end

#scanned_filesObject (readonly)

Returns the value of attribute scanned_files.



58
59
60
# File 'lib/maquina_components/doctor.rb', line 58

def scanned_files
  @scanned_files
end

Instance Method Details

#findings_for(severity) ⇒ Object



73
74
75
# File 'lib/maquina_components/doctor.rb', line 73

def findings_for(severity)
  findings.select { |finding| finding.severity == severity }
end

#reportObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/maquina_components/doctor.rb', line 77

def report
  out = []
  out << "maquina_components doctor"
  out << "Scanned #{scanned_files} file#{"s" unless scanned_files == 1} under #{root}"
  out << ""

  if findings.empty?
    out << "No at-risk patterns found. Nothing to migrate for 0.6.0."
    out << ""
    return out.join("\n")
  end

  SEVERITIES.each do |severity, heading|
    group = findings_for(severity)
    next if group.empty?

    out << "#{heading} (#{group.size})"
    out << "-" * 72
    group.each do |finding|
      out << "  #{relative(finding.path)}:#{finding.line}  [#{finding.rule}]"
      out << "    #{finding.source}"
      finding.suggestion.each_line { |line| out << "    -> #{line.chomp}" }
      out << ""
    end
  end

  out << "Summary: " + SEVERITIES.keys.map { |s| "#{findings_for(s).size} #{s}" }.join(", ")
  out << "Advisory only - nothing was changed. See the 0.6.0 upgrade notes for the full token list."
  out << ""
  out.join("\n")
end

#runObject



66
67
68
69
70
71
# File 'lib/maquina_components/doctor.rb', line 66

def run
  each_file(CSS_GLOBS) { |path| scan_css(path) }
  each_file(VIEW_GLOBS) { |path| scan_markup(path) }
  each_file(JS_GLOBS) { |path| scan_markup(path) }
  self
end