ruby_native 0.17.2 → 0.17.3

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: a20e456fe2c38a3885bd29bc796da70a6ef238bf30de87297ca14f9f407d783d
4
- data.tar.gz: b266e12b4c911bb57ba5c21eb6748f270a13a7449e27296fb5bf5fefb4b20d96
3
+ metadata.gz: 0c51153c763a49fbf4d88fa288adcfe46ef69402685a9ccc8ccdf9bfe9b35da8
4
+ data.tar.gz: 624eb747e1fccc0bf7e14d7b4a96822ea98fbd69ff9902cca01920e29997ac28
5
5
  SHA512:
6
- metadata.gz: 7a59080e79c82b040910d146ad1b3f214ef247a1f251783ba951400bf099bc1f161b41195b2230bb0da977988ae7850be1be6aff1e4f89c16a539b7b7dcef8f3
7
- data.tar.gz: 289783ff033617cc3b77f697334e861985f98786562d978b923118c56638c0411931df89929bf9c771cbe77a88d71f38d6f177fbb1762c66badfdb039fa7379c
6
+ metadata.gz: a4fe11e26a297f5ce16e995ad2892c5da65ba14b9703e0ea1c5f6bf1659bb43f3dfb18cdee60667159e71c8f4ca56dc44f8f7a731ce292f41416e15c46355525
7
+ data.tar.gz: 8d71d20c92e2a9203d2948df069ea4ed1fb6b20e6f76a13e38bcd67dfbd860c4982829f21fba5f2102cf145cc60bf97397d348c7ce5da2ad65de5b96ea1d93a7
data/config/signals.yml CHANGED
@@ -7,6 +7,15 @@
7
7
  #
8
8
  # `owner: shell` marks a signal the app writes into the page rather than one
9
9
  # your views emit.
10
+ #
11
+ # `helper` names the helper that can emit the signal; `always: true` means every
12
+ # call to that helper emits it. Without `always`, `check` cannot count a helper
13
+ # call against the signal: native_fab_tag emits data-native-color only when you
14
+ # pass `color:`, so counting it would report markup the view never renders.
15
+ #
16
+ # `renders_when` covers the helpers that can render nothing at all, naming the
17
+ # keyword values they need in order to emit anything. A call that passes
18
+ # something else, or something only known at request time, is left uncounted.
10
19
  ---
11
20
  signals:
12
21
  data-native-action:
@@ -19,6 +28,7 @@ signals:
19
28
  data-native-badge:
20
29
  since: "0.3.2"
21
30
  helper: native_badge_tag
31
+ always: true
22
32
  singleton: true
23
33
  data-native-badge-home:
24
34
  since: "0.3.2"
@@ -42,50 +52,63 @@ signals:
42
52
  data-native-fab:
43
53
  since: "0.8.0"
44
54
  helper: native_fab_tag
55
+ always: true
45
56
  singleton: true
46
57
  data-native-form:
47
58
  since: "0.1.2"
48
59
  helper: native_form_tag
60
+ always: true
49
61
  singleton: true
50
62
  data-native-haptic:
51
63
  since: "0.2.7"
52
64
  helper: native_haptic_data
65
+ always: true
53
66
  data-native-href:
54
67
  since: "0.4.0"
55
68
  helper: native_fab_tag
56
69
  data-native-icon:
57
70
  since: "0.4.0"
58
71
  helper: native_fab_tag
72
+ always: true
59
73
  data-native-identity:
60
74
  since: "0.12.0"
61
75
  helper: native_identity_tag
76
+ always: true
62
77
  singleton: true
63
78
  data-native-keyboard-toolbar:
64
79
  since: "0.15.0"
65
80
  helper: native_keyboard_tag
81
+ always: true
82
+ renders_when:
83
+ toolbar: false
66
84
  singleton: true
67
85
  data-native-menu:
68
86
  since: "0.14.0"
69
87
  helper: native_menu_tag
88
+ always: true
70
89
  data-native-menu-item:
71
90
  since: "0.4.0"
72
91
  data-native-navbar:
73
92
  since: "0.4.0"
74
93
  helper: native_navbar_tag
94
+ always: true
75
95
  singleton: true
76
96
  data-native-overscroll-bottom:
