dependabot-github_actions 0.393.0 → 0.394.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.
@@ -0,0 +1,212 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/github_actions/workflow_file"
5
+
6
+ module Dependabot
7
+ module GithubActions
8
+ class WorkflowFile
9
+ class MetadataBuilder
10
+ extend T::Sig
11
+
12
+ sig { params(workflow_file: WorkflowFile).void }
13
+ def initialize(workflow_file)
14
+ @workflow_file = workflow_file
15
+ end
16
+
17
+ sig { params(declaration: UsesDeclaration).returns(Metadata) }
18
+ def build(declaration)
19
+ metadata = T.let(
20
+ {
21
+ path: declaration.path,
22
+ value: node_metadata(declaration.value_node),
23
+ target: node_metadata(declaration.source_node),
24
+ mapping: node_metadata(declaration.mapping_node)
25
+ },
26
+ Metadata
27
+ )
28
+
29
+ sequence = declaration.steps_sequence
30
+ if sequence
31
+ metadata[:sequence] = sequence_metadata(
32
+ sequence,
33
+ declaration.steps_key_node,
34
+ declaration.sequence_item_index
35
+ )
36
+ end
37
+
38
+ metadata
39
+ end
40
+
41
+ sig { params(declaration: UsesDeclaration).returns(T::Array[UsesDeclaration]) }
42
+ def collision_declarations_for(declaration)
43
+ anchor = comment_anchor(declaration)
44
+ return [] unless anchor
45
+
46
+ workflow_file.uses_declarations.select do |item|
47
+ item_anchor = comment_anchor(item)
48
+ item_anchor && item_anchor.first == anchor.first
49
+ end
50
+ end
51
+
52
+ sig { params(declaration: UsesDeclaration).returns(T::Boolean) }
53
+ def comment_line_collision?(declaration)
54
+ anchor = comment_anchor(declaration)
55
+ return false unless anchor
56
+ return true if collision_declarations_for(declaration).length > 1
57
+
58
+ line = anchor.first
59
+ anchor_offset = workflow_file.offset(anchor.first, anchor.last)
60
+ workflow_file.steps_sequences.any? do |sequence|
61
+ sequence_starts_after_anchor?(sequence, line, anchor_offset) ||
62
+ child_starts_after_anchor?(sequence, line, anchor_offset)
63
+ end
64
+ end
65
+
66
+ sig { params(declaration: UsesDeclaration).returns(T::Array[Psych::Nodes::Sequence]) }
67
+ def collision_sequences_for(declaration)
68
+ anchor = comment_anchor(declaration)
69
+ return [] unless anchor
70
+
71
+ line = anchor.first
72
+ workflow_file.steps_sequences.select do |sequence|
73
+ line.between?(
74
+ workflow_file.node_start_line(sequence),
75
+ workflow_file.node_end_line(sequence)
76
+ )
77
+ end
78
+ end
79
+
80
+ sig { params(declaration: UsesDeclaration).returns(T.nilable([Integer, Integer])) }
81
+ def comment_anchor(declaration)
82
+ source_node = declaration.source_node
83
+ return unless source_node
84
+
85
+ if source_node.is_a?(Psych::Nodes::Scalar) && block_scalar?(source_node)
86
+ return [
87
+ workflow_file.node_start_line(source_node),
88
+ workflow_file.node_start_column(source_node)
89
+ ]
90
+ end
91
+
92
+ mapping = declaration.mapping_node
93
+ if mapping && workflow_file.mapping_node_style(mapping) == Psych::Nodes::Mapping::FLOW
94
+ return [workflow_file.node_end_line(mapping), workflow_file.node_end_column(mapping)]
95
+ end
96
+
97
+ value_node = declaration.value_node
98
+ return unless value_node
99
+
100
+ [workflow_file.node_end_line(value_node), workflow_file.node_end_column(value_node)]
101
+ end
102
+
103
+ private
104
+
105
+ sig { returns(WorkflowFile) }
106
+ attr_reader :workflow_file
107
+
108
+ sig do
109
+ params(
110
+ sequence: Psych::Nodes::Sequence,
111
+ line: Integer,
112
+ anchor_offset: Integer
113
+ ).returns(T::Boolean)
114
+ end
115
+ def sequence_starts_after_anchor?(sequence, line, anchor_offset)
116
+ workflow_file.node_start_line(sequence) == line && node_start_offset(sequence) > anchor_offset
117
+ end
118
+
119
+ sig do
120
+ params(
121
+ sequence: Psych::Nodes::Sequence,
122
+ line: Integer,
123
+ anchor_offset: Integer
124
+ ).returns(T::Boolean)
125
+ end
126
+ def child_starts_after_anchor?(sequence, line, anchor_offset)
127
+ workflow_file.node_children(sequence).any? do |child|
128
+ workflow_file.node_start_line(child) == line && node_start_offset(child) > anchor_offset
129
+ end
130
+ end
131
+
132
+ sig { params(node: Psych::Nodes::Node).returns(Integer) }
133
+ def node_start_offset(node)
134
+ workflow_file.offset(
135
+ workflow_file.node_start_line(node),
136
+ workflow_file.node_start_column(node)
137
+ )
138
+ end
139
+
140
+ sig { params(node: Psych::Nodes::Scalar).returns(T::Boolean) }
141
+ def block_scalar?(node)
142
+ style = workflow_file.scalar_node_style(node)
143
+ [Psych::Nodes::Scalar::LITERAL, Psych::Nodes::Scalar::FOLDED].include?(style)
144
+ end
145
+
146
+ sig { params(node: T.nilable(Psych::Nodes::Node)).returns(Metadata) }
147
+ def node_metadata(node)
148
+ return {} unless node
149
+
150
+ metadata = T.let(
151
+ {
152
+ kind: node_kind(node),
153
+ start_line: workflow_file.node_start_line(node),
154
+ start_column: workflow_file.node_start_column(node),
155
+ end_line: workflow_file.node_end_line(node),
156
+ end_column: workflow_file.node_end_column(node)
157
+ },
158
+ Metadata
159
+ )
160
+
161
+ if node.is_a?(Psych::Nodes::Scalar)
162
+ metadata[:style] = scalar_style(workflow_file.scalar_node_style(node))
163
+ metadata[:anchor] = node.anchor if node.anchor
164
+ elsif node.is_a?(Psych::Nodes::Alias)
165
+ metadata[:anchor] = node.anchor
166
+ end
167
+
168
+ metadata
169
+ end
170
+
171
+ sig do
172
+ params(
173
+ sequence: Psych::Nodes::Sequence,
174
+ key_node: T.nilable(Psych::Nodes::Scalar),
175
+ item_index: T.nilable(Integer)
176
+ ).returns(Metadata)
177
+ end
178
+ def sequence_metadata(sequence, key_node, item_index)
179
+ metadata = node_metadata(sequence)
180
+ metadata[:style] =
181
+ workflow_file.sequence_node_style(sequence) == Psych::Nodes::Sequence::FLOW ? "flow" : "block"
182
+ metadata[:item_index] = item_index if item_index
183
+ metadata[:item_count] = workflow_file.node_children(sequence).length
184
+ metadata[:key_start_column] = workflow_file.node_start_column(key_node) if key_node
185
+ metadata
186
+ end
187
+
188
+ sig { params(node: Psych::Nodes::Node).returns(String) }
189
+ def node_kind(node)
190
+ case node
191
+ when Psych::Nodes::Alias then "alias"
192
+ when Psych::Nodes::Scalar then "scalar"
193
+ when Psych::Nodes::Mapping then "mapping"
194
+ when Psych::Nodes::Sequence then "sequence"
195
+ else "node"
196
+ end
197
+ end
198
+
199
+ sig { params(style: Integer).returns(String) }
200
+ def scalar_style(style)
201
+ case style
202
+ when Psych::Nodes::Scalar::SINGLE_QUOTED then "single_quoted"
203
+ when Psych::Nodes::Scalar::DOUBLE_QUOTED then "double_quoted"
204
+ when Psych::Nodes::Scalar::LITERAL then "literal"
205
+ when Psych::Nodes::Scalar::FOLDED then "folded"
206
+ else "plain"
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,64 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/github_actions/workflow_file"
5
+
6
+ module Dependabot
7
+ module GithubActions
8
+ class WorkflowFile
9
+ class NodeContext
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ node: Psych::Nodes::Node,
15
+ mapping_node: T.nilable(Psych::Nodes::Mapping),
16
+ key_node: T.nilable(Psych::Nodes::Scalar),
17
+ steps_sequence: T.nilable(Psych::Nodes::Sequence),
18
+ steps_key_node: T.nilable(Psych::Nodes::Scalar),
19
+ sequence_item: T.nilable(Psych::Nodes::Node),
20
+ sequence_item_index: T.nilable(Integer)
21
+ ).void
22
+ end
23
+ def initialize(
24
+ node:,
25
+ mapping_node:,
26
+ key_node:,
27
+ steps_sequence:,
28
+ steps_key_node:,
29
+ sequence_item:,
30
+ sequence_item_index:
31
+ )
32
+ @node = node
33
+ @mapping_node = mapping_node
34
+ @key_node = key_node
35
+ @steps_sequence = steps_sequence
36
+ @steps_key_node = steps_key_node
37
+ @sequence_item = sequence_item
38
+ @sequence_item_index = sequence_item_index
39
+ end
40
+
41
+ sig { returns(Psych::Nodes::Node) }
42
+ attr_reader :node
43
+
44
+ sig { returns(T.nilable(Psych::Nodes::Mapping)) }
45
+ attr_reader :mapping_node
46
+
47
+ sig { returns(T.nilable(Psych::Nodes::Scalar)) }
48
+ attr_reader :key_node
49
+
50
+ sig { returns(T.nilable(Psych::Nodes::Sequence)) }
51
+ attr_reader :steps_sequence
52
+
53
+ sig { returns(T.nilable(Psych::Nodes::Scalar)) }
54
+ attr_reader :steps_key_node
55
+
56
+ sig { returns(T.nilable(Psych::Nodes::Node)) }
57
+ attr_reader :sequence_item
58
+
59
+ sig { returns(T.nilable(Integer)) }
60
+ attr_reader :sequence_item_index
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,78 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/github_actions/workflow_file"
5
+
6
+ module Dependabot
7
+ module GithubActions
8
+ class WorkflowFile
9
+ class UsesCollector
10
+ extend T::Sig
11
+
12
+ class ResolvedUse < T::Struct
13
+ const :value, String
14
+ const :path, Path
15
+ end
16
+
17
+ sig { params(data: T.nilable(Object)).void }
18
+ def initialize(data)
19
+ @data = data
20
+ end
21
+
22
+ sig { returns(T::Array[ResolvedUse]) }
23
+ def uses
24
+ root, path = workflow_root
25
+ return [] unless root && path
26
+
27
+ collect_uses(root, path)
28
+ end
29
+
30
+ private
31
+
32
+ sig { returns(T.nilable(Object)) }
33
+ attr_reader :data
34
+
35
+ sig { returns([T.nilable(Object), T.nilable(Path)]) }
36
+ def workflow_root
37
+ return [nil, nil] unless data.is_a?(Hash)
38
+
39
+ workflow_data = T.cast(data, T::Hash[Object, Object])
40
+ if workflow_data.key?("jobs")
41
+ [workflow_data["jobs"], ["jobs"]]
42
+ elsif workflow_data.key?("runs")
43
+ [workflow_data["runs"], ["runs"]]
44
+ else
45
+ [nil, nil]
46
+ end
47
+ end
48
+
49
+ sig { params(value: Object, path: Path).returns(T::Array[ResolvedUse]) }
50
+ def collect_uses(value, path)
51
+ case value
52
+ when Hash then collect_uses_from_hash(value, path)
53
+ when Array
54
+ value.each_with_index.flat_map do |child, index|
55
+ collect_uses(child, path + [index])
56
+ end
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ sig { params(value: T::Hash[Object, Object], path: Path).returns(T::Array[ResolvedUse]) }
63
+ def collect_uses_from_hash(value, path)
64
+ if value.key?(USES_KEY)
65
+ declaration_value = value[USES_KEY]
66
+ return [] unless declaration_value.is_a?(String)
67
+
68
+ [ResolvedUse.new(value: declaration_value, path: path + [USES_KEY])]
69
+ elsif value.key?(STEPS_KEY)
70
+ collect_uses(value[STEPS_KEY], path + [STEPS_KEY])
71
+ else
72
+ value.flat_map { |key, child| collect_uses(child, path + [key.to_s]) }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,76 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/github_actions/workflow_file"
5
+
6
+ module Dependabot
7
+ module GithubActions
8
+ class WorkflowFile
9
+ class UsesDeclaration
10
+ extend T::Sig
11
+
12
+ sig do
13
+ params(
14
+ value: String,
15
+ path: Path,
16
+ value_node: T.nilable(Psych::Nodes::Node),
17
+ source_node: T.nilable(Psych::Nodes::Node),
18
+ mapping_node: T.nilable(Psych::Nodes::Mapping),
19
+ steps_sequence: T.nilable(Psych::Nodes::Sequence),
20
+ steps_key_node: T.nilable(Psych::Nodes::Scalar),
21
+ sequence_item: T.nilable(Psych::Nodes::Node),
22
+ sequence_item_index: T.nilable(Integer)
23
+ ).void
24
+ end
25
+ def initialize(
26
+ value:,
27
+ path:,
28
+ value_node:,
29
+ source_node:,
30
+ mapping_node:,
31
+ steps_sequence:,
32
+ steps_key_node:,
33
+ sequence_item:,
34
+ sequence_item_index:
35
+ )
36
+ @value = value
37
+ @path = path
38
+ @value_node = value_node
39
+ @source_node = source_node
40
+ @mapping_node = mapping_node
41
+ @steps_sequence = steps_sequence
42
+ @steps_key_node = steps_key_node
43
+ @sequence_item = sequence_item
44
+ @sequence_item_index = sequence_item_index
45
+ end
46
+
47
+ sig { returns(String) }
48
+ attr_reader :value
49
+
50
+ sig { returns(Path) }
51
+ attr_reader :path
52
+
53
+ sig { returns(T.nilable(Psych::Nodes::Node)) }
54
+ attr_reader :value_node
55
+
56
+ sig { returns(T.nilable(Psych::Nodes::Node)) }
57
+ attr_reader :source_node
58
+
59
+ sig { returns(T.nilable(Psych::Nodes::Mapping)) }
60
+ attr_reader :mapping_node
61
+
62
+ sig { returns(T.nilable(Psych::Nodes::Sequence)) }
63
+ attr_reader :steps_sequence
64
+
65
+ sig { returns(T.nilable(Psych::Nodes::Scalar)) }
66
+ attr_reader :steps_key_node
67
+
68
+ sig { returns(T.nilable(Psych::Nodes::Node)) }
69
+ attr_reader :sequence_item
70
+
71
+ sig { returns(T.nilable(Integer)) }
72
+ attr_reader :sequence_item_index
73
+ end
74
+ end
75
+ end
76
+ end