Skip to content

fix(rubocop): report a missing icon directory instead of failing open - #10

Merged
mhenrixon merged 1 commit into
mainfrom
issue-8-report-missing-icon-directory
Jul 25, 2026
Merged

fix(rubocop): report a missing icon directory instead of failing open#10
mhenrixon merged 1 commit into
mainfrom
issue-8-report-missing-icon-directory

Conversation

@mhenrixon

Copy link
Copy Markdown
Collaborator

Fixes #8.

The problem

load_icons returned [] both when the icon directory was absent and when it existed with no SVGs, and check_call read [] as "nothing to validate". So a library that was never synced — or a DefaultVariant naming a variant the project does not have — silently disabled the cop for every call site of that library. No offence, no warning, no output at all.

That is the worst shape a bug can take in a linter: the cop is green, CI is green, and the guarantee it exists to provide is simply gone. Nothing ever draws attention to it.

The [] guard is load-bearing, though — a library that ships no icons must not produce an offence on every call. So the fix is not to delete the guard, it is to stop conflating two different states.

The fix

load_icons returns nil for an absent directory and [] for an empty one. check_call then branches:

State Behaviour
nil — directory absent warn once per library/variant, naming the path (offence under Strict)
[] — synced, no SVGs skip, exactly as before
icons present unchanged: membership check, fuzzy suggestion, autocorrect

Reporting is a warning by default and an offence under the new Strict option, per the issue's suggestion. Offence-by-default would be louder but is a breaking change: every project that references a library it does not sync would start failing CI on upgrade. Strict lets projects that depend on this cop opt into failing closed.

Test plan

  • Warns naming the missing path instead of silently validating nothing.
  • Warns once per library/variant, not once per call site — asserted across separate cop runs, since that is the real scenario (different files).
  • Strict: true adds an offence at the call site, emits no warning, and offers no autocorrection.
  • A directory that exists but holds no SVGs stays silent — new spec/fixtures/svg/icons/emptylib/ fixture pins the load-bearing guard.
  • Every existing resolution / suggestion / autocorrect spec is untouched and still passes.

End-to-end, using the issue's own reproduction

A project syncing only Phosphor light, so phosphor/regular (the built-in DefaultVariant) is absent:

BEFORE   icon_probe.rb:2  Icon bogus-lucide-xyzzy not found in lucide/outline
         icon_probe.rb:3  Icon bogus-heroicon-xyzzy not found in heroicons/outline
         2 offences  -- the bogus Phosphor name passed

AFTER    [Glyphs/IconResolution] Icon directory app/assets/svg/icons/phosphor/regular
                                 not found, so PhosphorIcon names are not validated.
         ...same 2 offences, byte-identical

STRICT   icon_probe.rb:1  Icon directory app/assets/svg/icons/phosphor/regular not found...
         3 offences

Also verified on a real two-file run: 3 Phosphor call sites across 2 files produce exactly 1 warning.

Suite

  • bundle exec rubocop lib spec — clean
  • bundle exec rspec — 140 examples, 0 failures

Deviations & judgment calls

  • Warn dedup had to live on the class, not the instance. RuboCop's Runner mobilizes a fresh Team — and therefore fresh cop instances — per file, so instance state would warn once per file rather than once per run. This is the same reason the existing icons cache is class-level. Added reset_warnings! so specs are order-independent; precedent is the gem's existing Glyphs.reset_cache!.

  • The memo moved from ||= to key?. @available_icons_cache[key] ||= ... never memoizes a nil, so an absent directory would re-probe the filesystem at every call site. That is the scan hot path, so the key? form keeps it to one Dir.exist? per library/variant.

  • Message paths are built from the unexpanded IconsPath. icons_base_path expands against Dir.pwd, so reusing it would put absolute machine-specific paths into offence messages and make specs unportable. A second one-line builder renders the path as the project wrote it.

  • Omitted VersionChanged from the config/default.yml entry. RuboCop convention is to bump it when a cop gains a parameter, but that means naming the next release number, and rake release owns versioning — a feature PR guessing 0.3.0 would be wrong if the release lands as 0.2.4. Happy to add it if you would rather pin it now.

  • Strict offences anchor on the name argument (the same node as the missing-icon offence) and are deliberately not autocorrectable — there is no safe edit for "this directory does not exist".

  • The fix(rubocop): declare the cop options config/default.yml only documented #9 drift guard earned itself immediately. Adding cop_config["Strict"] without declaring it in config/default.yml failed spec/rubocop/plugin_spec.rb before I ran anything by hand — the exact class of bug Glyphs/IconResolution: Libraries is documented in config/default.yml but not declared, so RuboCop warns it is unsupported #7 was about, caught automatically one PR later.

## Summary

`Glyphs/IconResolution` collapsed "directory absent" and "directory exists but
holds no SVGs" into the same empty array, and read both as "nothing to check".
A library that was never synced — or a `DefaultVariant` naming a variant the
project does not have — therefore disabled the cop for every call site of that
library, with no offence, no warning, and no output of any kind. Green cop,
green CI, guarantee silently absent.

`load_icons` now returns `nil` for an absent directory and `[]` for an empty
one. `check_call` reports the absent case (naming the path) and keeps skipping
the empty one, which is load-bearing: a synced-but-empty library is not a
misconfiguration.

Reporting is a warning by default, deduplicated per library/variant for the
process — RuboCop mobilizes a fresh cop instance per file, so the dedup lives on
the class. The new `Strict` option escalates it to an offence so projects that
depend on this cop can fail closed in CI.

## Test Coverage

- warns naming the missing path instead of silently validating nothing
- warns once per library/variant, not once per call site, across separate runs
- Strict: adds an offence at the call site, emits no warning, no autocorrection
- exists-but-empty directory stays silent (new spec/fixtures/svg/icons/emptylib)
- every existing resolution/suggestion/autocorrect spec unchanged

## Verification

- [x] bundle exec rubocop lib spec passes
- [x] bundle exec rspec passes (140 examples)
- [x] Reproduced the issue's scenario: before, the bogus Phosphor name passed;
      after, the missing phosphor/regular directory is named, with the Lucide
      and Heroicons offences byte-identical
- [x] Real two-file run: 3 Phosphor call sites produce exactly 1 warning

Refs #8
@mhenrixon mhenrixon self-assigned this Jul 25, 2026
@mhenrixon mhenrixon added the bug Something isn't working label Jul 25, 2026
@mhenrixon
mhenrixon merged commit c40c883 into main Jul 25, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Glyphs/IconResolution silently skips validation when the icon directory is missing (fails open)

1 participant