77
97
  since: "0.5.0"
78
98
  helper: native_overscroll_tag
99
+ always: true
79
100
  singleton: true
80
101
  data-native-overscroll-top:
81
102
  since: "0.5.0"
82
103
  helper: native_overscroll_tag
104
+ always: true
83
105
  singleton: true
84
106
  data-native-position:
85
107
  since: "0.4.0"
86
108
  data-native-presentation:
87
109
  since: "0.11.2"
88
110
  helper: native_presentation_tag
111
+ always: true
89
112
  singleton: true
90
113
  data-native-price:
91
114
  since: "0.5.3"
@@ -97,12 +120,14 @@ signals:
97
120
  data-native-push:
98
121
  since: "0.1.2"
99
122
  helper: native_push_tag
123
+ always: true
100
124
  singleton: true
101
125
  data-native-restore:
102
126
  since: "0.5.3"
103
127
  data-native-review:
104
128
  since: "0.9.3"
105
129
  helper: native_review_tag
130
+ always: true
106
131
  data-native-segment:
107
132
  since: "0.10.12"
108
133
  data-native-selected:
@@ -120,6 +145,9 @@ signals:
120
145
  data-native-tabs:
121
146
  since: "0.1.2"
122
147
  helper: native_tabs_tag
148
+ always: true
149
+ renders_when:
150
+ enabled: true
123
151
  singleton: true
124
152
  data-native-title:
125
153
  since: "0.4.0"
@@ -1,29 +1,131 @@
1
+ require "prism"
2
+
1
3
  module RubyNative
2
4
  class CLI
3
5
  class Check
4
- # Walks a parsed template and records every data-native-* attribute with
5
- # the lines it appears on. Attribute names built out of ERB cannot be
6
- # resolved statically and are skipped rather than guessed at.
6
+ # Walks a parsed template and records every data-native-* signal with the
7
+ # lines it appears on, whether the view writes the attribute by hand or
8
+ # calls the helper that emits it. Attribute names built out of ERB cannot
9
+ # be resolved statically and are skipped rather than guessed at.
7
10
  #
8
11
  # Subclasses Herb::Visitor, so this file is only loaded once `herb` is
9
12
  # known to be present.
10
13
  class SignalCollector < Herb::Visitor
11
- attr_reader :signals
14
+ COMMENT_OPENING = "<%#".freeze
12
15
 
13
16
  def initialize
14
17
  @signals = {}
15
18
  super
16
19
  end
17
20
 
21
+ # Attributes and helper calls arrive from different visits, so the lines
22
+ # for one signal are not collected in document order.
23
+ def signals
24
+ @signals.transform_values(&:sort)
25
+ end
26
+
18
27
  def visit_html_attribute_name_node(node)
19
28
  name = node.children.filter_map { |child| child.content if child.respond_to?(:content) }.join
20
29
 
21
- if name.start_with?("data-native-")
22
- (@signals[name] ||= []) << node.location.start.line
23
- end
30
+ record(name, node.location.start.line) if name.start_with?("data-native-")
31
+
32
+ super
33
+ end
34
+
35
+ def visit_erb_content_node(node)
36
+ record_helper_calls(node)
37
+
38
+ super
39
+ end
40
+
41
+ # `<%= native_navbar_tag "Orders" do |navbar| %>` opens a block, so its
42
+ # Ruby arrives here instead, without the `end` that would let it parse
43
+ # on its own.
44
+ def visit_erb_block_node(node)
45
+ record_helper_calls(node, suffix: "\nend")
24
46
 
25
47
  super
26
48
  end
