glyphs 0.2.3 → 0.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c8e883474d143ec14502ef27013e1a264c0a89d76b7dd24ef29d2025d96ad5c
4
- data.tar.gz: afad4dcca3bda0162be398c66a7a7bb65c36af8facb9c4b5eb13f5d4d29a1d2c
3
+ metadata.gz: 5635cb76266896b0fb2d2e661844d27400be25b6e4a1190271279c7a4331517c
4
+ data.tar.gz: d8afd8c82d444afd15e951eba3aae5d1daaa0fce73efd7ac86fe725b9ce6dd09
5
5
  SHA512:
6
- metadata.gz: a9c94ace100a67479cb24e793dfd7bc9c71238bfd472f2d29be37076410c27e0cc69d48b2952fad4574bac1e5184770d0b19542284c06edb520fece72d6c1526
7
- data.tar.gz: 8d2cb2a5ca53b92884b28de6a2813326796b034754d5b6f14ab37b4fb7f54c1d49b6deb61dc53d75c0c668e8908447e9a5fdfd8bb71fbb5440c22a5bd9c15b61
6
+ metadata.gz: dffab87c9e9862dad15042135dc4c2ef7fb5785dd848210538b6e63c5263b8be6fc4a9b6c5276ac8ee7523fe239e08bb63007aff45af8ba52721fd52d774a946
7
+ data.tar.gz: 492c537c6ebb87013ec9795406b78cfb6b50e06328e2cc9b8b072a0c359e71d575a6d4853450c66b7d3c526492679ad156ef858a122cf848f4856ae957b14e55
data/CHANGELOG.md CHANGED
@@ -4,6 +4,27 @@
4
4
 
5
5
  ### Fixed
6
6
 
7
+ - **`Glyphs/IconResolution` no longer fails open on a missing icon directory.**
8
+ A directory that did not exist produced the same empty list as one that
9
+ existed with no SVGs, and both were read as "nothing to check" — so a library
10
+ that was never synced, or a `DefaultVariant` naming a variant the project does
11
+ not have, silently disabled the cop for every call site of that library. Green
12
+ cop, green CI, no validation at all, indefinitely. `load_icons` now returns
13
+ `nil` for an absent directory and `[]` for an empty one; the absent case warns
14
+ once per library/variant, naming the path. The synced-but-empty case still
15
+ passes silently. Refs #8
16
+
17
+ - **Cop options are declared, so RuboCop stops calling them unsupported.**
18
+ `config/default.yml` documented `Libraries` (`Glyphs/IconResolution`) and
19
+ `Mappings` (`Glyphs/LegacyIconHelper`) only in comments, and never mentioned
20
+ `LibraryComponents` (`Glyphs/LegacyIconHelper`, `Glyphs/PreferLibraryComponent`)
21
+ at all. RuboCop derives its supported-parameter list from the keys actually
22
+ present in that file, so it warned `does not support <param> parameter` on
23
+ every run — for four working options, one of which
24
+ `Glyphs/LegacyIconHelper` itself tells you to add. Each is now declared with an
25
+ empty default (a no-op merge), so the options are validated instead of
26
+ reported, with behaviour unchanged. Refs #7
27
+
7
28
  - **Declaration harvest unwraps trailing `.freeze`.** `ICONS = { "x" => :car }.freeze`
8
29
  (and `%i[a b].freeze`) used to yield a Prism `CallNode`, so hash/array values
