From da19f6a2dc787411e34e4ec90547b136467e7149 Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Wed, 8 Feb 2023 11:01:27 -0500 Subject: [PATCH 1/2] Location information for parser nodes --- lib/syntax_tree/formatter.rb | 2 +- lib/syntax_tree/node.rb | 8 + lib/syntax_tree/parser.rb | 197 +++- lib/syntax_tree/translation/parser.rb | 1529 ++++++++++--------------- test/syntax_tree_test.rb | 2 +- test/translation/parser_test.rb | 2 +- 6 files changed, 774 insertions(+), 966 deletions(-) diff --git a/lib/syntax_tree/formatter.rb b/lib/syntax_tree/formatter.rb index c64cf7d1..60858bf2 100644 --- a/lib/syntax_tree/formatter.rb +++ b/lib/syntax_tree/formatter.rb @@ -138,7 +138,7 @@ def format(node, stackable: true) # going to just print out the node as it was seen in the source. doc = if last_leading&.ignore? - range = source[node.location.start_char...node.location.end_char] + range = source[node.start_char...node.end_char] first = true range.each_line(chomp: true) do |line| diff --git a/lib/syntax_tree/node.rb b/lib/syntax_tree/node.rb index 4a98dae4..627deab1 100644 --- a/lib/syntax_tree/node.rb +++ b/lib/syntax_tree/node.rb @@ -126,6 +126,14 @@ def format(q) raise NotImplementedError end + def start_char + location.start_char + end + + def end_char + location.end_char + end + def pretty_print(q) accept(Visitor::PrettyPrintVisitor.new(q)) end diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index c15a0339..cf3982f9 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -256,11 +256,37 @@ def find_token(type) tokens[index] if index end + def find_token_between(type, left, right) + bounds = left.location.end_char...right.location.start_char + index = + tokens.rindex do |token| + char = token.location.start_char + break if char < bounds.begin + + token.is_a?(type) && bounds.cover?(char) + end + + tokens[index] if index + end + def find_keyword(name) index = tokens.rindex { |token| token.is_a?(Kw) && (token.name == name) } tokens[index] if index end + def find_keyword_between(name, left, right) + bounds = left.location.end_char...right.location.start_char + index = + tokens.rindex do |token| + char = token.location.start_char + break if char < bounds.begin + + token.is_a?(Kw) && (token.name == name) && bounds.cover?(char) + end + + tokens[index] if index + end + def find_operator(name) index = tokens.rindex { |token| token.is_a?(Op) && (token.name == name) } tokens[index] if index @@ -645,7 +671,7 @@ def visit_var_ref(node) end def self.visit(node, tokens) - start_char = node.location.start_char + start_char = node.start_char allocated = [] tokens.reverse_each do |token| @@ -874,13 +900,34 @@ def on_binary(left, operator, right) # on_block_var: (Params params, (nil | Array[Ident]) locals) -> BlockVar def on_block_var(params, locals) index = - tokens.rindex do |node| - node.is_a?(Op) && %w[| ||].include?(node.value) && - node.location.start_char < params.location.start_char - end + tokens.rindex { |node| node.is_a?(Op) && %w[| ||].include?(node.value) } + + ending = tokens.delete_at(index) + beginning = ending.value == "||" ? ending : consume_operator(:|) + + # If there are no parameters, then we didn't have anything to base the + # location information of off. Now that we have an opening of the + # block, we can correct this. + if params.empty? + start_line = params.location.start_line + start_char = + ( + if beginning.value == "||" + beginning.location.start_char + else + find_next_statement_start(beginning.location.end_char) + end + ) + + location = + Location.fixed( + line: start_line, + char: start_char, + column: start_char - line_counts[start_line - 1].start + ) - beginning = tokens[index] - ending = tokens[-1] + params = params.copy(location: location) + end BlockVar.new( params: params, @@ -1762,15 +1809,13 @@ def on_for(index, collection, statements) # Consume the do keyword if it exists so that it doesn't get confused for # some other block - keyword = find_keyword(:do) - if keyword && - keyword.location.start_char > collection.location.end_char && - keyword.location.end_char < ending.location.start_char + if (keyword = find_keyword_between(:do, collection, ending)) tokens.delete(keyword) end start_char = find_next_statement_start((keyword || collection).location.end_char) + statements.bind( start_char, start_char - @@ -1984,7 +2029,12 @@ def on_if(predicate, statements, consequent) beginning = consume_keyword(:if) ending = consequent || consume_keyword(:end) - start_char = find_next_statement_start(predicate.location.end_char) + if (keyword = find_keyword_between(:then, predicate, ending)) + tokens.delete(keyword) + end + + start_char = + find_next_statement_start((keyword || predicate).location.end_char) statements.bind( start_char, start_char - line_counts[predicate.location.end_line - 1].start, @@ -2068,7 +2118,8 @@ def on_in(pattern, statements, consequent) statements_start = token end - start_char = find_next_statement_start(statements_start.location.end_char) + start_char = + find_next_statement_start((token || statements_start).location.end_char) statements.bind( start_char, start_char - @@ -2194,12 +2245,19 @@ def on_lambda(params, statements) token.location.start_char > beginning.location.start_char end + if braces + opening = consume_token(TLamBeg) + closing = consume_token(RBrace) + else + opening = consume_keyword(:do) + closing = consume_keyword(:end) + end + # We need to do some special mapping here. Since ripper doesn't support - # capturing lambda var until 3.2, we need to normalize all of that here. + # capturing lambda vars, we need to normalize all of that here. params = - case params - when Paren - # In this case we've gotten to the <3.2 parentheses wrapping a set of + if params.is_a?(Paren) + # In this case we've gotten to the parentheses wrapping a set of # parameters case. Here we need to manually scan for lambda locals. range = (params.location.start_char + 1)...params.location.end_char locals = lambda_locals(source[range]) @@ -2221,25 +2279,28 @@ def on_lambda(params, statements) node.comments.concat(params.comments) node - when Params - # In this case we've gotten to the <3.2 plain set of parameters. In - # this case there cannot be lambda locals, so we will wrap the - # parameters into a lambda var that has no locals. + else + # If there are no parameters, then we didn't have anything to base the + # location information of off. Now that we have an opening of the + # block, we can correct this. + if params.empty? + opening_location = opening.location + location = + Location.fixed( + line: opening_location.start_line, + char: opening_location.start_char, + column: opening_location.start_column + ) + + params = params.copy(location: location) + end + + # In this case we've gotten to the plain set of parameters. In this + # case there cannot be lambda locals, so we will wrap the parameters + # into a lambda var that has no locals. LambdaVar.new(params: params, locals: [], location: params.location) - when LambdaVar - # In this case we've gotten to 3.2+ lambda var. In this case we don't - # need to do anything and can just the value as given. - params end - if braces - opening = consume_token(TLamBeg) - closing = consume_token(RBrace) - else - opening = consume_keyword(:do) - closing = consume_keyword(:end) - end - start_char = find_next_statement_start(opening.location.end_char) statements.bind( start_char, @@ -3134,7 +3195,7 @@ def on_rescue(exceptions, variable, statements, consequent) exceptions = exceptions[0] if exceptions.is_a?(Array) last_node = variable || exceptions || keyword - start_char = find_next_statement_start(last_node.location.end_char) + start_char = find_next_statement_start(last_node.end_char) statements.bind( start_char, start_char - line_counts[last_node.location.start_line - 1].start, @@ -3156,7 +3217,7 @@ def on_rescue(exceptions, variable, statements, consequent) start_char: keyword.location.end_char + 1, start_column: keyword.location.end_column + 1, end_line: last_node.location.end_line, - end_char: last_node.location.end_char, + end_char: last_node.end_char, end_column: last_node.location.end_column ) ) @@ -3267,9 +3328,27 @@ def on_sclass(target, bodystmt) ) end - # def on_semicolon(value) - # value - # end + class Semicolon + attr_reader :location + + def initialize(location:) + @location = location + end + end + + # :call-seq: + # on_semicolon: (String value) -> Semicolon + def on_semicolon(value) + tokens << Semicolon.new( + location: + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) + ) + end # def on_sp(value) # value @@ -3706,7 +3785,12 @@ def on_unless(predicate, statements, consequent) beginning = consume_keyword(:unless) ending = consequent || consume_keyword(:end) - start_char = find_next_statement_start(predicate.location.end_char) + if (keyword = find_keyword_between(:then, predicate, ending)) + tokens.delete(keyword) + end + + start_char = + find_next_statement_start((keyword || predicate).location.end_char) statements.bind( start_char, start_char - line_counts[predicate.location.end_line - 1].start, @@ -3742,16 +3826,16 @@ def on_until(predicate, statements) beginning = consume_keyword(:until) ending = consume_keyword(:end) - # Consume the do keyword if it exists so that it doesn't get confused for - # some other block - keyword = find_keyword(:do) - if keyword && keyword.location.start_char > predicate.location.end_char && - keyword.location.end_char < ending.location.start_char - tokens.delete(keyword) - end + delimiter = + find_keyword_between(:do, predicate, statements) || + find_token_between(Semicolon, predicate, statements) + + tokens.delete(delimiter) if delimiter # Update the Statements location information - start_char = find_next_statement_start(predicate.location.end_char) + start_char = + find_next_statement_start((delimiter || predicate).location.end_char) + statements.bind( start_char, start_char - line_counts[predicate.location.end_line - 1].start, @@ -3845,7 +3929,8 @@ def on_when(arguments, statements, consequent) statements_start = token end - start_char = find_next_statement_start(statements_start.location.end_char) + start_char = + find_next_statement_start((token || statements_start).location.end_char) statements.bind( start_char, @@ -3869,16 +3954,16 @@ def on_while(predicate, statements) beginning = consume_keyword(:while) ending = consume_keyword(:end) - # Consume the do keyword if it exists so that it doesn't get confused for - # some other block - keyword = find_keyword(:do) - if keyword && keyword.location.start_char > predicate.location.end_char && - keyword.location.end_char < ending.location.start_char - tokens.delete(keyword) - end + delimiter = + find_keyword_between(:do, predicate, statements) || + find_token_between(Semicolon, predicate, statements) + + tokens.delete(delimiter) if delimiter # Update the Statements location information - start_char = find_next_statement_start(predicate.location.end_char) + start_char = + find_next_statement_start((delimiter || predicate).location.end_char) + statements.bind( start_char, start_char - line_counts[predicate.location.end_line - 1].start, diff --git a/lib/syntax_tree/translation/parser.rb b/lib/syntax_tree/translation/parser.rb index 184bb165..b9e91e5f 100644 --- a/lib/syntax_tree/translation/parser.rb +++ b/lib/syntax_tree/translation/parser.rb @@ -27,9 +27,9 @@ def visit_alias(node) s( :alias, [visit(node.left), visit(node.right)], - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) end @@ -41,26 +41,20 @@ def visit_aref(node) s( :index, [visit(node.collection)], - source_map_index( - begin_token: - source_range_find( - node.collection.location.end_char, - node.location.end_char, - "[" - ), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_index( + srange_find(node.collection.end_char, node.end_char, "["), + srange_length(node.end_char, -1), + srange_node(node) ) ) else s( :index, [visit(node.collection)].concat(visit_all(node.index.parts)), - source_map_index( - begin_token: - source_range_find_between(node.collection, node.index, "["), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_index( + srange_find_between(node.collection, node.index, "["), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -69,31 +63,25 @@ def visit_aref(node) s( :send, [visit(node.collection), :[]], - source_map_send( - selector: - source_range_find( - node.collection.location.end_char, - node.location.end_char, - "[]" - ), - expression: source_range_node(node) + smap_send_bare( + srange_find(node.collection.end_char, node.end_char, "[]"), + srange_node(node) ) ) else s( :send, [visit(node.collection), :[], *visit_all(node.index.parts)], - source_map_send( - selector: - source_range( - source_range_find_between( - node.collection, - node.index, - "[" - ).begin_pos, - node.location.end_char - ), - expression: source_range_node(node) + smap_send_bare( + srange( + srange_find_between( + node.collection, + node.index, + "[" + ).begin_pos, + node.end_char + ), + srange_node(node) ) ) end @@ -107,26 +95,20 @@ def visit_aref_field(node) s( :indexasgn, [visit(node.collection)], - source_map_index( - begin_token: - source_range_find( - node.collection.location.end_char, - node.location.end_char, - "[" - ), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_index( + srange_find(node.collection.end_char, node.end_char, "["), + srange_length(node.end_char, -1), + srange_node(node) ) ) else s( :indexasgn, [visit(node.collection)].concat(visit_all(node.index.parts)), - source_map_index( - begin_token: - source_range_find_between(node.collection, node.index, "["), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_index( + srange_find_between(node.collection, node.index, "["), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -135,14 +117,9 @@ def visit_aref_field(node) s( :send, [visit(node.collection), :[]=], - source_map_send( - selector: - source_range_find( - node.collection.location.end_char, - node.location.end_char, - "[]" - ), - expression: source_range_node(node) + smap_send_bare( + srange_find(node.collection.end_char, node.end_char, "[]"), + srange_node(node) ) ) else @@ -151,17 +128,16 @@ def visit_aref_field(node) [visit(node.collection), :[]=].concat( visit_all(node.index.parts) ), - source_map_send( - selector: - source_range( - source_range_find_between( - node.collection, - node.index, - "[" - ).begin_pos, - node.location.end_char - ), - expression: source_range_node(node) + smap_send_bare( + srange( + srange_find_between( + node.collection, + node.index, + "[" + ).begin_pos, + node.end_char + ), + srange_node(node) ) ) end @@ -173,10 +149,7 @@ def visit_arg_block(node) s( :block_pass, [visit(node.value)], - source_map_operator( - source_range_length(node.location.start_char, 1), - source_range_node(node) - ) + smap_operator(srange_length(node.start_char, 1), srange_node(node)) ) end @@ -184,32 +157,26 @@ def visit_arg_block(node) def visit_arg_star(node) if stack[-3].is_a?(MLHSParen) && stack[-3].contents.is_a?(MLHS) if node.value.nil? - s(:restarg, [], source_map_variable(nil, source_range_node(node))) + s(:restarg, [], smap_variable(nil, srange_node(node))) else s( :restarg, [node.value.value.to_sym], - source_map_variable( - source_range_node(node.value), - source_range_node(node) - ) + smap_variable(srange_node(node.value), srange_node(node)) ) end else s( :splat, node.value.nil? ? [] : [visit(node.value)], - source_map_operator( - source_range_length(node.location.start_char, 1), - source_range_node(node) - ) + smap_operator(srange_length(node.start_char, 1), srange_node(node)) ) end end # Visit an ArgsForward node. def visit_args_forward(node) - s(:forwarded_args, [], source_map(expression: source_range_node(node))) + s(:forwarded_args, [], smap(srange_node(node))) end # Visit an ArrayLiteral node. @@ -218,12 +185,12 @@ def visit_array(node) :array, node.contents ? visit_all(node.contents.parts) : [], if node.lbracket.nil? - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) else - source_map_collection( - begin_token: source_range_node(node.lbracket), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_node(node.lbracket), + srange_length(node.end_char, -1), + srange_node(node) ) end ) @@ -237,8 +204,7 @@ def visit_aryptn(node) if node.rest.is_a?(VarField) if !node.rest.value.nil? children << s(:match_rest, [visit(node.rest)], nil) - elsif node.posts.empty? && - node.rest.location.start_char == node.rest.location.end_char + elsif node.posts.empty? && node.rest.start_char == node.rest.end_char # Here we have an implicit rest, as in [foo,]. parser has a specific # type for these patterns. type = :array_pattern_with_tail @@ -255,34 +221,29 @@ def visit_aryptn(node) s( type, children + visit_all(node.posts), - source_map_collection( - expression: - source_range( - node.constant.location.end_char + 1, - node.location.end_char - 1 - ) + smap_collection_bare( + srange(node.constant.end_char + 1, node.end_char - 1) ) ) ], - source_map_collection( - begin_token: - source_range_length(node.constant.location.end_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.constant.end_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) else s( type, children + visit_all(node.posts), - if buffer.source[node.location.start_char] == "[" - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + if buffer.source[node.start_char] == "[" + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) else - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) end ) end @@ -294,10 +255,8 @@ def visit_assign(node) location = target .location - .with_operator( - source_range_find_between(node.target, node.value, "=") - ) - .with_expression(source_range_node(node)) + .with_operator(srange_find_between(node.target, node.value, "=")) + .with_expression(srange_node(node)) s(target.type, target.children + [visit(node.value)], location) end @@ -305,17 +264,13 @@ def visit_assign(node) # Visit an Assoc node. def visit_assoc(node) if node.value.nil? - expression = - source_range(node.location.start_char, node.location.end_char - 1) + expression = srange(node.start_char, node.end_char - 1) type, location = if node.key.value.start_with?(/[A-Z]/) - [:const, source_map_constant(nil, expression, expression)] + [:const, smap_constant(nil, expression, expression)] else - [ - :send, - source_map_send(selector: expression, expression: expression) - ] + [:send, smap_send_bare(expression, expression)] end s( @@ -324,19 +279,19 @@ def visit_assoc(node) visit(node.key), s(type, [nil, node.key.value.chomp(":").to_sym], location) ], - source_map_operator( - source_range_length(node.key.location.end_char, -1), - source_range_node(node) + smap_operator( + srange_length(node.key.end_char, -1), + srange_node(node) ) ) else s( :pair, [visit(node.key), visit(node.value)], - source_map_operator( - source_range_search_between(node.key, node.value, "=>") || - source_range_length(node.key.location.end_char, -1), - source_range_node(node) + smap_operator( + srange_search_between(node.key, node.value, "=>") || + srange_length(node.key.end_char, -1), + srange_node(node) ) ) end @@ -347,16 +302,13 @@ def visit_assoc_splat(node) s( :kwsplat, [visit(node.value)], - source_map_operator( - source_range_length(node.location.start_char, 2), - source_range_node(node) - ) + smap_operator(srange_length(node.start_char, 2), srange_node(node)) ) end # Visit a Backref node. def visit_backref(node) - location = source_map(expression: source_range_node(node)) + location = smap(srange_node(node)) if node.value.match?(/^\$\d+$/) s(:nth_ref, [node.value[1..].to_i], location) @@ -375,7 +327,7 @@ def visit_bare_assoc_hash(node) :hash end, visit_all(node.assocs), - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) ) end @@ -384,15 +336,11 @@ def visit_BEGIN(node) s( :preexe, [visit(node.statements)], - source_map_keyword( - source_range_length(node.location.start_char, 5), - source_range_find( - node.location.start_char + 5, - node.statements.location.start_char, - "{" - ), - source_range_length(node.location.end_char, -1), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_find(node.start_char + 5, node.statements.start_char, "{"), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -400,10 +348,10 @@ def visit_BEGIN(node) # Visit a Begin node. def visit_begin(node) location = - source_map_collection( - begin_token: source_range_length(node.location.start_char, 5), - end_token: source_range_length(node.location.end_char, -3), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 5), + srange_length(node.end_char, -3), + srange_node(node) ) if node.bodystmt.empty? @@ -439,13 +387,9 @@ def visit_binary(node) node.operator ), [visit(node.left), visit(node.right)], - source_map_operator( - source_range_find_between( - node.left, - node.right, - node.operator.to_s - ), - source_range_node(node) + smap_operator( + srange_find_between(node.left, node.right, node.operator.to_s), + srange_node(node) ) ) when :=~ @@ -459,13 +403,9 @@ def visit_binary(node) s( :match_with_lvasgn, [visit(node.left), visit(node.right)], - source_map_operator( - source_range_find_between( - node.left, - node.right, - node.operator.to_s - ), - source_range_node(node) + smap_operator( + srange_find_between(node.left, node.right, node.operator.to_s), + srange_node(node) ) ) else @@ -479,15 +419,12 @@ def visit_binary(node) # Visit a BlockArg node. def visit_blockarg(node) if node.name.nil? - s(:blockarg, [nil], source_map_variable(nil, source_range_node(node))) + s(:blockarg, [nil], smap_variable(nil, srange_node(node))) else s( :blockarg, [node.name.value.to_sym], - source_map_variable( - source_range_node(node.name), - source_range_node(node) - ) + smap_variable(srange_node(node.name), srange_node(node)) ) end end @@ -499,10 +436,7 @@ def visit_block_var(node) s( :shadowarg, [local.value.to_sym], - source_map_variable( - source_range_node(local), - source_range_node(local) - ) + smap_variable(srange_node(local), srange_node(local)) ) end @@ -522,13 +456,13 @@ def visit_block_var(node) s( :arg, [required.value.to_sym], - source_map_variable( - source_range_node(required), - source_range_node(required) + smap_variable( + srange_node(required), + srange_node(required) ) ) ], - source_map_collection(expression: source_range_node(required)) + smap_collection_bare(srange_node(required)) ) else child = visit(required) @@ -543,10 +477,10 @@ def visit_block_var(node) s( :args, children + shadowargs, - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -566,17 +500,12 @@ def visit_bodystmt(node) children << visit(node.else_clause) location = - source_map_condition( - else_token: - source_range_length( - node.else_clause.location.start_char - 3, - -4 - ), - expression: - source_range( - location.expression.begin_pos, - node.else_clause.location.end_char - ) + smap_condition( + nil, + nil, + srange_length(node.else_clause.start_char - 3, -4), + nil, + srange(location.expression.begin_pos, node.else_clause.end_char) ) end @@ -608,9 +537,9 @@ def visit_break(node) s( :break, visit_all(node.arguments.parts), - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) end @@ -638,17 +567,18 @@ def visit_case(node) else_token = if clauses.last.is_a?(Else) - source_range_length(clauses.last.location.start_char, 4) + srange_length(clauses.last.start_char, 4) end s( node.consequent.is_a?(In) ? :case_match : :case, [visit(node.value)] + clauses.map { |clause| visit(clause) }, - source_map_condition( - keyword: source_range_length(node.location.start_char, 4), - else_token: else_token, - end_token: source_range_length(node.location.end_char, -3), - expression: source_range_node(node) + smap_condition( + srange_length(node.start_char, 4), + nil, + else_token, + srange_length(node.end_char, -3), + srange_node(node) ) ) end @@ -658,9 +588,10 @@ def visit_CHAR(node) s( :str, [node.value[1..]], - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + nil, + srange_node(node) ) ) end @@ -669,18 +600,18 @@ def visit_CHAR(node) def visit_class(node) operator = if node.superclass - source_range_find_between(node.constant, node.superclass, "<") + srange_find_between(node.constant, node.superclass, "<") end s( :class, [visit(node.constant), visit(node.superclass), visit(node.bodystmt)], - source_map_definition( - keyword: source_range_length(node.location.start_char, 5), - operator: operator, - name: source_range_node(node.constant), - end_token: source_range_length(node.location.end_char, -3) - ).with_expression(source_range_node(node)) + smap_definition( + srange_length(node.start_char, 5), + operator, + srange_node(node.constant), + srange_length(node.end_char, -3) + ).with_expression(srange_node(node)) ) end @@ -721,18 +652,17 @@ def visit_command_call(node) children += visit_all(node.arguments.arguments.parts) end - begin_token = - source_range_length(node.arguments.location.start_char, 1) - end_token = source_range_length(node.arguments.location.end_char, -1) + begin_token = srange_length(node.arguments.start_char, 1) + end_token = srange_length(node.arguments.end_char, -1) end dot_bound = if node.arguments - node.arguments.location.start_char + node.arguments.start_char elsif node.block - node.block.location.start_char + node.block.start_char else - node.location.end_char + node.end_char end call = @@ -743,37 +673,31 @@ def visit_command_call(node) :send end, children, - source_map_send( - dot: - if node.operator == :"::" - source_range_find( - node.receiver.location.end_char, - if node.message == :call - dot_bound - else - node.message.location.start_char - end, - "::" - ) - elsif node.operator - source_range_node(node.operator) - end, - begin_token: begin_token, - end_token: end_token, - selector: - node.message == :call ? nil : source_range_node(node.message), - expression: - if node.arguments.is_a?(ArgParen) || - (node.arguments.is_a?(Args) && node.arguments.parts.any?) - source_range( - node.location.start_char, - node.arguments.location.end_char - ) - elsif node.block - source_range_node(node.message) - else - source_range_node(node) - end + smap_send( + if node.operator == :"::" + srange_find( + node.receiver.end_char, + if node.message == :call + dot_bound + else + node.message.start_char + end, + "::" + ) + elsif node.operator + srange_node(node.operator) + end, + node.message == :call ? nil : srange_node(node.message), + begin_token, + end_token, + if node.arguments.is_a?(ArgParen) || + (node.arguments.is_a?(Args) && node.arguments.parts.any?) + srange(node.start_char, node.arguments.end_char) + elsif node.block + srange_node(node.message) + else + srange_node(node) + end ) ) @@ -783,14 +707,13 @@ def visit_command_call(node) s( type, [call, arguments, visit(node.block.bodystmt)], - source_map_collection( - begin_token: source_range_node(node.block.opening), - end_token: - source_range_length( - node.location.end_char, - node.block.opening.is_a?(Kw) ? -3 : -1 - ), - expression: source_range_node(node) + smap_collection( + srange_node(node.block.opening), + srange_length( + node.end_char, + node.block.opening.is_a?(Kw) ? -3 : -1 + ), + srange_node(node) ) ) else @@ -803,11 +726,7 @@ def visit_const(node) s( :const, [nil, node.value.to_sym], - source_map_constant( - nil, - source_range_node(node), - source_range_node(node) - ) + smap_constant(nil, srange_node(node), srange_node(node)) ) end @@ -820,10 +739,10 @@ def visit_const_path_field(node) s( :casgn, [visit(node.parent), node.constant.value.to_sym], - source_map_constant( - source_range_find_between(node.parent, node.constant, "::"), - source_range_node(node.constant), - source_range_node(node) + smap_constant( + srange_find_between(node.parent, node.constant, "::"), + srange_node(node.constant), + srange_node(node) ) ) end @@ -834,10 +753,10 @@ def visit_const_path_ref(node) s( :const, [visit(node.parent), node.constant.value.to_sym], - source_map_constant( - source_range_find_between(node.parent, node.constant, "::"), - source_range_node(node.constant), - source_range_node(node) + smap_constant( + srange_find_between(node.parent, node.constant, "::"), + srange_node(node.constant), + srange_node(node) ) ) end @@ -847,11 +766,7 @@ def visit_const_ref(node) s( :const, [nil, node.constant.value.to_sym], - source_map_constant( - nil, - source_range_node(node.constant), - source_range_node(node) - ) + smap_constant(nil, srange_node(node.constant), srange_node(node)) ) end @@ -860,7 +775,7 @@ def visit_cvar(node) s( :cvar, [node.value.to_sym], - source_map_variable(source_range_node(node), source_range_node(node)) + smap_variable(srange_node(node), srange_node(node)) ) end @@ -875,7 +790,7 @@ def visit_def(node) s( child.type, child.children, - source_map_collection(expression: nil) + smap_collection_bare(child.location&.expression) ) when Paren child = visit(node.params.contents) @@ -883,37 +798,38 @@ def visit_def(node) s( child.type, child.children, - source_map_collection( - begin_token: - source_range_length(node.params.location.start_char, 1), - end_token: - source_range_length(node.params.location.end_char, -1), - expression: source_range_node(node.params) + smap_collection( + srange_length(node.params.start_char, 1), + srange_length(node.params.end_char, -1), + srange_node(node.params) ) ) else - s(:args, [], source_map_collection(expression: nil)) + s(:args, [], smap_collection_bare(nil)) end location = if node.endless? - source_map_method_definition( - keyword: source_range_length(node.location.start_char, 3), - assignment: - source_range_find_between( - (node.params || node.name), - node.bodystmt, - "=" - ), - name: source_range_node(node.name), - expression: source_range_node(node) + smap_method_definition( + srange_length(node.start_char, 3), + nil, + srange_node(node.name), + nil, + srange_find_between( + (node.params || node.name), + node.bodystmt, + "=" + ), + srange_node(node) ) else - source_map_method_definition( - keyword: source_range_length(node.location.start_char, 3), - name: source_range_node(node.name), - end_token: source_range_length(node.location.end_char, -3), - expression: source_range_node(node) + smap_method_definition( + srange_length(node.start_char, 3), + nil, + srange_node(node.name), + srange_length(node.end_char, -3), + nil, + srange_node(node) ) end @@ -923,13 +839,13 @@ def visit_def(node) s( :defs, [visit(target), name, args, visit(node.bodystmt)], - source_map_method_definition( - keyword: location.keyword, - assignment: location.assignment, - operator: source_range_node(node.operator), - name: location.name, - end_token: location.end, - expression: location.expression + smap_method_definition( + location.keyword, + srange_node(node.operator), + location.name, + location.end, + location.assignment, + location.expression ) ) else @@ -939,23 +855,23 @@ def visit_def(node) # Visit a Defined node. def visit_defined(node) - paren_range = (node.location.start_char + 8)...node.location.end_char + paren_range = (node.start_char + 8)...node.end_char begin_token, end_token = if buffer.source[paren_range].include?("(") [ - source_range_find(paren_range.begin, paren_range.end, "("), - source_range_length(node.location.end_char, -1) + srange_find(paren_range.begin, paren_range.end, "("), + srange_length(node.end_char, -1) ] end s( :defined?, [visit(node.value)], - source_map_keyword( - source_range_length(node.location.start_char, 8), + smap_keyword( + srange_length(node.start_char, 8), begin_token, end_token, - source_range_node(node) + srange_node(node) ) ) end @@ -964,17 +880,13 @@ def visit_defined(node) def visit_dyna_symbol(node) location = if node.quote - source_map_collection( - begin_token: - source_range_length( - node.location.start_char, - node.quote.length - ), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, node.quote.length), + srange_length(node.end_char, -1), + srange_node(node) ) else - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) end if node.parts.length == 1 && node.parts.first.is_a?(TStringContent) @@ -998,16 +910,12 @@ def visit_elsif(node) else_token = case node.consequent when Elsif - source_range_length(node.consequent.location.start_char, 5) + srange_length(node.consequent.start_char, 5) when Else - source_range_length(node.consequent.location.start_char, 4) + srange_length(node.consequent.start_char, 4) end - expression = - source_range( - node.location.start_char, - node.statements.location.end_char - 1 - ) + expression = srange(node.start_char, node.statements.end_char - 1) s( :if, @@ -1016,10 +924,12 @@ def visit_elsif(node) visit(node.statements), visit(node.consequent) ], - source_map_condition( - keyword: source_range_length(node.location.start_char, 5), - else_token: else_token, - expression: expression + smap_condition( + srange_length(node.start_char, 5), + nil, + else_token, + nil, + expression ) ) end @@ -1029,35 +939,34 @@ def visit_END(node) s( :postexe, [visit(node.statements)], - source_map_keyword( - source_range_length(node.location.start_char, 3), - source_range_find( - node.location.start_char + 3, - node.statements.location.start_char, - "{" - ), - source_range_length(node.location.end_char, -1), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 3), + srange_find(node.start_char + 3, node.statements.start_char, "{"), + srange_length(node.end_char, -1), + srange_node(node) ) ) end # Visit an Ensure node. def visit_ensure(node) - start_char = node.location.start_char + start_char = node.start_char end_char = if node.statements.empty? start_char + 6 else - node.statements.body.last.location.end_char + node.statements.body.last.end_char end s( :ensure, [visit(node.statements)], - source_map_condition( - keyword: source_range_length(start_char, 6), - expression: source_range(start_char, end_char) + smap_condition( + srange_length(start_char, 6), + nil, + nil, + nil, + srange(start_char, end_char) ) ) end @@ -1090,15 +999,11 @@ def visit_field(node) # Visit a FloatLiteral node. def visit_float(node) operator = - if %w[+ -].include?(buffer.source[node.location.start_char]) - source_range_length(node.location.start_char, 1) + if %w[+ -].include?(buffer.source[node.start_char]) + srange_length(node.start_char, 1) end - s( - :float, - [node.value.to_f], - source_map_operator(operator, source_range_node(node)) - ) + s(:float, [node.value.to_f], smap_operator(operator, srange_node(node))) end # Visit a FndPtn node. @@ -1106,9 +1011,9 @@ def visit_fndptn(node) left, right = [node.left, node.right].map do |child| location = - source_map_operator( - source_range_length(child.location.start_char, 1), - source_range_node(child) + smap_operator( + srange_length(child.start_char, 1), + srange_node(child) ) if child.is_a?(VarField) && child.value.nil? @@ -1122,10 +1027,10 @@ def visit_fndptn(node) s( :find_pattern, [left, *visit_all(node.values), right], - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) @@ -1141,12 +1046,12 @@ def visit_for(node) s( :for, [visit(node.index), visit(node.collection), visit(node.statements)], - source_map_for( - source_range_length(node.location.start_char, 3), - source_range_find_between(node.index, node.collection, "in"), - source_range_search_between(node.collection, node.statements, "do"), - source_range_length(node.location.end_char, -3), - source_range_node(node) + smap_for( + srange_length(node.start_char, 3), + srange_find_between(node.index, node.collection, "in"), + srange_search_between(node.collection, node.statements, "do"), + srange_length(node.end_char, -3), + srange_node(node) ) ) end @@ -1156,7 +1061,7 @@ def visit_gvar(node) s( :gvar, [node.value.to_sym], - source_map_variable(source_range_node(node), source_range_node(node)) + smap_variable(srange_node(node), srange_node(node)) ) end @@ -1165,10 +1070,10 @@ def visit_hash(node) s( :hash, visit_all(node.assocs), - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -1260,20 +1165,17 @@ def visit_heredoc(node) heredoc_segments.trim! location = - source_map_heredoc( - source_range_node(node.beginning), - source_range( + smap_heredoc( + srange_node(node.beginning), + srange( if node.parts.empty? - node.beginning.location.end_char + node.beginning.end_char else - node.parts.first.location.start_char + node.parts.first.start_char end, - node.ending.location.start_char + node.ending.start_char ), - source_range( - node.ending.location.start_char, - node.ending.location.end_char - 1 - ) + srange(node.ending.start_char, node.ending.end_char - 1) ) if node.beginning.value.match?(/`\w+`\z/) @@ -1326,7 +1228,7 @@ def visit_ident(node) s( :lvar, [node.value.to_sym], - source_map_variable(source_range_node(node), source_range_node(node)) + smap_variable(srange_node(node), srange_node(node)) ) end @@ -1359,40 +1261,40 @@ def visit_if(node) :if, [predicate, visit(node.statements), visit(node.consequent)], if node.modifier? - source_map_keyword_bare( - source_range_find_between(node.statements, node.predicate, "if"), - source_range_node(node) + smap_keyword_bare( + srange_find_between(node.statements, node.predicate, "if"), + srange_node(node) ) else - begin_start = node.predicate.location.end_char + begin_start = node.predicate.end_char begin_end = if node.statements.empty? - node.statements.location.end_char + node.statements.end_char else - node.statements.body.first.location.start_char + node.statements.body.first.start_char end begin_token = if buffer.source[begin_start...begin_end].include?("then") - source_range_find(begin_start, begin_end, "then") + srange_find(begin_start, begin_end, "then") elsif buffer.source[begin_start...begin_end].include?(";") - source_range_find(begin_start, begin_end, ";") + srange_find(begin_start, begin_end, ";") end else_token = case node.consequent when Elsif - source_range_length(node.consequent.location.start_char, 5) + srange_length(node.consequent.start_char, 5) when Else - source_range_length(node.consequent.location.start_char, 4) + srange_length(node.consequent.start_char, 4) end - source_map_condition( - keyword: source_range_length(node.location.start_char, 2), - begin_token: begin_token, - else_token: else_token, - end_token: source_range_length(node.location.end_char, -3), - expression: source_range_node(node) + smap_condition( + srange_length(node.start_char, 2), + begin_token, + else_token, + srange_length(node.end_char, -3), + srange_node(node) ) end ) @@ -1403,7 +1305,11 @@ def visit_if_op(node) s( :if, [visit(node.predicate), visit(node.truthy), visit(node.falsy)], - nil + smap_ternary( + srange_find_between(node.predicate, node.truthy, "?"), + srange_find_between(node.truthy, node.falsy, ":"), + srange_node(node) + ) ) end @@ -1417,7 +1323,7 @@ def visit_imaginary(node) # case. Maybe there's an API for this but I can't find it. eval(node.value) ], - source_map_operator(nil, source_range_node(node)) + smap_operator(nil, srange_node(node)) ) end @@ -1446,23 +1352,23 @@ def visit_in(node) ) else begin_token = - source_range_search_between(node.pattern, node.statements, "then") + srange_search_between(node.pattern, node.statements, "then") end_char = if begin_token || node.statements.empty? - node.statements.location.end_char - 1 + node.statements.end_char - 1 else - node.statements.body.last.location.start_char + node.statements.body.last.start_char end s( :in_pattern, [visit(node.pattern), nil, visit(node.statements)], - source_map_keyword( - source_range_length(node.location.start_char, 2), + smap_keyword( + srange_length(node.start_char, 2), begin_token, nil, - source_range(node.location.start_char, end_char) + srange(node.start_char, end_char) ) ) end @@ -1471,15 +1377,11 @@ def visit_in(node) # Visit an Int node. def visit_int(node) operator = - if %w[+ -].include?(buffer.source[node.location.start_char]) - source_range_length(node.location.start_char, 1) + if %w[+ -].include?(buffer.source[node.start_char]) + srange_length(node.start_char, 1) end - s( - :int, - [node.value.to_i], - source_map_operator(operator, source_range_node(node)) - ) + s(:int, [node.value.to_i], smap_operator(operator, srange_node(node))) end # Visit an IVar node. @@ -1487,13 +1389,13 @@ def visit_ivar(node) s( :ivar, [node.value.to_sym], - source_map_variable(source_range_node(node), source_range_node(node)) + smap_variable(srange_node(node), srange_node(node)) ) end # Visit a Kw node. def visit_kw(node) - location = source_map(expression: source_range_node(node)) + location = smap(srange_node(node)) case node.value when "__FILE__" @@ -1514,15 +1416,12 @@ def visit_kw(node) # Visit a KwRestParam node. def visit_kwrest_param(node) if node.name.nil? - s(:kwrestarg, [], source_map_variable(nil, source_range_node(node))) + s(:kwrestarg, [], smap_variable(nil, srange_node(node))) else s( :kwrestarg, [node.name.value.to_sym], - source_map_variable( - source_range_node(node.name), - source_range_node(node) - ) + smap_variable(srange_node(node.name), srange_node(node)) ) end end @@ -1532,10 +1431,7 @@ def visit_label(node) s( :sym, [node.value.chomp(":").to_sym], - source_map_collection( - expression: - source_range(node.location.start_char, node.location.end_char - 1) - ) + smap_collection_bare(srange(node.start_char, node.end_char - 1)) ) end @@ -1550,42 +1446,30 @@ def visit_lambda(node) args_node = maximum end - begin_start = node.params.location.end_char begin_token, end_token = - if buffer.source[begin_start - 1] == "{" - [ - source_range_length(begin_start, -1), - source_range_length(node.location.end_char, -1) - ] + if (srange = srange_search_between(node.params, node.statements, "{")) + [srange, srange_length(node.end_char, -1)] else [ - source_range_length(begin_start, -2), - source_range_length(node.location.end_char, -3) + srange_find_between(node.params, node.statements, "do"), + srange_length(node.end_char, -3) ] end - selector = source_range_length(node.location.start_char, 2) + selector = srange_length(node.start_char, 2) s( type, [ if ::Parser::Builders::Default.emit_lambda - s(:lambda, [], source_map(expression: selector)) + s(:lambda, [], smap(selector)) else - s( - :send, - [nil, :lambda], - source_map_send(selector: selector, expression: selector) - ) + s(:send, [nil, :lambda], smap_send_bare(selector, selector)) end, args_node, visit(node.statements) ], - source_map_collection( - begin_token: begin_token, - end_token: end_token, - expression: source_range_node(node) - ) + smap_collection(begin_token, end_token, srange_node(node)) ) end @@ -1596,21 +1480,18 @@ def visit_lambda_var(node) s( :shadowarg, [local.value.to_sym], - source_map_variable( - source_range_node(local), - source_range_node(local) - ) + smap_variable(srange_node(local), srange_node(local)) ) end location = - if node.location.start_char == node.location.end_char - source_map_collection(expression: nil) + if node.start_char == node.end_char + smap_collection_bare(nil) else - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) end @@ -1622,9 +1503,9 @@ def visit_massign(node) s( :masgn, [visit(node.target), visit(node.value)], - source_map_operator( - source_range_find_between(node.target, node.value, "="), - source_range_node(node) + smap_operator( + srange_find_between(node.target, node.value, "="), + srange_node(node) ) ) end @@ -1678,16 +1559,13 @@ def visit_mlhs(node) s( :arg, [part.value.to_sym], - source_map_variable( - source_range_node(part), - source_range_node(part) - ) + smap_variable(srange_node(part), srange_node(part)) ) else visit(part) end end, - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) ) end @@ -1698,10 +1576,10 @@ def visit_mlhs_paren(node) s( child.type, child.children, - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -1711,11 +1589,12 @@ def visit_module(node) s( :module, [visit(node.constant), visit(node.bodystmt)], - source_map_definition( - keyword: source_range_length(node.location.start_char, 6), - name: source_range_node(node.constant), - end_token: source_range_length(node.location.end_char, -3) - ).with_expression(source_range_node(node)) + smap_definition( + srange_length(node.start_char, 6), + nil, + srange_node(node.constant), + srange_length(node.end_char, -3) + ).with_expression(srange_node(node)) ) end @@ -1735,9 +1614,9 @@ def visit_next(node) s( :next, visit_all(node.arguments.parts), - source_map_keyword_bare( - source_range_length(node.location.start_char, 4), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 4), + srange_node(node) ) ) end @@ -1745,8 +1624,8 @@ def visit_next(node) # Visit a Not node. def visit_not(node) if node.statement.nil? - begin_token = source_range_find(node.location.start_char, nil, "(") - end_token = source_range_find(node.location.start_char, nil, ")") + begin_token = srange_find(node.start_char, nil, "(") + end_token = srange_find(node.start_char, nil, ")") s( :send, @@ -1754,40 +1633,38 @@ def visit_not(node) s( :begin, [], - source_map_collection( - begin_token: begin_token, - end_token: end_token, - expression: begin_token.join(end_token) + smap_collection( + begin_token, + end_token, + begin_token.join(end_token) ) ), :! ], - source_map_send( - selector: source_range_length(node.location.start_char, 3), - expression: source_range_node(node) - ) + smap_send_bare(srange_length(node.start_char, 3), srange_node(node)) ) else begin_token, end_token = if node.parentheses? [ - source_range_find( - node.location.start_char + 3, - node.statement.location.start_char, + srange_find( + node.start_char + 3, + node.statement.start_char, "(" ), - source_range_length(node.location.end_char, -1) + srange_length(node.end_char, -1) ] end s( :send, [visit(node.statement), :!], - source_map_send( - begin_token: begin_token, - end_token: end_token, - selector: source_range_length(node.location.start_char, 3), - expression: source_range_node(node) + smap_send( + nil, + srange_length(node.start_char, 3), + begin_token, + end_token, + srange_node(node) ) ) end @@ -1795,60 +1672,22 @@ def visit_not(node) # Visit an OpAssign node. def visit_opassign(node) + target = visit(node.target) location = - case node.target - when ARefField - source_map_index( - begin_token: - source_range_find( - node.target.collection.location.end_char, - if node.target.index - node.target.index.location.start_char - else - node.target.location.end_char - end, - "[" - ), - end_token: source_range_length(node.target.location.end_char, -1), - expression: source_range_node(node) - ) - when Field - source_map_send( - dot: - if node.target.operator == :"::" - source_range_find_between( - node.target.parent, - node.target.name, - "::" - ) - else - source_range_node(node.target.operator) - end, - selector: source_range_node(node.target.name), - expression: source_range_node(node) - ) - else - source_map_variable( - source_range_node(node.target), - source_range_node(node) - ) - end - - location = location.with_operator(source_range_node(node.operator)) + target + .location + .with_expression(srange_node(node)) + .with_operator(srange_node(node.operator)) case node.operator.value when "||=" - s(:or_asgn, [visit(node.target), visit(node.value)], location) + s(:or_asgn, [target, visit(node.value)], location) when "&&=" - s(:and_asgn, [visit(node.target), visit(node.value)], location) + s(:and_asgn, [target, visit(node.value)], location) else s( :op_asgn, - [ - visit(node.target), - node.operator.value.chomp("=").to_sym, - visit(node.value) - ], + [target, node.operator.value.chomp("=").to_sym, visit(node.value)], location ) end @@ -1867,10 +1706,7 @@ def visit_params(node) s( :arg, [required.value.to_sym], - source_map_variable( - source_range_node(required), - source_range_node(required) - ) + smap_variable(srange_node(required), srange_node(required)) ) end end @@ -1880,10 +1716,10 @@ def visit_params(node) s( :optarg, [name.value.to_sym, visit(value)], - source_map_variable( - source_range_node(name), - source_range_node(name).join(source_range_node(value)) - ).with_operator(source_range_find_between(name, value, "=")) + smap_variable( + srange_node(name), + srange_node(name).join(srange_node(value)) + ).with_operator(srange_find_between(name, value, "=")) ) end @@ -1896,10 +1732,7 @@ def visit_params(node) s( :arg, [post.value.to_sym], - source_map_variable( - source_range_node(post), - source_range_node(post) - ) + smap_variable(srange_node(post), srange_node(post)) ) end @@ -1911,24 +1744,18 @@ def visit_params(node) s( :kwoptarg, [key, visit(value)], - source_map_variable( - source_range( - name.location.start_char, - name.location.end_char - 1 - ), - source_range_node(name).join(source_range_node(value)) + smap_variable( + srange(name.start_char, name.end_char - 1), + srange_node(name).join(srange_node(value)) ) ) else s( :kwarg, [key], - source_map_variable( - source_range( - name.location.start_char, - name.location.end_char - 1 - ), - source_range_node(name) + smap_variable( + srange(name.start_char, name.end_char - 1), + srange_node(name) ) ) end @@ -1941,10 +1768,7 @@ def visit_params(node) children << s( :kwnilarg, [], - source_map_variable( - source_range_length(node.location.end_char, -3), - source_range_node(node) - ) + smap_variable(srange_length(node.end_char, -3), srange_node(node)) ) else children << visit(node.keyword_rest) @@ -1953,8 +1777,7 @@ def visit_params(node) children << visit(node.block) if node.block if node.keyword_rest.is_a?(ArgsForward) - location = - source_map(expression: source_range_node(node.keyword_rest)) + location = smap(srange_node(node.keyword_rest)) # If there are no other arguments and we have the emit_forward_arg # option enabled, then the entire argument list is represented by a @@ -1970,16 +1793,23 @@ def visit_params(node) children.insert(index, s(:forward_arg, [], location)) end - s(:args, children, nil) + location = + unless children.empty? + first = children.first.location.expression + last = children.last.location.expression + smap_collection_bare(first.join(last)) + end + + s(:args, children, location) end # Visit a Paren node. def visit_paren(node) location = - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) if node.contents.nil? || @@ -1999,22 +1829,14 @@ def visit_pinned_begin(node) s( :begin, [visit(node.statement)], - source_map_collection( - begin_token: - source_range_length(node.location.start_char + 1, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: - source_range( - node.location.start_char + 1, - node.location.end_char - ) + smap_collection( + srange_length(node.start_char + 1, 1), + srange_length(node.end_char, -1), + srange(node.start_char + 1, node.end_char) ) ) ], - source_map_send( - selector: source_range_length(node.location.start_char, 1), - expression: source_range_node(node) - ) + smap_send_bare(srange_length(node.start_char, 1), srange_node(node)) ) end @@ -2023,10 +1845,7 @@ def visit_pinned_var_ref(node) s( :pin, [visit(node.value)], - source_map_send( - selector: source_range_length(node.location.start_char, 1), - expression: source_range_node(node) - ) + smap_send_bare(srange_length(node.start_char, 1), srange_node(node)) ) end @@ -2067,10 +1886,7 @@ def visit_range(node) s( node.operator.value == ".." ? :irange : :erange, [visit(node.left), visit(node.right)], - source_map_operator( - source_range_node(node.operator), - source_range_node(node) - ) + smap_operator(srange_node(node.operator), srange_node(node)) ) end @@ -2079,32 +1895,18 @@ def visit_rassign(node) s( node.operator.value == "=>" ? :match_pattern : :match_pattern_p, [visit(node.value), visit(node.pattern)], - source_map_operator( - source_range_node(node.operator), - source_range_node(node) - ) + smap_operator(srange_node(node.operator), srange_node(node)) ) end # Visit a Rational node. def visit_rational(node) - s( - :rational, - [node.value.to_r], - source_map_operator(nil, source_range_node(node)) - ) + s(:rational, [node.value.to_r], smap_operator(nil, srange_node(node))) end # Visit a Redo node. def visit_redo(node) - s( - :redo, - [], - source_map_keyword_bare( - source_range_node(node), - source_range_node(node) - ) - ) + s(:redo, [], smap_keyword_bare(srange_node(node), srange_node(node))) end # Visit a RegexpLiteral node. @@ -2115,27 +1917,13 @@ def visit_regexp_literal(node) s( :regopt, node.ending.scan(/[a-z]/).sort.map(&:to_sym), - source_map( - expression: - source_range_length( - node.location.end_char, - -(node.ending.length - 1) - ) - ) + smap(srange_length(node.end_char, -(node.ending.length - 1))) ) ), - source_map_collection( - begin_token: - source_range_length( - node.location.start_char, - node.beginning.length - ), - end_token: - source_range_length( - node.location.end_char - node.ending.length, - 1 - ), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, node.beginning.length), + srange_length(node.end_char - node.ending.length, 1), + srange_node(node) ) ) end @@ -2145,13 +1933,13 @@ def visit_rescue(node) # In the parser gem, there is a separation between the rescue node and # the rescue body. They have different bounds, so we have to calculate # those here. - start_char = node.location.start_char + start_char = node.start_char body_end_char = if node.statements.empty? start_char + 6 else - node.statements.body.last.location.end_char + node.statements.body.last.end_char end end_char = @@ -2162,16 +1950,16 @@ def visit_rescue(node) if end_node.statements.empty? start_char + 6 else - end_node.statements.body.last.location.end_char + end_node.statements.body.last.end_char end else body_end_char end # These locations are reused for multiple children. - keyword = source_range_length(start_char, 6) - body_expression = source_range(start_char, body_end_char) - expression = source_range(start_char, end_char) + keyword = srange_length(start_char, 6) + body_expression = srange(start_char, body_end_char) + expression = srange(start_char, end_char) exceptions = case node.exception&.exceptions @@ -2208,19 +1996,13 @@ def visit_rescue(node) s( :resbody, [nil, nil, visit(node.statements)], - source_map_rescue_body( - keyword: keyword, - expression: body_expression - ) + smap_rescue_body(keyword, nil, nil, body_expression) ) elsif node.exception.variable.nil? s( :resbody, [exceptions, nil, visit(node.statements)], - source_map_rescue_body( - keyword: keyword, - expression: body_expression - ) + smap_rescue_body(keyword, nil, nil, body_expression) ) else s( @@ -2230,15 +2012,15 @@ def visit_rescue(node) visit(node.exception.variable), visit(node.statements) ], - source_map_rescue_body( - keyword: keyword, - assoc: - source_range_find( - node.location.start_char + 6, - node.exception.variable.location.start_char, - "=>" - ), - expression: body_expression + smap_rescue_body( + keyword, + srange_find( + node.start_char + 6, + node.exception.variable.start_char, + "=>" + ), + nil, + body_expression ) ) end @@ -2250,13 +2032,12 @@ def visit_rescue(node) children << nil end - s(:rescue, children, source_map_condition(expression: expression)) + s(:rescue, children, smap_condition_bare(expression)) end # Visit a RescueMod node. def visit_rescue_mod(node) - keyword = - source_range_find_between(node.statement, node.value, "rescue") + keyword = srange_find_between(node.statement, node.value, "rescue") s( :rescue, @@ -2265,14 +2046,16 @@ def visit_rescue_mod(node) s( :resbody, [nil, nil, visit(node.value)], - source_map_rescue_body( - keyword: keyword, - expression: keyword.join(source_range_node(node.value)) + smap_rescue_body( + keyword, + nil, + nil, + keyword.join(srange_node(node.value)) ) ), nil ], - source_map_condition(expression: source_range_node(node)) + smap_condition_bare(srange_node(node)) ) end @@ -2282,26 +2065,16 @@ def visit_rest_param(node) s( :restarg, [node.name.value.to_sym], - source_map_variable( - source_range_node(node.name), - source_range_node(node) - ) + smap_variable(srange_node(node.name), srange_node(node)) ) else - s(:restarg, [], source_map_variable(nil, source_range_node(node))) + s(:restarg, [], smap_variable(nil, srange_node(node))) end end # Visit a Retry node. def visit_retry(node) - s( - :retry, - [], - source_map_keyword_bare( - source_range_node(node), - source_range_node(node) - ) - ) + s(:retry, [], smap_keyword_bare(srange_node(node), srange_node(node))) end # Visit a ReturnNode node. @@ -2309,9 +2082,9 @@ def visit_return(node) s( :return, node.arguments ? visit_all(node.arguments.parts) : [], - source_map_keyword_bare( - source_range_length(node.location.start_char, 6), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 6), + srange_node(node) ) ) end @@ -2321,16 +2094,12 @@ def visit_sclass(node) s( :sclass, [visit(node.target), visit(node.bodystmt)], - source_map_definition( - keyword: source_range_length(node.location.start_char, 5), - operator: - source_range_find( - node.location.start_char + 5, - node.target.location.start_char, - "<<" - ), - end_token: source_range_length(node.location.end_char, -3) - ).with_expression(source_range_node(node)) + smap_definition( + srange_length(node.start_char, 5), + srange_find(node.start_char + 5, node.target.start_char, "<<"), + nil, + srange_length(node.end_char, -3) + ).with_expression(srange_node(node)) ) end @@ -2351,12 +2120,8 @@ def visit_statements(node) s( :begin, visit_all(children), - source_map_collection( - expression: - source_range( - children.first.location.start_char, - children.last.location.end_char - ) + smap_collection_bare( + srange(children.first.start_char, children.last.end_char) ) ) end @@ -2364,15 +2129,11 @@ def visit_statements(node) # Visit a StringConcat node. def visit_string_concat(node) - location = source_map_collection(expression: source_range_node(node)) - - s(:dstr, [visit(node.left), visit(node.right)], location) - end - - # Visit a StringContent node. - def visit_string_content(node) - # Can get here if you're inside a hash pattern, e.g., in "a": 1 - s(:sym, [node.parts.first.value.to_sym], nil) + s( + :dstr, + [visit(node.left), visit(node.right)], + smap_collection_bare(srange_node(node)) + ) end # Visit a StringDVar node. @@ -2385,10 +2146,10 @@ def visit_string_embexpr(node) s( :begin, visit(node.statements).then { |child| child ? [child] : [] }, - source_map_collection( - begin_token: source_range_length(node.location.start_char, 2), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, 2), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -2397,17 +2158,13 @@ def visit_string_embexpr(node) def visit_string_literal(node) location = if node.quote - source_map_collection( - begin_token: - source_range_length( - node.location.start_char, - node.quote.length - ), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length(node.start_char, node.quote.length), + srange_length(node.end_char, -1), + srange_node(node) ) else - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) end if node.parts.empty? @@ -2426,9 +2183,9 @@ def visit_super(node) s( :super, visit_all(node.arguments.parts), - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) else @@ -2437,15 +2194,11 @@ def visit_super(node) s( :super, [], - source_map_keyword( - source_range_length(node.location.start_char, 5), - source_range_find( - node.location.start_char + 5, - node.location.end_char, - "(" - ), - source_range_length(node.location.end_char, -1), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_find(node.start_char + 5, node.end_char, "("), + srange_length(node.end_char, -1), + srange_node(node) ) ) when ArgsForward @@ -2454,15 +2207,11 @@ def visit_super(node) s( :super, visit_all(node.arguments.arguments.parts), - source_map_keyword( - source_range_length(node.location.start_char, 5), - source_range_find( - node.location.start_char + 5, - node.location.end_char, - "(" - ), - source_range_length(node.location.end_char, -1), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_find(node.start_char + 5, node.end_char, "("), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -2472,17 +2221,14 @@ def visit_super(node) # Visit a SymbolLiteral node. def visit_symbol_literal(node) begin_token = - if buffer.source[node.location.start_char] == ":" - source_range_length(node.location.start_char, 1) + if buffer.source[node.start_char] == ":" + srange_length(node.start_char, 1) end s( :sym, [node.value.value.to_sym], - source_map_collection( - begin_token: begin_token, - expression: source_range_node(node) - ) + smap_collection(begin_token, nil, srange_node(node)) ) end @@ -2517,19 +2263,13 @@ def visit_top_const_field(node) s( :casgn, [ - s( - :cbase, - [], - source_map( - expression: source_range_length(node.location.start_char, 2) - ) - ), + s(:cbase, [], smap(srange_length(node.start_char, 2))), node.constant.value.to_sym ], - source_map_constant( - source_range_length(node.location.start_char, 2), - source_range_node(node.constant), - source_range_node(node) + smap_constant( + srange_length(node.start_char, 2), + srange_node(node.constant), + srange_node(node) ) ) end @@ -2539,19 +2279,13 @@ def visit_top_const_ref(node) s( :const, [ - s( - :cbase, - [], - source_map( - expression: source_range_length(node.location.start_char, 2) - ) - ), + s(:cbase, [], smap(srange_length(node.start_char, 2))), node.constant.value.to_sym ], - source_map_constant( - source_range_length(node.location.start_char, 2), - source_range_node(node.constant), - source_range_node(node) + smap_constant( + srange_length(node.start_char, 2), + srange_node(node.constant), + srange_node(node) ) ) end @@ -2563,7 +2297,7 @@ def visit_tstring_content(node) s( :str, ["\"#{dumped}\"".undump], - source_map_collection(expression: source_range_node(node)) + smap_collection_bare(srange_node(node)) ) end @@ -2593,9 +2327,9 @@ def visit_undef(node) s( :undef, visit_all(node.symbols), - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) end @@ -2625,19 +2359,17 @@ def visit_unless(node) :if, [predicate, visit(node.consequent), visit(node.statements)], if node.modifier? - source_map_keyword_bare( - source_range_find_between( - node.statements, - node.predicate, - "unless" - ), - source_range_node(node) + smap_keyword_bare( + srange_find_between(node.statements, node.predicate, "unless"), + srange_node(node) ) else - source_map_condition( - keyword: source_range_length(node.location.start_char, 6), - end_token: source_range_length(node.location.end_char, -3), - expression: source_range_node(node) + smap_condition( + srange_length(node.start_char, 6), + srange_search_between(node.predicate, node.statements, "then"), + nil, + srange_length(node.end_char, -3), + srange_node(node) ) end ) @@ -2649,20 +2381,17 @@ def visit_until(node) loop_post?(node) ? :until_post : :until, [visit(node.predicate), visit(node.statements)], if node.modifier? - source_map_keyword_bare( - source_range_find_between( - node.statements, - node.predicate, - "until" - ), - source_range_node(node) + smap_keyword_bare( + srange_find_between(node.statements, node.predicate, "until"), + srange_node(node) ) else - source_map_keyword( - source_range_length(node.location.start_char, 5), - nil, - source_range_length(node.location.end_char, -3), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_search_between(node.predicate, node.statements, "do") || + srange_search_between(node.predicate, node.statements, ";"), + srange_length(node.end_char, -3), + srange_node(node) ) end ) @@ -2687,27 +2416,16 @@ def visit_var_field(node) s( :match_var, [name], - source_map_variable( - source_range_node(node.value), - source_range_node(node.value) - ) + smap_variable(srange_node(node.value), srange_node(node.value)) ) elsif node.value.is_a?(Const) s( :casgn, [nil, name], - source_map_constant( - nil, - source_range_node(node.value), - source_range_node(node) - ) + smap_constant(nil, srange_node(node.value), srange_node(node)) ) else - location = - source_map_variable( - source_range_node(node), - source_range_node(node) - ) + location = smap_variable(srange_node(node), srange_node(node)) case node.value when CVar @@ -2747,27 +2465,27 @@ def visit_vcall(node) # Visit a When node. def visit_when(node) - keyword = source_range_length(node.location.start_char, 4) + keyword = srange_length(node.start_char, 4) begin_token = - if buffer.source[node.statements.location.start_char] == ";" - source_range_length(node.statements.location.start_char, 1) + if buffer.source[node.statements.start_char] == ";" + srange_length(node.statements.start_char, 1) end end_char = if node.statements.body.empty? - node.statements.location.end_char + node.statements.end_char else - node.statements.body.last.location.end_char + node.statements.body.last.end_char end s( :when, visit_all(node.arguments.parts) + [visit(node.statements)], - source_map_keyword( + smap_keyword( keyword, begin_token, nil, - source_range(keyword.begin_pos, end_char) + srange(keyword.begin_pos, end_char) ) ) end @@ -2778,20 +2496,17 @@ def visit_while(node) loop_post?(node) ? :while_post : :while, [visit(node.predicate), visit(node.statements)], if node.modifier? - source_map_keyword_bare( - source_range_find_between( - node.statements, - node.predicate, - "while" - ), - source_range_node(node) + smap_keyword_bare( + srange_find_between(node.statements, node.predicate, "while"), + srange_node(node) ) else - source_map_keyword( - source_range_length(node.location.start_char, 5), - nil, - source_range_length(node.location.end_char, -3), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_search_between(node.predicate, node.statements, "do") || + srange_search_between(node.predicate, node.statements, ";"), + srange_length(node.end_char, -3), + srange_node(node) ) end ) @@ -2824,10 +2539,13 @@ def visit_xstring_literal(node) s( :xstr, visit_all(node.parts), - source_map_collection( - begin_token: source_range_length(node.location.start_char, 1), - end_token: source_range_length(node.location.end_char, -1), - expression: source_range_node(node) + smap_collection( + srange_length( + node.start_char, + buffer.source[node.start_char] == "%" ? 3 : 1 + ), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -2838,29 +2556,29 @@ def visit_yield(node) s( :yield, [], - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) when Args s( :yield, visit_all(node.arguments.parts), - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) else s( :yield, visit_all(node.arguments.contents.parts), - source_map_keyword( - source_range_length(node.location.start_char, 5), - source_range_length(node.arguments.location.start_char, 1), - source_range_length(node.location.end_char, -1), - source_range_node(node) + smap_keyword( + srange_length(node.start_char, 5), + srange_length(node.arguments.start_char, 1), + srange_length(node.end_char, -1), + srange_node(node) ) ) end @@ -2871,9 +2589,9 @@ def visit_zsuper(node) s( :zsuper, [], - source_map_keyword_bare( - source_range_length(node.location.start_char, 5), - source_range_node(node) + smap_keyword_bare( + srange_length(node.start_char, 5), + srange_node(node) ) ) end @@ -2885,7 +2603,7 @@ def block_children(node) if node.block_var visit(node.block_var) else - s(:args, [], source_map_collection(expression: nil)) + s(:args, [], smap_collection_bare(nil)) end type = :block @@ -2923,10 +2641,10 @@ def canonical_unary(node) location: Location.new( start_line: node.location.start_line, - start_char: node.location.start_char, + start_char: node.start_char, start_column: node.location.start_column, end_line: node.location.start_line, - end_char: node.location.start_char + length, + end_char: node.start_char + length, end_column: node.location.start_column + length ) ), @@ -2940,8 +2658,8 @@ def canonical_unary(node) def canonical_binary(node) operator = node.operator.to_s - start_char = node.left.location.end_char - end_char = node.right.location.start_char + start_char = node.left.end_char + end_char = node.right.start_char index = buffer.source[start_char...end_char].index(operator) start_line = @@ -3007,12 +2725,12 @@ def s(type, children, location) end # Constructs a plain source map just for an expression. - def source_map(expression:) + def smap(expression) ::Parser::Source::Map.new(expression) end # Constructs a new source map for a collection. - def source_map_collection(begin_token: nil, end_token: nil, expression:) + def smap_collection(begin_token, end_token, expression) ::Parser::Source::Map::Collection.new( begin_token, end_token, @@ -3020,13 +2738,18 @@ def source_map_collection(begin_token: nil, end_token: nil, expression:) ) end + # Constructs a new source map for a collection without a begin or end. + def smap_collection_bare(expression) + smap_collection(nil, nil, expression) + end + # Constructs a new source map for a conditional expression. - def source_map_condition( - keyword: nil, - begin_token: nil, - else_token: nil, - end_token: nil, - expression: + def smap_condition( + keyword, + begin_token, + else_token, + end_token, + expression ) ::Parser::Source::Map::Condition.new( keyword, @@ -3037,18 +2760,19 @@ def source_map_condition( ) end + # Constructs a new source map for a conditional expression with no begin + # or end. + def smap_condition_bare(expression) + smap_condition(nil, nil, nil, nil, expression) + end + # Constructs a new source map for a constant reference. - def source_map_constant(double_colon, name, expression) + def smap_constant(double_colon, name, expression) ::Parser::Source::Map::Constant.new(double_colon, name, expression) end # Constructs a new source map for a class definition. - def source_map_definition( - keyword: nil, - operator: nil, - name: nil, - end_token: nil - ) + def smap_definition(keyword, operator, name, end_token) ::Parser::Source::Map::Definition.new( keyword, operator, @@ -3058,7 +2782,7 @@ def source_map_definition( end # Constructs a new source map for a for loop. - def source_map_for(keyword, in_token, begin_token, end_token, expression) + def smap_for(keyword, in_token, begin_token, end_token, expression) ::Parser::Source::Map::For.new( keyword, in_token, @@ -3069,7 +2793,7 @@ def source_map_for(keyword, in_token, begin_token, end_token, expression) end # Constructs a new source map for a heredoc. - def source_map_heredoc(expression, heredoc_body, heredoc_end) + def smap_heredoc(expression, heredoc_body, heredoc_end) ::Parser::Source::Map::Heredoc.new( expression, heredoc_body, @@ -3078,12 +2802,12 @@ def source_map_heredoc(expression, heredoc_body, heredoc_end) end # Construct a source map for an index operation. - def source_map_index(begin_token: nil, end_token: nil, expression:) + def smap_index(begin_token, end_token, expression) ::Parser::Source::Map::Index.new(begin_token, end_token, expression) end # Constructs a new source map for the use of a keyword. - def source_map_keyword(keyword, begin_token, end_token, expression) + def smap_keyword(keyword, begin_token, end_token, expression) ::Parser::Source::Map::Keyword.new( keyword, begin_token, @@ -3094,18 +2818,18 @@ def source_map_keyword(keyword, begin_token, end_token, expression) # Constructs a new source map for the use of a keyword without a begin or # end token. - def source_map_keyword_bare(keyword, expression) - source_map_keyword(keyword, nil, nil, expression) + def smap_keyword_bare(keyword, expression) + smap_keyword(keyword, nil, nil, expression) end # Constructs a new source map for a method definition. - def source_map_method_definition( - keyword: nil, - operator: nil, - name: nil, - end_token: nil, - assignment: nil, - expression: + def smap_method_definition( + keyword, + operator, + name, + end_token, + assignment, + expression ) ::Parser::Source::Map::MethodDefinition.new( keyword, @@ -3118,17 +2842,12 @@ def source_map_method_definition( end # Constructs a new source map for an operator. - def source_map_operator(operator, expression) + def smap_operator(operator, expression) ::Parser::Source::Map::Operator.new(operator, expression) end # Constructs a source map for the body of a rescue clause. - def source_map_rescue_body( - keyword: nil, - assoc: nil, - begin_token: nil, - expression: - ) + def smap_rescue_body(keyword, assoc, begin_token, expression) ::Parser::Source::Map::RescueBody.new( keyword, assoc, @@ -3138,13 +2857,7 @@ def source_map_rescue_body( end # Constructs a new source map for a method call. - def source_map_send( - dot: nil, - selector: nil, - begin_token: nil, - end_token: nil, - expression: - ) + def smap_send(dot, selector, begin_token, end_token, expression) ::Parser::Source::Map::Send.new( dot, selector, @@ -3154,74 +2867,76 @@ def source_map_send( ) end + # Constructs a new source map for a method call without a begin or end. + def smap_send_bare(selector, expression) + smap_send(nil, selector, nil, nil, expression) + end + + # Constructs a new source map for a ternary expression. + def smap_ternary(question, colon, expression) + ::Parser::Source::Map::Ternary.new(question, colon, expression) + end + # Constructs a new source map for a variable. - def source_map_variable(name, expression) + def smap_variable(name, expression) ::Parser::Source::Map::Variable.new(name, expression) end # Constructs a new source range from the given start and end offsets. - def source_range(start_char, end_char) + def srange(start_char, end_char) ::Parser::Source::Range.new(buffer, start_char, end_char) end # Constructs a new source range by finding the given needle in the given # range of the source. If the needle is not found, returns nil. - def source_range_search(start_char, end_char, needle) + def srange_search(start_char, end_char, needle) index = buffer.source[start_char...end_char].index(needle) return unless index offset = start_char + index - source_range(offset, offset + needle.length) + srange(offset, offset + needle.length) end # Constructs a new source range by searching for the given needle between # the end location of the start node and the start location of the end # node. If the needle is not found, returns nil. - def source_range_search_between(start_node, end_node, needle) - source_range_search( - start_node.location.end_char, - end_node.location.start_char, - needle - ) + def srange_search_between(start_node, end_node, needle) + srange_search(start_node.end_char, end_node.start_char, needle) end # Constructs a new source range by finding the given needle in the given # range of the source. If it needle is not found, raises an error. - def source_range_find(start_char, end_char, needle) - source_range = source_range_search(start_char, end_char, needle) + def srange_find(start_char, end_char, needle) + srange = srange_search(start_char, end_char, needle) - unless source_range + unless srange slice = buffer.source[start_char...end_char].inspect raise "Could not find #{needle.inspect} in #{slice}" end - source_range + srange end # Constructs a new source range by finding the given needle between the # end location of the start node and the start location of the end node. # If the needle is not found, returns raises an error. - def source_range_find_between(start_node, end_node, needle) - source_range_find( - start_node.location.end_char, - end_node.location.start_char, - needle - ) + def srange_find_between(start_node, end_node, needle) + srange_find(start_node.end_char, end_node.start_char, needle) end # Constructs a new source range from the given start offset and length. - def source_range_length(start_char, length) + def srange_length(start_char, length) if length > 0 - source_range(start_char, start_char + length) + srange(start_char, start_char + length) else - source_range(start_char + length, start_char) + srange(start_char + length, start_char) end end # Constructs a new source range using the given node's location. - def source_range_node(node) + def srange_node(node) location = node.location - source_range(location.start_char, location.end_char) + srange(location.start_char, location.end_char) end end end diff --git a/test/syntax_tree_test.rb b/test/syntax_tree_test.rb index 05242d94..f12065b8 100644 --- a/test/syntax_tree_test.rb +++ b/test/syntax_tree_test.rb @@ -22,7 +22,7 @@ def method # comment SOURCE bodystmt = SyntaxTree.parse(source).statements.body.first.bodystmt - assert_equal(20, bodystmt.location.start_char) + assert_equal(20, bodystmt.start_char) end def test_parse_error diff --git a/test/translation/parser_test.rb b/test/translation/parser_test.rb index 576d4ac1..ad87d8c6 100644 --- a/test/translation/parser_test.rb +++ b/test/translation/parser_test.rb @@ -113,7 +113,7 @@ class ParserTest < Minitest::Test name = prefix[4..] next if all_failures.any? { |pattern| File.fnmatch?(pattern, name) } - define_method(name) { assert_parses(lines.join("\n")) } + define_method(name) { assert_parses("#{lines.join("\n")}\n") } end private From 52f44038ca66a4542d97aff05b85e1e6e84b002a Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Thu, 9 Feb 2023 16:25:29 -0500 Subject: [PATCH 2/2] Add a rubocop ast translator --- lib/syntax_tree/parser.rb | 34 ++-- lib/syntax_tree/translation.rb | 11 ++ lib/syntax_tree/translation/parser.rb | 213 ++++++++++++--------- lib/syntax_tree/translation/rubocop_ast.rb | 21 ++ 4 files changed, 169 insertions(+), 110 deletions(-) create mode 100644 lib/syntax_tree/translation/rubocop_ast.rb diff --git a/lib/syntax_tree/parser.rb b/lib/syntax_tree/parser.rb index cf3982f9..be6265d1 100644 --- a/lib/syntax_tree/parser.rb +++ b/lib/syntax_tree/parser.rb @@ -275,7 +275,7 @@ def find_keyword(name) end def find_keyword_between(name, left, right) - bounds = left.location.end_char...right.location.start_char + bounds = left.end_char...right.start_char index = tokens.rindex do |token| char = token.location.start_char @@ -1807,19 +1807,19 @@ def on_for(index, collection, statements) in_keyword = consume_keyword(:in) ending = consume_keyword(:end) - # Consume the do keyword if it exists so that it doesn't get confused for - # some other block - if (keyword = find_keyword_between(:do, collection, ending)) - tokens.delete(keyword) - end + delimiter = + find_keyword_between(:do, collection, ending) || + find_token_between(Semicolon, collection, ending) + + tokens.delete(delimiter) if delimiter start_char = - find_next_statement_start((keyword || collection).location.end_char) + find_next_statement_start((delimiter || collection).location.end_char) statements.bind( start_char, start_char - - line_counts[(keyword || collection).location.end_line - 1].start, + line_counts[(delimiter || collection).location.end_line - 1].start, ending.location.start_char, ending.location.start_column ) @@ -3328,10 +3328,13 @@ def on_sclass(target, bodystmt) ) end + # Semicolons are tokens that get added to the token list but never get + # attached to the AST. Because of this they only need to track their + # associated location so they can be used for computing bounds. class Semicolon attr_reader :location - def initialize(location:) + def initialize(location) @location = location end end @@ -3340,13 +3343,12 @@ def initialize(location:) # on_semicolon: (String value) -> Semicolon def on_semicolon(value) tokens << Semicolon.new( - location: - Location.token( - line: lineno, - char: char_pos, - column: current_column, - size: value.size - ) + Location.token( + line: lineno, + char: char_pos, + column: current_column, + size: value.size + ) ) end diff --git a/lib/syntax_tree/translation.rb b/lib/syntax_tree/translation.rb index d3f2e56f..6fc96f00 100644 --- a/lib/syntax_tree/translation.rb +++ b/lib/syntax_tree/translation.rb @@ -13,5 +13,16 @@ def self.to_parser(node, buffer) node.accept(Parser.new(buffer)) end + + # This method translates the given node into the representation defined by + # the rubocop/rubocop-ast gem. We don't explicitly list it as a dependency + # because it's not required for the core functionality of Syntax Tree. + def self.to_rubocop_ast(node, buffer) + require "rubocop/ast" + require_relative "translation/parser" + require_relative "translation/rubocop_ast" + + node.accept(RuboCopAST.new(buffer)) + end end end diff --git a/lib/syntax_tree/translation/parser.rb b/lib/syntax_tree/translation/parser.rb index b9e91e5f..70c98336 100644 --- a/lib/syntax_tree/translation/parser.rb +++ b/lib/syntax_tree/translation/parser.rb @@ -5,6 +5,73 @@ module Translation # This visitor is responsible for converting the syntax tree produced by # Syntax Tree into the syntax tree produced by the whitequark/parser gem. class Parser < BasicVisitor + # Heredocs are represented _very_ differently in the parser gem from how + # they are represented in the Syntax Tree AST. This class is responsible + # for handling the translation. + class HeredocBuilder + Line = Struct.new(:value, :segments) + + attr_reader :node, :segments + + def initialize(node) + @node = node + @segments = [] + end + + def <<(segment) + if segment.type == :str && segments.last && + segments.last.type == :str && + !segments.last.children.first.end_with?("\n") + segments.last.children.first << segment.children.first + else + segments << segment + end + end + + def trim! + return unless node.beginning.value[2] == "~" + lines = [Line.new(+"", [])] + + segments.each do |segment| + lines.last.segments << segment + + if segment.type == :str + lines.last.value << segment.children.first + lines << Line.new(+"", []) if lines.last.value.end_with?("\n") + end + end + + lines.pop if lines.last.value.empty? + return if lines.empty? + + segments.clear + lines.each do |line| + remaining = node.dedent + + line.segments.each do |segment| + if segment.type == :str + if remaining > 0 + whitespace = segment.children.first[/^\s{0,#{remaining}}/] + segment.children.first.sub!(/^#{whitespace}/, "") + remaining -= whitespace.length + end + + if node.beginning.value[3] != "'" && segments.any? && + segments.last.type == :str && + segments.last.children.first.end_with?("\\\n") + segments.last.children.first.gsub!(/\\\n\z/, "") + segments.last.children.first.concat(segment.children.first) + elsif !segment.children.first.empty? + segments << segment + end + else + segments << segment + end + end + end + end + end + attr_reader :buffer, :stack def initialize(buffer) @@ -665,6 +732,25 @@ def visit_command_call(node) node.end_char end + expression = + if node.arguments.is_a?(ArgParen) + srange(node.start_char, node.arguments.end_char) + elsif node.arguments.is_a?(Args) && node.arguments.parts.any? + last_part = node.arguments.parts.last + end_char = + if last_part.is_a?(Heredoc) + last_part.beginning.end_char + else + last_part.end_char + end + + srange(node.start_char, end_char) + elsif node.block + srange_node(node.message) + else + srange_node(node) + end + call = s( if node.operator.is_a?(Op) && node.operator.value == "&." @@ -690,14 +776,7 @@ def visit_command_call(node) node.message == :call ? nil : srange_node(node.message), begin_token, end_token, - if node.arguments.is_a?(ArgParen) || - (node.arguments.is_a?(Args) && node.arguments.parts.any?) - srange(node.start_char, node.arguments.end_char) - elsif node.block - srange_node(node.message) - else - srange_node(node) - end + expression ) ) @@ -1049,7 +1128,8 @@ def visit_for(node) smap_for( srange_length(node.start_char, 3), srange_find_between(node.index, node.collection, "in"), - srange_search_between(node.collection, node.statements, "do"), + srange_search_between(node.collection, node.statements, "do") || + srange_search_between(node.collection, node.statements, ";"), srange_length(node.end_char, -3), srange_node(node) ) @@ -1078,98 +1158,43 @@ def visit_hash(node) ) end - # Heredocs are represented _very_ differently in the parser gem from how - # they are represented in the Syntax Tree AST. This class is responsible - # for handling the translation. - class HeredocSegments - HeredocLine = Struct.new(:value, :segments) - - attr_reader :node, :segments - - def initialize(node) - @node = node - @segments = [] - end - - def <<(segment) - if segment.type == :str && segments.last && - segments.last.type == :str && - !segments.last.children.first.end_with?("\n") - segments.last.children.first << segment.children.first - else - segments << segment - end - end - - def trim! - return unless node.beginning.value[2] == "~" - lines = [HeredocLine.new(+"", [])] - - segments.each do |segment| - lines.last.segments << segment - - if segment.type == :str - lines.last.value << segment.children.first - - if lines.last.value.end_with?("\n") - lines << HeredocLine.new(+"", []) - end - end - end - - lines.pop if lines.last.value.empty? - return if lines.empty? - - segments.clear - lines.each do |line| - remaining = node.dedent - - line.segments.each do |segment| - if segment.type == :str - if remaining > 0 - whitespace = segment.children.first[/^\s{0,#{remaining}}/] - segment.children.first.sub!(/^#{whitespace}/, "") - remaining -= whitespace.length - end - - if node.beginning.value[3] != "'" && segments.any? && - segments.last.type == :str && - segments.last.children.first.end_with?("\\\n") - segments.last.children.first.gsub!(/\\\n\z/, "") - segments.last.children.first.concat(segment.children.first) - elsif !segment.children.first.empty? - segments << segment - end - else - segments << segment - end - end - end - end - end - # Visit a Heredoc node. def visit_heredoc(node) - heredoc_segments = HeredocSegments.new(node) + heredoc = HeredocBuilder.new(node) + # For each part of the heredoc, if it's a string content node, split it + # into multiple string content nodes, one for each line. Otherwise, + # visit the node as normal. node.parts.each do |part| if part.is_a?(TStringContent) && part.value.count("\n") > 1 - part - .value - .split("\n") - .each { |line| heredoc_segments << s(:str, ["#{line}\n"], nil) } + index = part.start_char + lines = part.value.split("\n") + + lines.each do |line| + length = line.length + 1 + location = smap_collection_bare(srange_length(index, length)) + + heredoc << s(:str, ["#{line}\n"], location) + index += length + end else - heredoc_segments << visit(part) + heredoc << visit(part) end end - heredoc_segments.trim! + # Now that we have all of the pieces on the heredoc, we can trim it if + # it is a heredoc that supports trimming (i.e., it has a ~ on the + # declaration). + heredoc.trim! + + # Generate the location for the heredoc, which goes from the declaration + # to the ending delimiter. location = smap_heredoc( srange_node(node.beginning), srange( if node.parts.empty? - node.beginning.end_char + node.beginning.end_char + 1 else node.parts.first.start_char end, @@ -1178,15 +1203,15 @@ def visit_heredoc(node) srange(node.ending.start_char, node.ending.end_char - 1) ) + # Finally, decide which kind of heredoc node to generate based on its + # declaration and contents. if node.beginning.value.match?(/`\w+`\z/) - s(:xstr, heredoc_segments.segments, location) - elsif heredoc_segments.segments.length > 1 - s(:dstr, heredoc_segments.segments, location) - elsif heredoc_segments.segments.empty? - s(:dstr, [], location) - else - segment = heredoc_segments.segments.first + s(:xstr, heredoc.segments, location) + elsif heredoc.segments.length == 1 + segment = heredoc.segments.first s(segment.type, segment.children, location) + else + s(:dstr, heredoc.segments, location) end end diff --git a/lib/syntax_tree/translation/rubocop_ast.rb b/lib/syntax_tree/translation/rubocop_ast.rb new file mode 100644 index 00000000..53c6737b --- /dev/null +++ b/lib/syntax_tree/translation/rubocop_ast.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SyntaxTree + module Translation + # This visitor is responsible for converting the syntax tree produced by + # Syntax Tree into the syntax tree produced by the rubocop/rubocop-ast gem. + class RuboCopAST < Parser + private + + # This method is effectively the same thing as the parser gem except that + # it uses the rubocop-ast specializations of the nodes. + def s(type, children, location) + ::RuboCop::AST::Builder::NODE_MAP.fetch(type, ::RuboCop::AST::Node).new( + type, + children, + location: location + ) + end + end + end +end