json-merge 1.1.2 → 7.1.1

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.
@@ -15,23 +15,23 @@ module Json
15
15
  extend Ast::Merge::DebugLogger
16
16
 
17
17
  # Json-specific configuration
18
- self.env_var_name = "JSON_MERGE_DEBUG"
19
- self.log_prefix = "[Json::Merge]"
18
+ self.env_var_name = 'JSON_MERGE_DEBUG'
19
+ self.log_prefix = '[Json::Merge]'
20
20
 
21
21
  class << self
22
22
  # Override log_node to handle Json-specific node types.
23
23
  #
24
24
  # @param node [Object] Node to log information about
25
25
  # @param label [String] Label for the node
26
- def log_node(node, label: "Node")
26
+ def log_node(node, label: 'Node')
27
27
  return unless enabled?
28
28
 
29
29
  info = case node
30
- when Json::Merge::NodeWrapper
31
- {type: node.type.to_s, lines: "#{node.start_line}..#{node.end_line}"}
32
- else
33
- extract_node_info(node)
34
- end
30
+ when Json::Merge::NodeWrapper
31
+ { type: node.type.to_s, lines: "#{node.start_line}..#{node.end_line}" }
32
+ else
33
+ extract_node_info(node)
34
+ end
35
35
 
36
36
  debug(label, info)
37
37
  end
@@ -14,28 +14,36 @@ module Json
14
14
  # emitter.emit_pair("key", '"value"')
15
15
  # emitter.emit_object_end
16
16
  class Emitter < Ast::Merge::EmitterBase
17
+ include Ast::Merge::EmitterLineMetadataSupport
18
+
17
19
  # @return [Boolean] Whether next item needs a comma
18
20
  attr_reader :needs_comma
19
21
 
20
22
  # Initialize subclass-specific state (comma tracking for JSON)
21
- def initialize_subclass_state(**options)
23
+ def initialize_subclass_state(**_options)
22
24
  @needs_comma = false
25
+ initialize_line_metadata_state
23
26
  end
24
27
 
25
28
  # Clear subclass-specific state
26
29
  def clear_subclass_state
27
30
  @needs_comma = false
31
+ clear_line_metadata_state
32
+ end
33
+
34
+ def emit_blank_line
35
+ append_line('')
28
36
  end
29
37
 
30
38
  # Emit a tracked comment from CommentTracker
31
39
  # @param comment [Hash] Comment with :text, :indent, :block
32
40
  def emit_tracked_comment(comment)
33
- indent = " " * (comment[:indent] || 0)
34
- @lines << if comment[:block]
35
- "#{indent}/* #{comment[:text]} */"
36
- else
37
- "#{indent}// #{comment[:text]}"
38
- end
41
+ indent = ' ' * (comment[:indent] || 0)
42
+ append_line(if comment[:block]
43
+ "#{indent}/* #{comment[:text]} */"
44
+ else
45
+ "#{indent}// #{comment[:text]}"
46
+ end)
39
47
  end
40
48
 
41
49
  # Emit a single-line comment
@@ -49,7 +57,7 @@ module Json
49
57
 
50
58
  @lines[-1] = "#{@lines[-1]} // #{text}"
51
59
  else
52
- @lines << "#{current_indent}// #{text}"
60
+ append_line("#{current_indent}// #{text}")
53
61
  end
54
62
  end
55
63
 
@@ -57,42 +65,45 @@ module Json
57
65
  #
58
66
  # @param text [String] Comment text
59
67
  def emit_block_comment(text)
60
- @lines << "#{current_indent}/* #{text} */"
68
+ append_line("#{current_indent}/* #{text} */")
61
69
  end
62
70
 
63
71
  # Emit object start
64
- def emit_object_start
72
+ def emit_object_start(metadata: nil)
65
73
  add_comma_if_needed
66
- @lines << "#{current_indent}{"
74
+ append_line("#{current_indent}{", metadata)
67
75
  indent
68
76
  @needs_comma = false
69
77
  end
70
78
 
71
79
  # Emit object end
72
- def emit_object_end
80
+ def emit_object_end(metadata: nil)
73
81
  dedent
74
- @lines << "#{current_indent}}"
82
+ append_line("#{current_indent}}", metadata)
75
83
  @needs_comma = true
76
84
  end
77
85
 
78
86
  # Emit array start
79
87
  #
80
88
  # @param key [String, nil] Key name if this array is a value in an object
81
- def emit_array_start(key = nil)
89
+ # @param inline_comment [String, nil] Optional inline comment for the opening line
90
+ def emit_array_start(key = nil, inline_comment: nil, metadata: nil)
82
91
  add_comma_if_needed
83
- @lines << if key
84
- "#{current_indent}\"#{key}\": ["
85
- else
86
- "#{current_indent}["
87
- end
92
+ line = if key
93
+ "#{current_indent}\"#{key}\": ["
94
+ else
95
+ "#{current_indent}["
96
+ end
97
+ line += " // #{inline_comment}" if inline_comment
98
+ append_line(line, metadata)
88
99
  indent
89
100
  @needs_comma = false
90
101
  end
91
102
 
92
103
  # Emit array end
93
- def emit_array_end
104
+ def emit_array_end(metadata: nil)
94
105
  dedent
