Class: Astel::Rewriter

Inherits:
Object
  • Object
show all
Includes:
Refactor, RewriterDiff
Defined in:
lib/astel/rewriter.rb

Defined Under Namespace

Classes: ConflictError, Edit, EditIndex, ValidationError

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RewriterDiff

#to_diff

Methods included from Refactor

#comment_out, #dedent, #find_keyword_argument, #indent, #indentation_of, #insert_into_body, #insert_keyword_argument, #insert_line_after, #insert_line_before, #insert_list_element, #leading_comments, #reindent, #remove_keyword_argument, #remove_line, #remove_list_element, #remove_list_elements, #remove_with_comments, #replace_keyword_key, #replace_keyword_value, #trailing_comment

Constructor Details

#initialize(source_file, duplicate_insertions: :accept) ⇒ Rewriter

Returns a new instance of Rewriter.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/astel/rewriter.rb', line 113

def initialize(source_file, duplicate_insertions: :accept)
  unless %i[accept raise].include?(duplicate_insertions)
    raise ArgumentError, 'duplicate_insertions must be :accept or :raise'
  end

  @source_file = source_file
  @duplicate_insertions = duplicate_insertions
  @edits = []
  @edits_snapshot = nil
  @edit_index = EditIndex.new
  @sequence = 0
  @result_size_delta = 0
  @transaction_staging = false
  @transaction_failed = false
end

Instance Attribute Details

#source_fileObject (readonly)

Returns the value of attribute source_file.



111
112
113
# File 'lib/astel/rewriter.rb', line 111

def source_file
  @source_file
end

Instance Method Details

#editsObject



129
130
131
# File 'lib/astel/rewriter.rb', line 129

def edits
  @edits_snapshot ||= @edits.dup.freeze
end

#insert_after(location, content) ⇒ Object



143
144
145
146
# File 'lib/astel/rewriter.rb', line 143

def insert_after(location, content)
  _, end_offset = offsets(location)
  add_edit(end_offset, end_offset, content)
end

#insert_before(location, content) ⇒ Object



138
139
140
141
# File 'lib/astel/rewriter.rb', line 138

def insert_before(location, content)
  start_offset, = offsets(location)
  add_edit(start_offset, start_offset, content)
end

#merge!(other) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/astel/rewriter.rb', line 160

def merge!(other)
  unless other.is_a?(Rewriter) && other.source_file.equal?(@source_file)
    raise ArgumentError, 'cannot merge edits for a different source'
  end

  validation_index = EditIndex.new
  @edit_index.each { |edit| validation_index.add(edit) { false } }
  other.edits.each do |edit|
    conflict = validation_index.find_conflict(edit) { |existing| conflict?(edit, existing) }
    raise ConflictError.new(edit, conflict) if conflict

    validation_index.add(edit) { false }
  end
  other.edits.each { |edit| add_edit(edit.start_offset, edit.end_offset, edit.replacement) }
  self
end

#remove(location) ⇒ Object



148
149
150
# File 'lib/astel/rewriter.rb', line 148

def remove(location)
  replace(location, '')
end

#replace(location, replacement) ⇒ Object



133
134
135
136
# File 'lib/astel/rewriter.rb', line 133

def replace(location, replacement)
  start_offset, end_offset = offsets(location)
  add_edit(start_offset, end_offset, replacement)
end

#rewrite(validate: false) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/astel/rewriter.rb', line 192

def rewrite(validate: false)
  source = @source_file.source
  result = String.new(capacity: source.bytesize + @result_size_delta, encoding: source.encoding)
  cursor = 0

  @edit_index.each do |edit|
    result << source.byteslice(cursor, edit.start_offset - cursor)
    result << edit.replacement
    cursor = edit.end_offset
  end

  result << source.byteslice(cursor, source.bytesize - cursor)
  validate_result(result, validate)
  result
end

#transaction(raise_on_conflict: false) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/astel/rewriter.rb', line 177

def transaction(raise_on_conflict: false)
  staging = Rewriter.new(@source_file, duplicate_insertions: @duplicate_insertions)
  staging.instance_variable_set(:@transaction_staging, true)
  yield staging
  return transaction_failed if staging.instance_variable_get(:@transaction_failed)

  merge!(staging)
  true
rescue ConflictError
  @transaction_failed = true if @transaction_staging
  raise if raise_on_conflict

  false
end

#wrap(location, before, after) ⇒ Object



152
153
154
155
156
157
158
# File 'lib/astel/rewriter.rb', line 152

def wrap(location, before, after)
  transaction(raise_on_conflict: true) do |rewriter|
    rewriter.insert_before(location, before)
    rewriter.insert_after(location, after)
  end
  self
end