Class: RuboCop::Cop::Glyphs::IconResolution
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::Glyphs::IconResolution
- Extended by:
- AutoCorrector
- Defined in:
- lib/rubocop/cop/glyphs/icon_resolution.rb
Overview
Validates that statically-known icon names exist in the synced SVG
directories (IconsPath), suggests close matches for typos, and flags
raw iconify class strings.
Both Glyphs component calls (LucideIcon(:house)) and legacy helper
calls (_lucide(:house)) are validated. A literal variant: keyword is
honored; calls with dynamic names or variants are skipped.
A directory that does not exist is a configuration error, not "nothing
to check": it warns once per library/variant, or — with Strict — adds
an offence, so the cop can never quietly validate nothing. A directory
that exists but ships no SVGs stays silent.
Constant Summary collapse
- DEFAULT_LIBRARIES =
{ "LucideIcon" => { "Dir" => "lucide", "DefaultVariant" => "outline" }, "PhosphorIcon" => { "Dir" => "phosphor", "DefaultVariant" => "regular" }, "HeroIcon" => { "Dir" => "heroicons", "DefaultVariant" => "outline" }, "TablerIcon" => { "Dir" => "tabler", "DefaultVariant" => "outline" }, "SidekickIcon" => { "Dir" => "sidekickicons", "DefaultVariant" => "outline" } }.freeze
- LEGACY_HELPERS =
{ "_lucide" => "LucideIcon", "_phosphor" => "PhosphorIcon", "_hero" => "HeroIcon", "_heroicon" => "HeroIcon", "_tabler" => "TablerIcon" }.freeze
- ICONIFY_PREFIX_TO_COMPONENT =
{ "lucide" => "LucideIcon", "phosphor" => "PhosphorIcon", "heroicons" => "HeroIcon" }.freeze
- KNOWN_LUCIDE_RENAMES =
{ "alert-triangle" => "triangle-alert", "alert-circle" => "circle-alert", "x-circle" => "circle-x" }.freeze
- ICONIFY_PATTERN =
/\biconify\s+(lucide|phosphor|heroicons)--([a-z0-9-]+)/- DEFAULT_ICONS_PATH =
"app/assets/svg/icons"- RAW_MSG =
"Use `%{component}(:%{symbol}, class: \"%{rest}\")` instead of raw `iconify %{library}--…` class."- RAW_DSTR_MSG =
"Use `LucideIcon(name, class: ...)` etc. instead of building a raw " \ "`iconify <library>--…` class string."
- MISSING_MSG =
"Icon `%{name}` not found in %{library}/%{variant}. %{suggestion}"- MISSING_DIR_MSG =
"Icon directory `%{path}` not found, so `%{component}` names are not validated. " \ "Sync the library or fix `IconsPath`/`Libraries`."
Class Method Summary collapse
-
.available_icons_for(base_path, library_dir, variant) ⇒ Object
nil when the directory is absent, [] when it exists but ships no SVGs — the caller has to tell those apart.
- .directory_for(base_path, library_dir, variant) ⇒ Object
- .reset_warnings! ⇒ Object
-
.warn_once(message) ⇒ Object
RuboCop builds a fresh cop instance per file, so deduplicating the missing-directory warning has to outlive the instance.
Instance Method Summary collapse
Class Method Details
.available_icons_for(base_path, library_dir, variant) ⇒ Object
nil when the directory is absent, [] when it exists but ships no
SVGs — the caller has to tell those apart. Memoized with key? (not
||=) so a nil result is not re-probed on every call site.
297 298 299 300 301 302 303 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 297 def available_icons_for(base_path, library_dir, variant) @available_icons_cache ||= {} key = [base_path, library_dir, variant] return @available_icons_cache[key] if @available_icons_cache.key?(key) @available_icons_cache[key] = load_icons(directory_for(base_path, library_dir, variant)) end |
.directory_for(base_path, library_dir, variant) ⇒ Object
305 306 307 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 305 def directory_for(base_path, library_dir, variant) File.join(*[base_path, library_dir, variant].compact.reject { |part| part.to_s.empty? }) end |
.reset_warnings! ⇒ Object
319 320 321 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 319 def reset_warnings! @warned_messages = nil end |
.warn_once(message) ⇒ Object
RuboCop builds a fresh cop instance per file, so deduplicating the missing-directory warning has to outlive the instance.
311 312 313 314 315 316 317 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 311 def warn_once() @warned_messages ||= {} return if @warned_messages.key?() @warned_messages[] = true warn end |
Instance Method Details
#on_dstr(node) ⇒ Object
74 75 76 77 78 79 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 74 def on_dstr(node) combined = node.children.filter_map { |child| child.str_type? ? child.value : "INTERPOLATED" }.join return unless combined.include?("iconify ") add_offense(node, message: RAW_DSTR_MSG) end |
#on_send(node) ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 81 def on_send(node) return unless node.receiver.nil? component = component_for(node.method_name) return unless component library = libraries[component] return unless library check_call(node, component, library) rescue StandardError => e source = node.location.expression.source_buffer.name warn "[Glyphs/IconResolution] suppressed #{e.class}: #{e.} at #{source}:#{node.first_line}" end |
#on_str(node) ⇒ Object
68 69 70 71 72 |
# File 'lib/rubocop/cop/glyphs/icon_resolution.rb', line 68 def on_str(node) return if node.parent&.dstr_type? check_iconify_string(node, node.value) end |