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 +4 -4
- data/config/signals.yml +28 -0
- data/lib/ruby_native/cli/check/signal_collector.rb +109 -7
- data/lib/ruby_native/signals.rb +39 -0
- data/lib/ruby_native/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c51153c763a49fbf4d88fa288adcfe46ef69402685a9ccc8ccdf9bfe9b35da8
|
|
4
|
+
data.tar.gz: 624eb747e1fccc0bf7e14d7b4a96822ea98fbd69ff9902cca01920e29997ac28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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-*
|
|
5
|
-
#
|
|
6
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
data/lib/ruby_native/signals.rb
CHANGED
|
@@ -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.
|
data/lib/ruby_native/version.rb
CHANGED