astel 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +110 -60
- data/docs/codemod-guide.md +2 -2
- data/docs/refactor.md +2 -2
- data/lib/astel/diff.rb +3 -130
- data/lib/astel/dispatcher.rb +47 -31
- data/lib/astel/location.rb +12 -2
- data/lib/astel/node_ext.rb +2 -8
- data/lib/astel/node_pattern/lexer.rb +33 -12
- data/lib/astel/node_pattern/matcher_compiler.rb +8 -3
- data/lib/astel/node_pattern/parser.rb +16 -8
- data/lib/astel/node_pattern/predicate_compiler.rb +24 -10
- data/lib/astel/node_pattern.rb +18 -10
- data/lib/astel/node_source_text.rb +13 -0
- data/lib/astel/refactor.rb +2 -187
- data/lib/astel/rewriter/edit_index.rb +84 -0
- data/lib/astel/rewriter/structured/collections.rb +168 -0
- data/lib/astel/rewriter/structured/comments.rb +31 -0
- data/lib/astel/rewriter/structured/formatting.rb +200 -0
- data/lib/astel/rewriter/structured/source_indenter.rb +88 -0
- data/lib/astel/rewriter/structured.rb +163 -0
- data/lib/astel/rewriter.rb +85 -113
- data/lib/astel/source_file.rb +37 -24
- data/lib/astel/unified_diff.rb +205 -0
- data/lib/astel/version.rb +1 -1
- data/lib/astel.rb +1 -0
- metadata +9 -3
- data/lib/astel/refactor/comments.rb +0 -36
- data/lib/astel/refactor/formatting.rb +0 -138
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Astel
|
|
4
|
-
module Refactor
|
|
5
|
-
def indentation_of(node)
|
|
6
|
-
@source_file.indentation_at(node.location.start_offset)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def indent(text, level: 1)
|
|
10
|
-
prefix = @source_file.indent_unit * level
|
|
11
|
-
text.lines(chomp: false).map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def dedent(text)
|
|
15
|
-
widths = text.lines.filter_map do |line|
|
|
16
|
-
next if line.strip.empty?
|
|
17
|
-
|
|
18
|
-
line[/\A[ \t]*/].bytesize
|
|
19
|
-
end
|
|
20
|
-
width = widths.min || 0
|
|
21
|
-
text.lines(chomp: false).map { |line| line.strip.empty? ? line : line.byteslice(width..) }.join
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def reindent(text, to:)
|
|
25
|
-
prefix = indentation_of(to)
|
|
26
|
-
dedent(text).lines(chomp: false).map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def insert_into_body(node, source_text, position: :end)
|
|
30
|
-
statements = body_statements(node)
|
|
31
|
-
target, after, section_break = body_insertion_target(node, statements, position)
|
|
32
|
-
first_statement = statements&.body&.first
|
|
33
|
-
child_indent = if first_statement &&
|
|
34
|
-
@source_file.line_at(first_statement.location.start_offset) !=
|
|
35
|
-
@source_file.line_at(node.location.start_offset)
|
|
36
|
-
indentation_of(first_statement)
|
|
37
|
-
else
|
|
38
|
-
"#{indentation_of(node)}#{@source_file.indent_unit}"
|
|
39
|
-
end
|
|
40
|
-
content = format_body_source(source_text, child_indent)
|
|
41
|
-
|
|
42
|
-
if after
|
|
43
|
-
line = @source_file.line_location_with_newline(target.location.end_offset - 1)
|
|
44
|
-
suffix = line_terminated?(line) ? '' : @source_file.newline
|
|
45
|
-
insert_after(line, "#{suffix}#{content}#{@source_file.newline}")
|
|
46
|
-
else
|
|
47
|
-
offset = target ? @source_file.line_start(target.location.start_offset) : body_closing_location(node).start_offset
|
|
48
|
-
prefix = needs_leading_newline?(offset) ? @source_file.newline : ''
|
|
49
|
-
suffix = section_break ? @source_file.newline * 2 : @source_file.newline
|
|
50
|
-
insert_before(offset...offset, "#{prefix}#{content}#{suffix}")
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def remove_line(node)
|
|
55
|
-
location = node.location
|
|
56
|
-
first = @source_file.line_location(location.start_offset)
|
|
57
|
-
last_offset = [location.end_offset - 1, location.start_offset].max
|
|
58
|
-
last = @source_file.line_location(last_offset)
|
|
59
|
-
before = @source_file.source.byteslice(first.start_offset, location.start_offset - first.start_offset)
|
|
60
|
-
after = @source_file.source.byteslice(location.end_offset, last.end_offset - location.end_offset)
|
|
61
|
-
return remove(location) unless before.strip.empty? && after.strip.empty?
|
|
62
|
-
|
|
63
|
-
ending = @source_file.line_location_with_newline(last_offset).end_offset
|
|
64
|
-
remove(first.start_offset...ending)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def insert_line_before(node, text)
|
|
68
|
-
line_start = @source_file.line_start(node.location.start_offset)
|
|
69
|
-
content = format_body_source(text, indentation_of(node))
|
|
70
|
-
insert_before(line_start...line_start, "#{content}#{@source_file.newline}")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def insert_line_after(node, text)
|
|
74
|
-
line = @source_file.line_location_with_newline(node.location.end_offset - 1)
|
|
75
|
-
content = format_body_source(text, indentation_of(node))
|
|
76
|
-
prefix = line_terminated?(line) ? '' : @source_file.newline
|
|
77
|
-
insert_after(line, "#{prefix}#{content}#{@source_file.newline}")
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def comment_out(node)
|
|
81
|
-
first = @source_file.line_start(node.location.start_offset)
|
|
82
|
-
last = @source_file.line_end([node.location.end_offset - 1, node.location.start_offset].max)
|
|
83
|
-
text = @source_file.source.byteslice(first, last - first)
|
|
84
|
-
commented = text.lines(chomp: false).map do |line|
|
|
85
|
-
indentation = line[/\A[ \t]*/]
|
|
86
|
-
"#{indentation}# #{line.delete_prefix(indentation)}"
|
|
87
|
-
end.join
|
|
88
|
-
replace(first...last, commented)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
private
|
|
92
|
-
|
|
93
|
-
def body_statements(node)
|
|
94
|
-
body = node.respond_to?(:body) ? node.body : nil
|
|
95
|
-
body if body.is_a?(Prism::StatementsNode)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def body_closing_location(node)
|
|
99
|
-
return node.end_keyword_loc if node.respond_to?(:end_keyword_loc) && node.end_keyword_loc
|
|
100
|
-
return node.closing_loc if node.respond_to?(:closing_loc) && node.closing_loc
|
|
101
|
-
|
|
102
|
-
raise ArgumentError, 'node does not have a delimited body'
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def body_insertion_target(node, statements, position)
|
|
106
|
-
body = statements&.body || []
|
|
107
|
-
case position
|
|
108
|
-
when :beginning then return [body.first, false, false]
|
|
109
|
-
when :end then return [nil, false, false]
|
|
110
|
-
when :before_private
|
|
111
|
-
marker = body.find { |child| visibility_marker?(child) }
|
|
112
|
-
return [marker, false, !marker.nil?]
|
|
113
|
-
when Hash
|
|
114
|
-
return [position.fetch(:after), true, false] if position.key?(:after)
|
|
115
|
-
return [position.fetch(:before), false, false] if position.key?(:before)
|
|
116
|
-
end
|
|
117
|
-
raise ArgumentError, 'unsupported body insertion position'
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def visibility_marker?(node)
|
|
121
|
-
node.is_a?(Prism::CallNode) && node.receiver.nil? && node.arguments.nil? &&
|
|
122
|
-
%i[private protected].include?(node.name)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def format_body_source(text, indentation)
|
|
126
|
-
normalized = dedent(text.to_s).gsub(/\r?\n/, @source_file.newline).delete_suffix(@source_file.newline)
|
|
127
|
-
normalized.lines(chomp: false).map { |line| line.strip.empty? ? line : "#{indentation}#{line}" }.join
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def needs_leading_newline?(offset)
|
|
131
|
-
offset.positive? && ![10, 13].include?(@source_file.source.getbyte(offset - 1))
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def line_terminated?(location)
|
|
135
|
-
location.end_offset.positive? && @source_file.source.getbyte(location.end_offset - 1) == 10
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
end
|