laneyard 0.6.0 → 0.7.0

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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>laneyard</title>
7
- <script type="module" crossorigin src="/assets/index-B8lAQPEM.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-DsjxZtsO.css">
7
+ <script type="module" crossorigin src="/assets/index-Cq1Ze8dP.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-CPKV0yx1.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "laneyard",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "laneyard": "dist/src/main.js"
package/ruby/scan.rb CHANGED
@@ -76,6 +76,22 @@ rescue LoadError => e
76
76
  fail_with("prism is not available in this Ruby (#{e.message})")
77
77
  end
78
78
 
79
+ # The name in `ENV["X"]` or `ENV.fetch("X")`, or nil for anything else.
80
+ #
81
+ # Both spellings read the environment, and both are how a Fastfile that already
82
+ # uses variables writes a credential — the caller rewrites either to the name a
83
+ # signing block exports. Only a literal string argument is resolved: `ENV[key]`
84
+ # with a computed key names nothing this can report.
85
+ def env_lookup_name(node)
86
+ return nil unless node.is_a?(Prism::CallNode)
87
+ receiver = node.receiver
88
+ return nil unless receiver.is_a?(Prism::ConstantReadNode) && receiver.name == :ENV
89
+ return nil unless node.name == :[] || node.name == :fetch
90
+
91
+ first = node.arguments&.arguments&.first
92
+ first.is_a?(Prism::StringNode) ? first.unescaped : nil
93
+ end
94
+
79
95
  # Every `key: "literal"` inside a call, wherever the call sits.
80
96
  #
81
97
  # Descends through everything: a call inside an `if`, inside a `def`, inside a
@@ -99,16 +115,30 @@ def literals_in(node, out = [])
99
115
  hashes.flat_map(&:elements).each do |el|
100
116
  next unless el.is_a?(Prism::AssocNode)
101
117
  next unless el.key.is_a?(Prism::SymbolNode)
102
- next unless el.value.is_a?(Prism::StringNode)
103
- # A heredoc's location covers its *marker*`<<~P` is four bytes
104
- # while its value lives on the lines below. Reporting that range would
105
- # have the caller splice `ENV.fetch("...")` over the marker and leave
106
- # the body stranded after the call: a Fastfile that no longer parses,
107
- # written silently into someone's repository. Dropping heredocs costs
108
- # nothing real, because a credential path is never written as one while
109
- # `changelog:` and `message:` routinely are. Only Ruby can tell a
110
- # heredoc from a quoted string, so the decision has to be made here.
111
- next if el.value.opening&.start_with?("<<")
118
+
119
+ # A literal string, or an environment lookup the two ways a credential
120
+ # is written into a call. `adoption.ts` decides which arguments matter;
121
+ # this stays ignorant of credentials and reports both shapes, tagged so
122
+ # the caller can tell "a path to lift" from "a variable to rename". A
123
+ # value that is neither a computed expression — has nothing the caller
124
+ # can act on and is skipped.
125
+ value = el.value
126
+ if value.is_a?(Prism::StringNode)
127
+ # A heredoc's location covers its *marker* — `<<~P` is four bytes —
128
+ # while its value lives on the lines below. Reporting that range would
129
+ # have the caller splice `ENV.fetch("...")` over the marker and leave
130
+ # the body stranded after the call: a Fastfile that no longer parses,
131
+ # written silently into someone's repository. Only Ruby can tell a
132
+ # heredoc from a quoted string, so the decision has to be made here.
133
+ next if value.opening&.start_with?("<<")
134
+ kind = "literal"
135
+ reported = value.unescaped
136
+ else
137
+ name = env_lookup_name(value)
138
+ next if name.nil?
139
+ kind = "env"
140
+ reported = name
141
+ end
112
142
 
113
143
  # `start_offset` and `length` are byte offsets, and must stay that way:
114
144
  # the caller splices them into a Buffer. Prism also offers
@@ -118,9 +148,10 @@ def literals_in(node, out = [])
118
148
  out << {
119
149
  action: child.name.to_s,
120
150
  arg: el.key.unescaped,
121
- value: el.value.unescaped,
122
- value_start: el.value.location.start_offset,
123
- value_length: el.value.location.length,
151
+ kind: kind,
152
+ value: reported,
153
+ value_start: value.location.start_offset,
154
+ value_length: value.location.length,
124
155
  pair_start: el.location.start_offset,
125
156
  pair_length: el.location.length,
126
157
  line: el.location.start_line