95
- @lines << "#{current_indent}]"
106
+ append_line("#{current_indent}]", metadata)
96
107
  @needs_comma = true
97
108
  end
98
109
 
@@ -101,11 +112,11 @@ module Json
101
112
  # @param key [String] Key name (without quotes)
102
113
  # @param value [String] Value (already formatted, e.g., '"string"', '123', 'true')
103
114
  # @param inline_comment [String, nil] Optional inline comment
104
- def emit_pair(key, value, inline_comment: nil)
115
+ def emit_pair(key, value, inline_comment: nil, metadata: nil)
105
116
  add_comma_if_needed
106
117
  line = "#{current_indent}\"#{key}\": #{value}"
107
118
  line += " // #{inline_comment}" if inline_comment
108
- @lines << line
119
+ append_line(line, metadata)
109
120
  @needs_comma = true
110
121
  end
111
122
 
@@ -113,34 +124,57 @@ module Json
113
124
  #
114
125
  # @param value [String] Value (already formatted)
115
126
  # @param inline_comment [String, nil] Optional inline comment
116
- def emit_array_element(value, inline_comment: nil)
127
+ def emit_array_element(value, inline_comment: nil, metadata: nil)
117
128
  add_comma_if_needed
118
129
  line = "#{current_indent}#{value}"
119
130
  line += " // #{inline_comment}" if inline_comment
120
- @lines << line
131
+ append_line(line, metadata)
121
132
  @needs_comma = true
122
133
  end
123
134
 
124
135
  # Emit a key with opening brace for nested object
125
136
  # @param key [String] Key name
126
- def emit_nested_object_start(key)
137
+ # @param inline_comment [String, nil] Optional inline comment for the opening line
138
+ def emit_nested_object_start(key, inline_comment: nil, metadata: nil)
127
139
  add_comma_if_needed
128
- @lines << "#{current_indent}\"#{key}\": {"
140
+ line = "#{current_indent}\"#{key}\": {"
141
+ line += " // #{inline_comment}" if inline_comment
142
+ append_line(line, metadata)
129
143
  indent
130
144
  @needs_comma = false
131
145
  end
132
146
 
133
147
  # Emit closing brace for nested object
134
- def emit_nested_object_end
148
+ def emit_nested_object_end(metadata: nil)
135
149
  dedent
136
- @lines << "#{current_indent}}"
150
+ append_line("#{current_indent}}", metadata)
151
+ @needs_comma = true
152
+ end
153
+
154
+ def emit_raw_lines(raw_lines, metadata: nil)
155
+ raw_lines.each_with_index do |line, idx|
156
+ append_line(
157
+ line.chomp,
158
+ expanded_line_metadata(metadata, idx)
159
+ )
160
+ end
161
+ end
162
+
163
+ def emit_raw_fragment(fragment, metadata: nil)
164
+ add_comma_if_needed
165
+ fragment.to_s.lines(chomp: true).each_with_index do |line, idx|
166
+ append_line(
167
+ "#{current_indent}#{line}",
168
+ expanded_line_metadata(metadata, idx)
169
+ )
170
+ end
137
171
  @needs_comma = true
138
172
  end
139
173
 
140
174
  # Get the output as a JSON string
141
175
  #
142
176
  # @return [String]
143
- def to_json
177
+ def to_json(*_args)
144
178
  to_s
145
179
  end
146
180
 
@@ -149,14 +183,44 @@ module Json
149
183
  def add_comma_if_needed
150
184
  return unless @needs_comma && @lines.any?
151
185
 
152
- # Add comma to the previous line if it doesn't already have one
153
- last_line = @lines.last
154
- return if last_line.strip.empty?
155
- return if last_line.rstrip.end_with?(",")
156
- return if last_line.rstrip.end_with?("{")
157
- return if last_line.rstrip.end_with?("[")
186
+ line_index = @lines.length - 1
187
+ while line_index >= 0
188
+ line = @lines[line_index]
189
+ stripped = line.strip
190
+ if stripped.empty? || comment_line?(stripped)
191
+ line_index -= 1
192
+ next
193
+ end
194
+
195
+ break
196
+ end
197
+
198
+ return if line_index.negative?
199
+
200
+ # Add comma to the previous structural line if it doesn't already have one
201
+ last_line = @lines[line_index]
202
+ @lines[line_index] = add_comma_to_line(last_line)
203
+ end
204
+
205
+ def comment_line?(stripped_line)
206
+ stripped_line.start_with?('//', '/*', '*', '*/')
207
+ end
208
+
209
+ def add_comma_to_line(line)
210
+ return line if line.strip.empty?
211
+
212
+ inline_match = line.match(%r{\A(?<content>.*?)(?<spacing>\s+)(?<comment>//.*)\z})
213
+ if inline_match
214
+ content = inline_match[:content].rstrip
215
+ return line if content.end_with?(',', '{', '[')
216
+
217
+ return "#{content}, #{inline_match[:comment]}"
218
+ end
219
+
220
+ stripped = line.rstrip
221
+ return line if stripped.end_with?(',', '{', '[')
158
222
 
159
- @lines[-1] = "#{last_line},"
223
+ "#{line},"
160
224
  end
161
225
  end
162
226
  end