49
+
50
+ private
51
+
52
+ def record_helper_calls(node, suffix: "")
53
+ return if node.tag_opening&.value == COMMENT_OPENING
54
+
55
+ source = node.content&.value
56
+ return if source.nil?
57
+
58
+ # Ruby that does not parse is the app's own syntax error, which Rails
59
+ # raises on render and `herb lint` reports properly. Staying quiet
60
+ # here keeps `check` to the one job it claims.
61
+ result = Prism.parse(source + suffix)
62
+ return unless result.success?
63
+
64
+ line = node.location.start.line
65
+ calls(result.value).each do |call|
66
+ helper = call.name.to_s
67
+ signals = RubyNative::Signals.signals_for_helper(helper)
68
+ next if signals.empty? || !renders?(call, helper)
69
+
70
+ signals.each { |signal| record(signal, line) }
71
+ end
72
+ end
73
+
74
+ # A helper that can render nothing only counts when its arguments prove
75
+ # it renders. `native_tabs_tag(enabled: @show)` decides at request time,
76
+ # so it is skipped rather than guessed at, the same as an attribute name
77
+ # built out of ERB.
78
+ def renders?(call, helper)
79
+ condition = RubyNative::Signals.render_condition(helper)
80
+ return true unless condition
81
+
82
+ condition.all? do |keyword, required|
83
+ given = keyword_value(call, keyword)
84
+
85
+ given.nil? ? true : given == required
86
+ end
87
+ end
88
+
89
+ # nil when the keyword is absent, which leaves the helper's own default
90
+ # in charge. :unknown for anything that is not a literal, so it never
91
+ # equals what the condition requires.
92
+ def keyword_value(call, keyword)
93
+ pairs = call.arguments&.arguments&.grep(Prism::KeywordHashNode)&.flat_map(&:elements)
94
+ return nil if pairs.nil?
95
+
96
+ assoc = pairs.find do |pair|
97
+ pair.is_a?(Prism::AssocNode) && pair.key.is_a?(Prism::SymbolNode) &&
98
+ pair.key.unescaped == keyword.to_s
99
+ end
100
+ return nil unless assoc
101
+
102
+ case assoc.value
103
+ when Prism::TrueNode then true
104
+ when Prism::FalseNode then false
105
+ else :unknown
106
+ end
107
+ end
108
+
109
+ # A literal in an argument is not a call, so `placeholder: "native_tabs_tag"`
110
+ # reads as the string it is. That is the whole reason for parsing rather
111
+ # than scanning the source for helper names.
112
+ def calls(root)
113
+ found = []
114
+ stack = [root]
115
+
116
+ while (node = stack.pop)
117
+ next unless node.is_a?(Prism::Node)
118
+
119
+ found << node if node.is_a?(Prism::CallNode)
120
+ stack.concat(node.compact_child_nodes)
121
+ end
122
+
123
+ found
124
+ end
125
+
126
+ def record(name, line)
127
+ (@signals[name] ||= []) << line
128
+ end
27
129
  end
28
130
  end
29
131
  end
@@ -41,6 +41,45 @@ module RubyNative
41
41
  all.dig(name, "helper")
42
42
  end
43
43
 
44
+ # Signals a helper emits on every call, keyed by helper name. Only
45
+ # `always` signals are listed: native_fab_tag can emit data-native-color,
46
+ # but only when you pass `color:`, so counting it against a call would
47
+ # have `check` report markup the view never renders.
48
+ def helper_signals
49
+ @helper_signals ||= begin
50
+ map = {}
51
+ all.each do |name, meta|
52
+ helper = meta["helper"]
53
+ (map[helper] ||= []) << name if helper && meta["always"]
54
+ end
55
+ map.freeze
56
+ end
57
+ end
58
+
59
+ def signals_for_helper(helper)
60
+ helper_signals.fetch(helper, [])
61
+ end
62
+
63
+ # The keyword values a helper needs in order to render at all, keyed by
64
+ # helper name. native_tabs_tag(enabled: false) returns an empty string, so
65
+ # `always` alone would count a call that never reaches the page.
66
+ def render_conditions
67
+ @render_conditions ||= begin
68
+ map = {}
69
+ all.each do |_name, meta|
70
+ helper = meta["helper"]
71
+ next unless helper && meta["always"] && meta["renders_when"]
72
+
73
+ (map[helper] ||= {}).merge!(meta["renders_when"])
74
+ end
75
+ map.freeze
76
+ end
77
+ end
78
+
79
+ def render_condition(helper)
80
+ render_conditions[helper]
81
+ end
82
+
44
83
  # Closest known signal within one or two edits, for "did you mean". A
45
84
  # genuinely unrelated attribute gets no suggestion rather than a
46
85
  # confusing one.
@@ -1,3 +1,3 @@
1
1
  module RubyNative
2
- VERSION = "0.17.2"
2
+ VERSION = "0.17.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.17.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Masilotti