9
30
  were never collected. Cross-file dynamics (`PhosphorIcon(@icon)` in a shared
@@ -13,6 +34,11 @@
13
34
 
14
35
  ### Added
15
36
 
37
+ - **`Glyphs/IconResolution` gained a `Strict` option** (default `false`). It
38
+ escalates the missing-icon-directory warning above to an offence, so projects
39
+ that depend on this cop can fail closed in CI instead of trusting a warning
40
+ not to scroll past.
41
+
16
42
  - **Dynamic icon calls are now resolved from source.** `SourceScanner` no longer
17
43
  silently skips `LucideIcon(some_var)` / `PhosphorIcon(tile[:icon])` — it harvests
18
44
  the literal name from two places so the pruner keeps it:
data/README.md CHANGED
@@ -210,6 +210,10 @@ Glyphs/LegacyIconHelper:
210
210
  # Mappings replaces the built-in defaults (_lucide/_phosphor/_hero/_heroicon/_tabler):
211
211
  # Mappings:
212
212
  # _custom: CustomIcon
213
+ # LibraryComponents merges over the built-in library => component map, so
214
+ # `icon("x", library: "customlib")` corrects to `CustomIcon("x")`:
215
+ # LibraryComponents:
216
+ # customlib: CustomIcon
213
217
  ```
214
218
 
215
219
  ### Glyphs/IconResolution
@@ -231,14 +235,38 @@ Glyphs/IconResolution:
231
235
  # Libraries merges over the built-in defaults (component => Dir/DefaultVariant):
232
236
  Libraries:
233
237
  PhosphorIcon: { Dir: phosphor, DefaultVariant: regular }
238
+ # Escalate a missing icon directory from a warning to an offence:
239
+ Strict: false
234
240
  ```
235
241
 
242
+ If the directory a library resolves to does not exist — the library was never synced, or
243
+ `DefaultVariant` names a variant you do not have — the cop cannot validate any of that
244
+ library's call sites. Rather than pass silently, it warns once per library/variant:
245
+
246
+ ```
247
+ [Glyphs/IconResolution] Icon directory `app/assets/svg/icons/phosphor/regular` not found,
248
+ so `PhosphorIcon` names are not validated. Sync the library or fix `IconsPath`/`Libraries`.
249
+ ```
250
+
251
+ Set `Strict: true` to make that an offence instead, so CI fails closed rather than reporting
252
+ green while checking nothing. A directory that exists but ships no SVGs is not a
253
+ misconfiguration and stays silent either way.
254
+
236
255
  ### Glyphs/PreferLibraryComponent
237
256
 
238
257
  ```ruby
239
258
  Icon(:house, library: :lucide) # => LucideIcon(:house)
240
259
  ```
241
260
 
261
+ ```yaml
262
+ Glyphs/PreferLibraryComponent:
263
+ Include:
264
+ - app/**/*.rb
265
+ # LibraryComponents merges over the built-in library => component map:
266
+ # LibraryComponents:
267
+ # customlib: CustomIcon
268
+ ```
269
+
242
270
  ## Migrating an app off icon helpers
243
271
 
244
272
  1. Add `gem "glyphs"`, `include Glyphs` where the helpers used to be included.
data/config/default.yml CHANGED
@@ -3,10 +3,13 @@ Glyphs/LegacyIconHelper:
3
3
  Enabled: true
4
4
  VersionAdded: '0.1.0'
5
5
  SafeAutoCorrect: true
6
- # Mappings replaces the built-in defaults when set:
7
- # Mappings:
6
+ # Replaces the built-in helper => component defaults when set, e.g.
8
7
  # _lucide: LucideIcon
9
8
  # _heroicon: HeroIcon
9
+ Mappings: {}
10
+ # Merges over the built-in library => component map, e.g.
11
+ # customlib: CustomIcon
12
+ LibraryComponents: {}
10
13
  DefaultLibraryComponent: HeroIcon
11
14
 
12
15
  Glyphs/IconResolution:
@@ -14,12 +17,18 @@ Glyphs/IconResolution:
14
17
  Enabled: true
15
18
  VersionAdded: '0.1.0'
16
19
  IconsPath: app/assets/svg/icons
17
- # Libraries merges over the built-in defaults:
18
- # Libraries:
20
+ # Merges over the built-in component => directory/variant defaults, e.g.
19
21
  # PhosphorIcon: { Dir: phosphor, DefaultVariant: light }
22
+ Libraries: {}
23
+ # An icon directory that does not exist warns once per library/variant.
24
+ # Strict turns that warning into an offence so CI fails closed.
25
+ Strict: false
20
26
 
21
27
  Glyphs/PreferLibraryComponent:
22
28
  Description: 'Prefer library-specific components over generic Icon(..., library: ...).'
23
29
  Enabled: true
24
30
  VersionAdded: '0.1.0'
25
31
  SafeAutoCorrect: true
32
+ # Merges over the built-in library => component map, e.g.
33
+ # customlib: CustomIcon
34
+ LibraryComponents: {}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Glyphs
4
- VERSION = "0.2.3"
4
+ VERSION = "0.2.4"
5
5
  end
@@ -11,6 +11,11 @@ module RuboCop
11
11
  # calls (`_lucide(:house)`) are validated. A literal `variant:` keyword is
12
12
  # honored; calls with dynamic names or variants are skipped.
13
13
  #
14
+ # A directory that does not exist is a configuration error, not "nothing
15
+ # to check": it warns once per library/variant, or — with `Strict` — adds
16
+ # an offence, so the cop can never quietly validate nothing. A directory
17
+ # that exists but ships no SVGs stays silent.
18
+ #
14
19
  # @example
15
20
  # # bad
16
21
  # LucideIcon(:non_existent_icon)
@@ -51,10 +56,14 @@ module RuboCop
51
56
 
52
57
  ICONIFY_PATTERN = /\biconify\s+(lucide|phosphor|heroicons)--([a-z0-9-]+)/
53
58
 
59
+ DEFAULT_ICONS_PATH = "app/assets/svg/icons"
60
+
54
61
  RAW_MSG = "Use `%{component}(:%{symbol}, class: \"%{rest}\")` instead of raw `iconify %{library}--…` class."
55
62
  RAW_DSTR_MSG = "Use `LucideIcon(name, class: ...)` etc. instead of building a raw " \
56
63
  "`iconify <library>--…` class string."
57
64
  MISSING_MSG = "Icon `%{name}` not found in %{library}/%{variant}. %{suggestion}"
65
+ MISSING_DIR_MSG = "Icon directory `%{path}` not found, so `%{component}` names are not validated. " \
66
+ "Sync the library or fix `IconsPath`/`Libraries`."
58
67
 
59
68
  def on_str(node)
60
69
  return if node.parent&.dstr_type?
@@ -97,7 +106,7 @@ module RuboCop
97
106
  @libraries ||= DEFAULT_LIBRARIES.merge(cop_config["Libraries"] || {})
98
107
  end
99
108
 
100
- def check_call(node, _component, library)
109
+ def check_call(node, component, library)
101
110
  name = literal_name(node.first_argument)
102
111
  return unless name
103
112
 
@@ -105,6 +114,7 @@ module RuboCop
105
114
  return if variant == :dynamic
106
115
 
107
116
  available = available_icons(library["Dir"], variant)
117
+ return report_missing_directory(node, component, library["Dir"], variant) if available.nil?
108
118
  return if available.empty?
109
119
  return if available.include?(name)
110
120
 
@@ -124,6 +134,21 @@ module RuboCop
124
134
  end
125
135
  end
126
136
 
137
+ # The directory the cop was told to read is missing, so every call for
138
+ # this library goes unchecked. Report it rather than pass silently.
139
+ def report_missing_directory(node, component, library_dir, variant)
140
+ message = format(MISSING_DIR_MSG, path: configured_directory(library_dir, variant), component:)
141
+ return add_offense(node.first_argument, message:) if cop_config["Strict"]
142
+
143
+ self.class.warn_once("[Glyphs/IconResolution] #{message}")
144
+ end
145
+
146
+ # Built from the unexpanded `IconsPath` so the message shows the path as
147
+ # the project wrote it, not an absolute machine-specific one.
148
+ def configured_directory(library_dir, variant)
149
+ self.class.directory_for(cop_config["IconsPath"] || DEFAULT_ICONS_PATH, library_dir, variant)
150
+ end
151
+
127
152
  def variant_for(node, library)
128
153
  pair = variant_pair(node)
129
154
  return library["DefaultVariant"] if pair.nil?
@@ -258,7 +283,7 @@ module RuboCop
258
283
  end
259
284
 
260
285
  def icons_base_path
261
- @icons_base_path ||= File.expand_path(cop_config["IconsPath"] || "app/assets/svg/icons", Dir.pwd)
286
+ @icons_base_path ||= File.expand_path(cop_config["IconsPath"] || DEFAULT_ICONS_PATH, Dir.pwd)
262
287
  end
263
288
 
264
289
  def available_icons(library_dir, variant)
@@ -266,16 +291,39 @@ module RuboCop
266
291
  end
267
292
 
268
293
  class << self
294
+ # nil when the directory is absent, [] when it exists but ships no
295
+ # SVGs — the caller has to tell those apart. Memoized with `key?` (not
296
+ # `||=`) so a nil result is not re-probed on every call site.
269
297
  def available_icons_for(base_path, library_dir, variant)
270
298
  @available_icons_cache ||= {}
271
- @available_icons_cache[[base_path, library_dir, variant]] ||= load_icons(base_path, library_dir, variant)
299
+ key = [base_path, library_dir, variant]
300
+ return @available_icons_cache[key] if @available_icons_cache.key?(key)
301
+
302
+ @available_icons_cache[key] = load_icons(directory_for(base_path, library_dir, variant))
303
+ end
304
+
305
+ def directory_for(base_path, library_dir, variant)
306
+ File.join(*[base_path, library_dir, variant].compact.reject { |part| part.to_s.empty? })
307
+ end
308
+
309
+ # RuboCop builds a fresh cop instance per file, so deduplicating the
310
+ # missing-directory warning has to outlive the instance.
311
+ def warn_once(message)
312
+ @warned_messages ||= {}
313
+ return if @warned_messages.key?(message)
314
+
315
+ @warned_messages[message] = true
316
+ warn message
317
+ end
318
+
319
+ def reset_warnings!
320
+ @warned_messages = nil
272
321
  end
273
322
 
274
323
  private
275
324
 
276
- def load_icons(base_path, library_dir, variant)
277
- path = File.join(*[base_path, library_dir, variant].compact.reject { |part| part.to_s.empty? })
278
- return [] unless Dir.exist?(path)
325
+ def load_icons(path)
326
+ return nil unless Dir.exist?(path)
279
327
 
280
328
  Dir.children(path).filter_map { |file| File.basename(file, ".svg") if file.end_with?(".svg") }.sort
281
329
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: glyphs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson