prettier 0.12.2 → 0.12.3
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 +49 -1
- data/CONTRIBUTING.md +1 -1
- data/README.md +34 -18
- data/node_modules/prettier/bin-prettier.js +22 -13
- data/node_modules/prettier/index.js +22 -13
- data/package.json +2 -2
- data/src/nodes.js +10 -576
- data/src/nodes/alias.js +1 -1
- data/src/nodes/args.js +88 -0
- data/src/nodes/arrays.js +1 -1
- data/src/nodes/assign.js +39 -0
- data/src/nodes/blocks.js +18 -3
- data/src/nodes/calls.js +37 -3
- data/src/nodes/case.js +1 -1
- data/src/nodes/commands.js +2 -1
- data/src/nodes/conditionals.js +118 -57
- data/src/nodes/constants.js +25 -0
- data/src/nodes/flow.js +64 -0
- data/src/nodes/hashes.js +3 -2
- data/src/nodes/hooks.js +1 -1
- data/src/nodes/ints.js +24 -0
- data/src/nodes/lambdas.js +1 -1
- data/src/nodes/loops.js +1 -1
- data/src/nodes/massign.js +70 -0
- data/src/nodes/methods.js +40 -2
- data/src/nodes/operators.js +44 -0
- data/src/nodes/params.js +14 -3
- data/src/nodes/regexp.js +1 -1
- data/src/nodes/rescue.js +1 -1
- data/src/nodes/scopes.js +61 -0
- data/src/nodes/statements.js +105 -0
- data/src/nodes/strings.js +9 -2
- data/src/{builders.js → prettier.js} +9 -2
- data/src/ripper.rb +412 -353
- data/src/utils.js +1 -1
- metadata +40 -3
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            const {
         | 
| 2 | 
            +
              concat,
         | 
| 3 | 
            +
              dedent,
         | 
| 4 | 
            +
              group,
         | 
| 5 | 
            +
              hardline,
         | 
| 6 | 
            +
              indent,
         | 
| 7 | 
            +
              join,
         | 
| 8 | 
            +
              line,
         | 
| 9 | 
            +
              literalline,
         | 
| 10 | 
            +
              markAsRoot,
         | 
| 11 | 
            +
              softline,
         | 
| 12 | 
            +
              trim
         | 
| 13 | 
            +
            } = require("../prettier");
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            module.exports = {
         | 
| 16 | 
            +
              "@__end__": (path, _opts, _print) => {
         | 
| 17 | 
            +
                const { body } = path.getValue();
         | 
| 18 | 
            +
                return concat([trim, "__END__", literalline, body]);
         | 
| 19 | 
            +
              },
         | 
| 20 | 
            +
              bodystmt: (path, opts, print) => {
         | 
| 21 | 
            +
                const [_statements, rescue, elseClause, ensure] = path.getValue().body;
         | 
| 22 | 
            +
                const parts = [path.call(print, "body", 0)];
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                if (rescue) {
         | 
| 25 | 
            +
                  parts.push(dedent(concat([hardline, path.call(print, "body", 1)])));
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                if (elseClause) {
         | 
| 29 | 
            +
                  // Before Ruby 2.6, this piece of bodystmt was an explicit "else" node
         | 
| 30 | 
            +
                  const stmts =
         | 
| 31 | 
            +
                    elseClause.type === "else"
         | 
| 32 | 
            +
                      ? path.call(print, "body", 2, "body", 0)
         | 
| 33 | 
            +
                      : path.call(print, "body", 2);
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  parts.push(concat([dedent(concat([hardline, "else"])), hardline, stmts]));
         | 
| 36 | 
            +
                }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                if (ensure) {
         | 
| 39 | 
            +
                  parts.push(dedent(concat([hardline, path.call(print, "body", 3)])));
         | 
| 40 | 
            +
                }
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                return group(concat(parts));
         | 
| 43 | 
            +
              },
         | 
| 44 | 
            +
              embdoc: (path, _opts, _print) => concat([trim, path.getValue().body]),
         | 
| 45 | 
            +
              paren: (path, opts, print) => {
         | 
| 46 | 
            +
                if (!path.getValue().body[0]) {
         | 
| 47 | 
            +
                  return "()";
         | 
| 48 | 
            +
                }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                let content = path.call(print, "body", 0);
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                if (
         | 
| 53 | 
            +
                  ["args", "args_add_star", "args_add_block"].includes(
         | 
| 54 | 
            +
                    path.getValue().body[0].type
         | 
| 55 | 
            +
                  )
         | 
| 56 | 
            +
                ) {
         | 
| 57 | 
            +
                  content = join(concat([",", line]), content);
         | 
| 58 | 
            +
                }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                return group(
         | 
| 61 | 
            +
                  concat([
         | 
| 62 | 
            +
                    "(",
         | 
| 63 | 
            +
                    indent(concat([softline, content])),
         | 
| 64 | 
            +
                    concat([softline, ")"])
         | 
| 65 | 
            +
                  ])
         | 
| 66 | 
            +
                );
         | 
| 67 | 
            +
              },
         | 
| 68 | 
            +
              program: (path, opts, print) =>
         | 
| 69 | 
            +
                markAsRoot(
         | 
| 70 | 
            +
                  concat([join(literalline, path.map(print, "body")), literalline])
         | 
| 71 | 
            +
                ),
         | 
| 72 | 
            +
              stmts: (path, opts, print) => {
         | 
| 73 | 
            +
                const stmts = path.getValue().body;
         | 
| 74 | 
            +
                const parts = [];
         | 
| 75 | 
            +
                let lineNo = null;
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                stmts.forEach((stmt, index) => {
         | 
| 78 | 
            +
                  if (stmt.type === "void_stmt") {
         | 
| 79 | 
            +
                    return;
         | 
| 80 | 
            +
                  }
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  const printed = path.call(print, "body", index);
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  if (lineNo === null) {
         | 
| 85 | 
            +
                    parts.push(printed);
         | 
| 86 | 
            +
                  } else if (
         | 
| 87 | 
            +
                    stmt.start - lineNo > 1 ||
         | 
| 88 | 
            +
                    [stmt.type, stmts[index - 1].type].includes("access_ctrl")
         | 
| 89 | 
            +
                  ) {
         | 
| 90 | 
            +
                    parts.push(hardline, hardline, printed);
         | 
| 91 | 
            +
                  } else if (
         | 
| 92 | 
            +
                    stmt.start !== lineNo ||
         | 
| 93 | 
            +
                    path.getParentNode().type !== "string_embexpr"
         | 
| 94 | 
            +
                  ) {
         | 
| 95 | 
            +
                    parts.push(hardline, printed);
         | 
| 96 | 
            +
                  } else {
         | 
| 97 | 
            +
                    parts.push("; ", printed);
         | 
| 98 | 
            +
                  }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  lineNo = stmt.end;
         | 
| 101 | 
            +
                });
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                return concat(parts);
         | 
| 104 | 
            +
              }
         | 
| 105 | 
            +
            };
         | 
    
        data/src/nodes/strings.js
    CHANGED
    
    | @@ -6,8 +6,8 @@ const { | |
| 6 6 | 
             
              join,
         | 
| 7 7 | 
             
              literalline,
         | 
| 8 8 | 
             
              softline
         | 
| 9 | 
            -
            } = require("../ | 
| 10 | 
            -
            const { concatBody, empty, makeList, surround } = require("../utils");
         | 
| 9 | 
            +
            } = require("../prettier");
         | 
| 10 | 
            +
            const { concatBody, empty, makeList, prefix, surround } = require("../utils");
         | 
| 11 11 | 
             
            const escapePattern = require("../escapePattern");
         | 
| 12 12 |  | 
| 13 13 | 
             
            // If there is some part of this string that matches an escape sequence or that
         | 
| @@ -72,6 +72,11 @@ module.exports = { | |
| 72 72 | 
             
                const quote = preferSingleQuotes ? "'" : '"';
         | 
| 73 73 | 
             
                return body.length === 2 ? concat([quote, body.slice(1), quote]) : body;
         | 
| 74 74 | 
             
              },
         | 
| 75 | 
            +
              dyna_symbol: (path, opts, print) => {
         | 
| 76 | 
            +
                const { quote } = path.getValue().body[0];
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                return concat([":", quote, concat(path.call(print, "body", 0)), quote]);
         | 
| 79 | 
            +
              },
         | 
| 75 80 | 
             
              heredoc: (path, opts, print) => {
         | 
| 76 81 | 
             
                const { beging, ending } = path.getValue();
         | 
| 77 82 |  | 
| @@ -129,6 +134,8 @@ module.exports = { | |
| 129 134 |  | 
| 130 135 | 
             
                return concat([quote].concat(parts).concat([quote]));
         | 
| 131 136 | 
             
              },
         | 
| 137 | 
            +
              symbol: prefix(":"),
         | 
| 138 | 
            +
              symbol_literal: concatBody,
         | 
| 132 139 | 
             
              word_add: concatBody,
         | 
| 133 140 | 
             
              word_new: empty,
         | 
| 134 141 | 
             
              xstring: makeList,
         | 
| @@ -4,6 +4,13 @@ | |
| 4 4 | 
             
            const source = process.env.RBPRETTIER ? "../node_modules/prettier" : "prettier";
         | 
| 5 5 |  | 
| 6 6 | 
             
            // eslint-disable-next-line import/no-dynamic-require
         | 
| 7 | 
            -
            const  | 
| 7 | 
            +
            const prettier = require(source);
         | 
| 8 8 |  | 
| 9 | 
            -
             | 
| 9 | 
            +
            // Just combine all the things into one big object so that we can import
         | 
| 10 | 
            +
            // whatever we need from prettier without having to dive too deeply.
         | 
| 11 | 
            +
            module.exports = Object.assign(
         | 
| 12 | 
            +
              {},
         | 
| 13 | 
            +
              prettier.doc.builders,
         | 
| 14 | 
            +
              prettier.doc.utils,
         | 
| 15 | 
            +
              prettier.util
         | 
| 16 | 
            +
            );
         | 
    
        data/src/ripper.rb
    CHANGED
    
    | @@ -9,448 +9,503 @@ end | |
| 9 9 | 
             
            require 'json' unless defined?(JSON)
         | 
| 10 10 | 
             
            require 'ripper'
         | 
| 11 11 |  | 
| 12 | 
            -
             | 
| 12 | 
            +
            class RipperJS < Ripper
         | 
| 13 | 
            +
              private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              # Scanner events occur when the lexer hits a new token, like a keyword or an
         | 
| 16 | 
            +
              # end. These nodes always contain just one argument which is a string
         | 
| 17 | 
            +
              # representing the content. For the most part these can just be printed
         | 
| 18 | 
            +
              # directly, which very few exceptions.
         | 
| 19 | 
            +
              SCANNER_EVENTS.each do |event|
         | 
| 20 | 
            +
                define_method(:"on_#{event}") do |body|
         | 
| 21 | 
            +
                  { type: :"@#{event}", body: body, start: lineno, end: lineno }
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              # Parser events represent nodes in the ripper abstract syntax tree. The event
         | 
| 26 | 
            +
              # is reported after the children of the node have already been built.
         | 
| 27 | 
            +
              PARSER_EVENTS.each do |event|
         | 
| 28 | 
            +
                define_method(:"on_#{event}") do |*body|
         | 
| 29 | 
            +
                  min = body.map { |part| part.is_a?(Hash) ? part[:start] : lineno }.min
         | 
| 30 | 
            +
                  { type: event, body: body, start: min || lineno, end: lineno }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 13 34 | 
             
              # Some nodes are lists that come back from the parser. They always start with
         | 
| 14 | 
            -
              # a  | 
| 15 | 
            -
              # in the list is a  | 
| 16 | 
            -
              # into one node with an array body.
         | 
| 17 | 
            -
               | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 35 | 
            +
              # a `*_new` node (or in the case of string, `*_content`) and each additional
         | 
| 36 | 
            +
              # node in the list is a `*_add` node. This module takes those nodes and turns
         | 
| 37 | 
            +
              # them into one node with an array body.
         | 
| 38 | 
            +
              #
         | 
| 39 | 
            +
              # For example, the statement `[a, b, c]` would be parsed as:
         | 
| 40 | 
            +
              #
         | 
| 41 | 
            +
              # [:args_add,
         | 
| 42 | 
            +
              #   [:args_add,
         | 
| 43 | 
            +
              #     [:args_add,
         | 
| 44 | 
            +
              #       [:args_new],
         | 
| 45 | 
            +
              #       [:vcall, [:@ident, "a", [1, 1]]]
         | 
| 46 | 
            +
              #     ],
         | 
| 47 | 
            +
              #     [:vcall, [:@ident, "b", [1, 4]]]
         | 
| 48 | 
            +
              #   ],
         | 
| 49 | 
            +
              #   [:vcall, [:@ident, "c", [1, 7]]]
         | 
| 50 | 
            +
              # ]
         | 
| 51 | 
            +
              #
         | 
| 52 | 
            +
              # But after this module is applied that is instead parsed as:
         | 
| 53 | 
            +
              #
         | 
| 54 | 
            +
              # [:args,
         | 
| 55 | 
            +
              #   [
         | 
| 56 | 
            +
              #     [:vcall, [:@ident, "a", [1, 1]]],
         | 
| 57 | 
            +
              #     [:vcall, [:@ident, "b", [1, 4]]],
         | 
| 58 | 
            +
              #     [:vcall, [:@ident, "c", [1, 7]]]
         | 
| 59 | 
            +
              #   ]
         | 
| 60 | 
            +
              # ]
         | 
| 61 | 
            +
              #
         | 
| 62 | 
            +
              # This makes it a lot easier to join things with commas, and ends up resulting
         | 
| 63 | 
            +
              # in a much flatter `prettier` tree once it has been converted. Note that
         | 
| 64 | 
            +
              # because of this module some extra node types are added (the aggregate of
         | 
| 65 | 
            +
              # the previous `*_add` nodes) and some nodes now have arrays in places where
         | 
| 66 | 
            +
              # they previously had single nodes.
         | 
| 67 | 
            +
              prepend(
         | 
| 68 | 
            +
                Module.new do
         | 
| 69 | 
            +
                  events = %i[
         | 
| 70 | 
            +
                    args
         | 
| 71 | 
            +
                    mlhs
         | 
| 72 | 
            +
                    mrhs
         | 
| 73 | 
            +
                    qsymbols
         | 
| 74 | 
            +
                    qwords
         | 
| 75 | 
            +
                    regexp
         | 
| 76 | 
            +
                    stmts
         | 
| 77 | 
            +
                    string
         | 
| 78 | 
            +
                    symbols
         | 
| 79 | 
            +
                    words
         | 
| 80 | 
            +
                    xstring
         | 
| 81 | 
            +
                  ]
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  private
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  events.each do |event|
         | 
| 86 | 
            +
                    suffix = event == :string ? 'content' : 'new'
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                    define_method(:"on_#{event}_#{suffix}") do
         | 
| 89 | 
            +
                      { type: event, body: [], start: lineno, end: lineno }
         | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    define_method(:"on_#{event}_add") do |parts, part|
         | 
| 93 | 
            +
                      parts.tap do |node|
         | 
| 94 | 
            +
                        node[:body] << part
         | 
| 95 | 
            +
                        node[:end] = lineno
         | 
| 96 | 
            +
                      end
         | 
| 45 97 | 
             
                    end
         | 
| 46 98 | 
             
                  end
         | 
| 47 99 | 
             
                end
         | 
| 48 | 
            -
               | 
| 100 | 
            +
              )
         | 
| 49 101 |  | 
| 50 102 | 
             
              # For most nodes, it's enough to look at the child nodes to determine the
         | 
| 51 103 | 
             
              # start of the parent node. However, for some nodes it's necessary to keep
         | 
| 52 104 | 
             
              # track of the keywords as they come in from the lexer and to modify the start
         | 
| 53 | 
            -
              # node once we have it.
         | 
| 54 | 
            -
               | 
| 55 | 
            -
             | 
| 105 | 
            +
              # node once we have it. We need accurate start and end lines so that we can
         | 
| 106 | 
            +
              # embed block comments into the right kind of node.
         | 
| 107 | 
            +
              prepend(
         | 
| 108 | 
            +
                Module.new do
         | 
| 109 | 
            +
                  events = %i[begin else elsif ensure if rescue until while]
         | 
| 56 110 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 111 | 
            +
                  def initialize(*args)
         | 
| 112 | 
            +
                    super(*args)
         | 
| 113 | 
            +
                    @keywords = []
         | 
| 114 | 
            +
                  end
         | 
| 61 115 |  | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 116 | 
            +
                  def self.prepended(base)
         | 
| 117 | 
            +
                    base.attr_reader :keywords
         | 
| 118 | 
            +
                  end
         | 
| 65 119 |  | 
| 66 | 
            -
             | 
| 120 | 
            +
                  private
         | 
| 67 121 |  | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 122 | 
            +
                  def find_start(body)
         | 
| 123 | 
            +
                    keywords[keywords.rindex { |keyword| keyword[:body] == body }][:start]
         | 
| 124 | 
            +
                  end
         | 
| 71 125 |  | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 126 | 
            +
                  events.each do |event|
         | 
| 127 | 
            +
                    keyword = event.to_s
         | 
| 74 128 |  | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 129 | 
            +
                    define_method(:"on_#{event}") do |*body|
         | 
| 130 | 
            +
                      super(*body).tap { |sexp| sexp.merge!(start: find_start(keyword)) }
         | 
| 131 | 
            +
                    end
         | 
| 77 132 | 
             
                  end
         | 
| 78 | 
            -
                end
         | 
| 79 133 |  | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 134 | 
            +
                  def on_kw(body)
         | 
| 135 | 
            +
                    super(body).tap { |sexp| keywords << sexp }
         | 
| 136 | 
            +
                  end
         | 
| 83 137 |  | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 138 | 
            +
                  def on_program(*body)
         | 
| 139 | 
            +
                    super(*body).tap { |sexp| sexp.merge!(start: 1) }
         | 
| 140 | 
            +
                  end
         | 
| 86 141 | 
             
                end
         | 
| 87 | 
            -
               | 
| 142 | 
            +
              )
         | 
| 88 143 |  | 
| 89 | 
            -
              #  | 
| 90 | 
            -
              #  | 
| 91 | 
            -
              #  | 
| 92 | 
            -
              #  | 
| 93 | 
            -
               | 
| 94 | 
            -
                 | 
| 95 | 
            -
                   | 
| 96 | 
            -
                   | 
| 97 | 
            -
                   | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                   | 
| 107 | 
            -
                  unless: [1],
         | 
| 108 | 
            -
                  until: [1],
         | 
| 109 | 
            -
                  when: [1],
         | 
| 110 | 
            -
                  while: [1]
         | 
| 111 | 
            -
                }
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                def initialize(*args)
         | 
| 114 | 
            -
                  super(*args)
         | 
| 115 | 
            -
                  @block_comments = []
         | 
| 116 | 
            -
                  @current_embdoc = nil
         | 
| 117 | 
            -
                end
         | 
| 144 | 
            +
              # This layer keeps track of inline comments as they come in. Ripper itself
         | 
| 145 | 
            +
              # doesn't attach comments to the AST, so we need to do it manually. In this
         | 
| 146 | 
            +
              # case, inline comments are defined as any comments wherein the lexer state is
         | 
| 147 | 
            +
              # not equal to EXPR_BEG (tracked in the BlockComments layer).
         | 
| 148 | 
            +
              prepend(
         | 
| 149 | 
            +
                Module.new do
         | 
| 150 | 
            +
                  # Certain events needs to steal the comments from their children in order
         | 
| 151 | 
            +
                  # for them to display properly.
         | 
| 152 | 
            +
                  events = {
         | 
| 153 | 
            +
                    aref: [:body, 1],
         | 
| 154 | 
            +
                    args_add_block: [:body, 0],
         | 
| 155 | 
            +
                    break: [:body, 0],
         | 
| 156 | 
            +
                    command: [:body, 1],
         | 
| 157 | 
            +
                    command_call: [:body, 3],
         | 
| 158 | 
            +
                    regexp_literal: [:body, 0],
         | 
| 159 | 
            +
                    string_literal: [:body, 0],
         | 
| 160 | 
            +
                    symbol_literal: [:body, 0]
         | 
| 161 | 
            +
                  }
         | 
| 118 162 |  | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 163 | 
            +
                  def initialize(*args)
         | 
| 164 | 
            +
                    super(*args)
         | 
| 165 | 
            +
                    @inline_comments = []
         | 
| 166 | 
            +
                    @last_sexp = nil
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  def self.prepended(base)
         | 
| 170 | 
            +
                    base.attr_reader :inline_comments, :last_sexp
         | 
| 171 | 
            +
                  end
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                  private
         | 
| 122 174 |  | 
| 123 | 
            -
             | 
| 175 | 
            +
                  events.each do |event, path|
         | 
| 176 | 
            +
                    define_method(:"on_#{event}") do |*body|
         | 
| 177 | 
            +
                      @last_sexp =
         | 
| 178 | 
            +
                        super(*body).tap do |sexp|
         | 
| 179 | 
            +
                          comments = (sexp.dig(*path) || {}).delete(:comments)
         | 
| 180 | 
            +
                          sexp.merge!(comments: comments) if comments
         | 
| 181 | 
            +
                        end
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
                  end
         | 
| 124 184 |  | 
| 125 | 
            -
             | 
| 126 | 
            -
                  range = sexp[:start]..sexp[:end]
         | 
| 127 | 
            -
                  comments =
         | 
| 128 | 
            -
                    block_comments.group_by { |comment| range.include?(comment[:start]) }
         | 
| 185 | 
            +
                  SPECIAL_LITERALS = %i[qsymbols qwords symbols words].freeze
         | 
| 129 186 |  | 
| 130 | 
            -
                   | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 187 | 
            +
                  # Special array literals are handled in different ways and so their
         | 
| 188 | 
            +
                  # comments need to be passed up to their parent array node.
         | 
| 189 | 
            +
                  def on_array(*body)
         | 
| 190 | 
            +
                    @last_sexp =
         | 
| 191 | 
            +
                      super(*body).tap do |sexp|
         | 
| 192 | 
            +
                        next unless SPECIAL_LITERALS.include?(body.dig(0, :type))
         | 
| 133 193 |  | 
| 134 | 
            -
             | 
| 194 | 
            +
                        comments = sexp.dig(:body, 0).delete(:comments)
         | 
| 195 | 
            +
                        sexp.merge!(comments: comments) if comments
         | 
| 196 | 
            +
                      end
         | 
| 135 197 | 
             
                  end
         | 
| 136 | 
            -
                end
         | 
| 137 198 |  | 
| 138 | 
            -
             | 
| 139 | 
            -
                   | 
| 140 | 
            -
             | 
| 199 | 
            +
                  # Handling this specially because we want to pull the comments out of both
         | 
| 200 | 
            +
                  # child nodes.
         | 
| 201 | 
            +
                  def on_assoc_new(*body)
         | 
| 202 | 
            +
                    @last_sexp =
         | 
| 203 | 
            +
                      super(*body).tap do |sexp|
         | 
| 204 | 
            +
                        comments =
         | 
| 205 | 
            +
                          (sexp.dig(:body, 0).delete(:comments) || []) +
         | 
| 206 | 
            +
                            (sexp.dig(:body, 1).delete(:comments) || [])
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                        sexp.merge!(comments: comments) if comments.any?
         | 
| 209 | 
            +
                      end
         | 
| 141 210 | 
             
                  end
         | 
| 142 | 
            -
                end
         | 
| 143 211 |  | 
| 144 | 
            -
             | 
| 145 | 
            -
                   | 
| 146 | 
            -
             | 
| 212 | 
            +
                  # Most scanner events don't stand on their own as s-expressions, but the
         | 
| 213 | 
            +
                  # CHAR scanner event is effectively just a string, so we need to track it
         | 
| 214 | 
            +
                  # as a s-expression.
         | 
| 215 | 
            +
                  def on_CHAR(body)
         | 
| 216 | 
            +
                    @last_sexp = super(body)
         | 
| 147 217 | 
             
                  end
         | 
| 148 | 
            -
                end
         | 
| 149 218 |  | 
| 150 | 
            -
             | 
| 151 | 
            -
                   | 
| 152 | 
            -
             | 
| 153 | 
            -
                   | 
| 154 | 
            -
             | 
| 219 | 
            +
                  # We need to know exactly where the comment is, switching off the current
         | 
| 220 | 
            +
                  # lexer state. In Ruby 2.7.0-dev, that's defined as:
         | 
| 221 | 
            +
                  #
         | 
| 222 | 
            +
                  # enum lex_state_bits {
         | 
| 223 | 
            +
                  #     EXPR_BEG_bit,     /* ignore newline, +/- is a sign. */
         | 
| 224 | 
            +
                  #     EXPR_END_bit,     /* newline significant, +/- is an operator. */
         | 
| 225 | 
            +
                  #     EXPR_ENDARG_bit,  /* ditto, and unbound braces. */
         | 
| 226 | 
            +
                  #     EXPR_ENDFN_bit,   /* ditto, and unbound braces. */
         | 
| 227 | 
            +
                  #     EXPR_ARG_bit,     /* newline significant, +/- is an operator. */
         | 
| 228 | 
            +
                  #     EXPR_CMDARG_bit,  /* newline significant, +/- is an operator. */
         | 
| 229 | 
            +
                  #     EXPR_MID_bit,     /* newline significant, +/- is an operator. */
         | 
| 230 | 
            +
                  #     EXPR_FNAME_bit,   /* ignore newline, no reserved words. */
         | 
| 231 | 
            +
                  #     EXPR_DOT_bit,     /* right after `.' or `::', no reserved words. */
         | 
| 232 | 
            +
                  #     EXPR_CLASS_bit,   /* immediate after `class', no here document. */
         | 
| 233 | 
            +
                  #     EXPR_LABEL_bit,   /* flag bit, label is allowed. */
         | 
| 234 | 
            +
                  #     EXPR_LABELED_bit, /* flag bit, just after a label. */
         | 
| 235 | 
            +
                  #     EXPR_FITEM_bit,   /* symbol literal as FNAME. */
         | 
| 236 | 
            +
                  #     EXPR_MAX_STATE
         | 
| 237 | 
            +
                  # };
         | 
| 238 | 
            +
                  def on_comment(body)
         | 
| 239 | 
            +
                    sexp = { type: :@comment, body: body.chomp, start: lineno, end: lineno }
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                    case RipperJS.lex_state_name(state)
         | 
| 242 | 
            +
                    when 'EXPR_END', 'EXPR_ARG|EXPR_LABELED', 'EXPR_ENDFN'
         | 
| 243 | 
            +
                      last_sexp.merge!(comments: [sexp])
         | 
| 244 | 
            +
                    when 'EXPR_CMDARG', 'EXPR_END|EXPR_ENDARG', 'EXPR_ENDARG', 'EXPR_ARG',
         | 
| 245 | 
            +
                         'EXPR_FNAME|EXPR_FITEM', 'EXPR_CLASS', 'EXPR_END|EXPR_LABEL'
         | 
| 246 | 
            +
                      inline_comments << sexp
         | 
| 247 | 
            +
                    when 'EXPR_BEG|EXPR_LABEL', 'EXPR_MID'
         | 
| 248 | 
            +
                      inline_comments << sexp.merge!(break: true)
         | 
| 249 | 
            +
                    when 'EXPR_DOT'
         | 
| 250 | 
            +
                      last_sexp.merge!(comments: [sexp.merge!(break: true)])
         | 
| 251 | 
            +
                    end
         | 
| 155 252 |  | 
| 156 | 
            -
             | 
| 157 | 
            -
                   | 
| 158 | 
            -
                end
         | 
| 253 | 
            +
                    sexp
         | 
| 254 | 
            +
                  end
         | 
| 159 255 |  | 
| 160 | 
            -
             | 
| 161 | 
            -
                  @current_embdoc[:body] << comment.chomp
         | 
| 162 | 
            -
                  @block_comments << @current_embdoc
         | 
| 163 | 
            -
                  @current_embdoc = nil
         | 
| 164 | 
            -
                end
         | 
| 256 | 
            +
                  defined = private_instance_methods(false).grep(/\Aon_/) { $'.to_sym }
         | 
| 165 257 |  | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 258 | 
            +
                  (Ripper::PARSER_EVENTS - defined).each do |event|
         | 
| 259 | 
            +
                    define_method(:"on_#{event}") do |*body|
         | 
| 260 | 
            +
                      super(*body).tap do |sexp|
         | 
| 261 | 
            +
                        @last_sexp = sexp
         | 
| 262 | 
            +
                        next if inline_comments.empty?
         | 
| 170 263 |  | 
| 171 | 
            -
             | 
| 264 | 
            +
                        sexp[:comments] = inline_comments.reverse
         | 
| 265 | 
            +
                        @inline_comments = []
         | 
| 266 | 
            +
                      end
         | 
| 267 | 
            +
                    end
         | 
| 172 268 | 
             
                  end
         | 
| 173 269 | 
             
                end
         | 
| 174 | 
            -
               | 
| 270 | 
            +
              )
         | 
| 175 271 |  | 
| 176 | 
            -
              #  | 
| 177 | 
            -
              #  | 
| 178 | 
            -
              #  | 
| 179 | 
            -
               | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
                   | 
| 183 | 
            -
             | 
| 272 | 
            +
              # Nodes that are always on their own line occur when the lexer is in the
         | 
| 273 | 
            +
              # EXPR_BEG state. Those comments are tracked within the @block_comments
         | 
| 274 | 
            +
              # instance variable. Then for each node that could contain them, we attach
         | 
| 275 | 
            +
              # them after the node has been built.
         | 
| 276 | 
            +
              prepend(
         | 
| 277 | 
            +
                Module.new do
         | 
| 278 | 
            +
                  events = {
         | 
| 279 | 
            +
                    begin: [0, :body, 0],
         | 
| 280 | 
            +
                    bodystmt: [0],
         | 
| 281 | 
            +
                    class: [2, :body, 0],
         | 
| 282 | 
            +
                    def: [2, :body, 0],
         | 
| 283 | 
            +
                    defs: [4, :body, 0],
         | 
| 284 | 
            +
                    else: [0],
         | 
| 285 | 
            +
                    elsif: [1],
         | 
| 286 | 
            +
                    ensure: [0],
         | 
| 287 | 
            +
                    if: [1],
         | 
| 288 | 
            +
                    program: [0],
         | 
| 289 | 
            +
                    rescue: [2],
         | 
| 290 | 
            +
                    sclass: [1, :body, 0],
         | 
| 291 | 
            +
                    unless: [1],
         | 
| 292 | 
            +
                    until: [1],
         | 
| 293 | 
            +
                    when: [1],
         | 
| 294 | 
            +
                    while: [1]
         | 
| 295 | 
            +
                  }
         | 
| 184 296 |  | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 297 | 
            +
                  def initialize(*args)
         | 
| 298 | 
            +
                    super(*args)
         | 
| 299 | 
            +
                    @block_comments = []
         | 
| 300 | 
            +
                    @current_embdoc = nil
         | 
| 301 | 
            +
                  end
         | 
| 188 302 |  | 
| 189 | 
            -
             | 
| 303 | 
            +
                  def self.prepended(base)
         | 
| 304 | 
            +
                    base.attr_reader :block_comments, :current_embdoc
         | 
| 305 | 
            +
                  end
         | 
| 190 306 |  | 
| 191 | 
            -
             | 
| 192 | 
            -
                  super(body).tap { |sexp| heredoc_stack << sexp }
         | 
| 193 | 
            -
                end
         | 
| 307 | 
            +
                  private
         | 
| 194 308 |  | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
             | 
| 309 | 
            +
                  def attach_comments(sexp, stmts)
         | 
| 310 | 
            +
                    range = sexp[:start]..sexp[:end]
         | 
| 311 | 
            +
                    comments =
         | 
| 312 | 
            +
                      block_comments.group_by { |comment| range.include?(comment[:start]) }
         | 
| 198 313 |  | 
| 199 | 
            -
             | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
                end
         | 
| 314 | 
            +
                    if comments[true]
         | 
| 315 | 
            +
                      stmts[:body] =
         | 
| 316 | 
            +
                        (stmts[:body] + comments[true]).sort_by { |node| node[:start] }
         | 
| 203 317 |  | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 318 | 
            +
                      @block_comments = comments.fetch(false) { [] }
         | 
| 319 | 
            +
                    end
         | 
| 320 | 
            +
                  end
         | 
| 207 321 |  | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 322 | 
            +
                  events.each do |event, path|
         | 
| 323 | 
            +
                    define_method(:"on_#{event}") do |*body|
         | 
| 324 | 
            +
                      super(*body).tap { |sexp| attach_comments(sexp, body.dig(*path)) }
         | 
| 325 | 
            +
                    end
         | 
| 326 | 
            +
                  end
         | 
| 212 327 |  | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 328 | 
            +
                  def on_comment(body)
         | 
| 329 | 
            +
                    super(body).tap do |sexp|
         | 
| 330 | 
            +
                      block_comments << sexp if RipperJS.lex_state_name(state) == 'EXPR_BEG'
         | 
| 331 | 
            +
                    end
         | 
| 332 | 
            +
                  end
         | 
| 215 333 |  | 
| 216 | 
            -
                   | 
| 217 | 
            -
                     | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
                    super
         | 
| 334 | 
            +
                  def on_embdoc_beg(comment)
         | 
| 335 | 
            +
                    @current_embdoc = {
         | 
| 336 | 
            +
                      type: :embdoc, body: comment, start: lineno, end: lineno
         | 
| 337 | 
            +
                    }
         | 
| 221 338 | 
             
                  end
         | 
| 222 | 
            -
                end
         | 
| 223 | 
            -
              end
         | 
| 224 339 |  | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
              # everything, so we need to force the encoding back into UTF-8 so that
         | 
| 229 | 
            -
              # the JSON library won't break.
         | 
| 230 | 
            -
              module Encoding
         | 
| 231 | 
            -
                events = %w[comment ident tstring_content]
         | 
| 340 | 
            +
                  def on_embdoc(comment)
         | 
| 341 | 
            +
                    @current_embdoc[:body] << comment
         | 
| 342 | 
            +
                  end
         | 
| 232 343 |  | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
                     | 
| 344 | 
            +
                  def on_embdoc_end(comment)
         | 
| 345 | 
            +
                    @current_embdoc[:body] << comment.chomp
         | 
| 346 | 
            +
                    @block_comments << @current_embdoc
         | 
| 347 | 
            +
                    @current_embdoc = nil
         | 
| 236 348 | 
             
                  end
         | 
| 237 | 
            -
                end
         | 
| 238 | 
            -
              end
         | 
| 239 349 |  | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
             | 
| 244 | 
            -
              module InlineComments
         | 
| 245 | 
            -
                # Certain events needs to steal the comments from their children in order
         | 
| 246 | 
            -
                # for them to display properly.
         | 
| 247 | 
            -
                events = {
         | 
| 248 | 
            -
                  args_add_block: [:body, 0],
         | 
| 249 | 
            -
                  break: [:body, 0],
         | 
| 250 | 
            -
                  command: [:body, 1],
         | 
| 251 | 
            -
                  command_call: [:body, 3],
         | 
| 252 | 
            -
                  regexp_literal: [:body, 0],
         | 
| 253 | 
            -
                  string_literal: [:body, 0],
         | 
| 254 | 
            -
                  symbol_literal: [:body, 0]
         | 
| 255 | 
            -
                }
         | 
| 256 | 
            -
             | 
| 257 | 
            -
                def initialize(*args)
         | 
| 258 | 
            -
                  super(*args)
         | 
| 259 | 
            -
                  @inline_comments = []
         | 
| 260 | 
            -
                  @last_sexp = nil
         | 
| 261 | 
            -
                end
         | 
| 350 | 
            +
                  def on_method_add_block(*body)
         | 
| 351 | 
            +
                    super(*body).tap do |sexp|
         | 
| 352 | 
            +
                      stmts = body[1][:body][1]
         | 
| 353 | 
            +
                      stmts = stmts[:type] == :stmts ? stmts : body[1][:body][1][:body][0]
         | 
| 262 354 |  | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 355 | 
            +
                      attach_comments(sexp, stmts)
         | 
| 356 | 
            +
                    end
         | 
| 357 | 
            +
                  end
         | 
| 265 358 | 
             
                end
         | 
| 359 | 
            +
              )
         | 
| 266 360 |  | 
| 267 | 
            -
             | 
| 361 | 
            +
              # Tracking heredocs in somewhat interesting. Straight-line heredocs are
         | 
| 362 | 
            +
              # reported as strings, whereas squiggly-line heredocs are reported as
         | 
| 363 | 
            +
              # heredocs. We track the start and matching end of the heredoc as "beging" and
         | 
| 364 | 
            +
              # "ending" respectively.
         | 
| 365 | 
            +
              prepend(
         | 
| 366 | 
            +
                Module.new do
         | 
| 367 | 
            +
                  def initialize(*args)
         | 
| 368 | 
            +
                    super(*args)
         | 
| 369 | 
            +
                    @heredoc_stack = []
         | 
| 370 | 
            +
                  end
         | 
| 268 371 |  | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
                    @last_sexp =
         | 
| 272 | 
            -
                      super(*body).tap do |sexp|
         | 
| 273 | 
            -
                        comments = (sexp.dig(*path) || {}).delete(:comments)
         | 
| 274 | 
            -
                        sexp.merge!(comments: comments) if comments
         | 
| 275 | 
            -
                      end
         | 
| 372 | 
            +
                  def self.prepended(base)
         | 
| 373 | 
            +
                    base.attr_reader :heredoc_stack
         | 
| 276 374 | 
             
                  end
         | 
| 277 | 
            -
                end
         | 
| 278 375 |  | 
| 279 | 
            -
             | 
| 376 | 
            +
                  private
         | 
| 280 377 |  | 
| 281 | 
            -
             | 
| 282 | 
            -
             | 
| 283 | 
            -
             | 
| 284 | 
            -
                  @last_sexp =
         | 
| 285 | 
            -
                    super(*body).tap do |sexp|
         | 
| 286 | 
            -
                      next unless SPECIAL_LITERALS.include?(body.dig(0, :type))
         | 
| 378 | 
            +
                  def on_embexpr_beg(body)
         | 
| 379 | 
            +
                    super(body).tap { |sexp| heredoc_stack << sexp }
         | 
| 380 | 
            +
                  end
         | 
| 287 381 |  | 
| 288 | 
            -
             | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 291 | 
            -
                end
         | 
| 382 | 
            +
                  def on_embexpr_end(body)
         | 
| 383 | 
            +
                    super(body).tap { heredoc_stack.pop }
         | 
| 384 | 
            +
                  end
         | 
| 292 385 |  | 
| 293 | 
            -
             | 
| 294 | 
            -
             | 
| 295 | 
            -
             | 
| 296 | 
            -
                   | 
| 297 | 
            -
                    super(*body).tap do |sexp|
         | 
| 298 | 
            -
                      comments =
         | 
| 299 | 
            -
                        (sexp.dig(:body, 0).delete(:comments) || []) +
         | 
| 300 | 
            -
                          (sexp.dig(:body, 1).delete(:comments) || [])
         | 
| 386 | 
            +
                  def on_heredoc_beg(beging)
         | 
| 387 | 
            +
                    heredoc = { type: :heredoc, beging: beging, start: lineno, end: lineno }
         | 
| 388 | 
            +
                    heredoc_stack << heredoc
         | 
| 389 | 
            +
                  end
         | 
| 301 390 |  | 
| 302 | 
            -
             | 
| 303 | 
            -
                    end
         | 
| 304 | 
            -
             | 
| 391 | 
            +
                  def on_heredoc_end(ending)
         | 
| 392 | 
            +
                    heredoc_stack[-1].merge!(ending: ending.chomp, end: lineno)
         | 
| 393 | 
            +
                  end
         | 
| 305 394 |  | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
             | 
| 310 | 
            -
                  @last_sexp = super(body)
         | 
| 311 | 
            -
                end
         | 
| 395 | 
            +
                  def on_heredoc_dedent(string, _width)
         | 
| 396 | 
            +
                    heredoc = heredoc_stack.pop
         | 
| 397 | 
            +
                    string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
         | 
| 398 | 
            +
                  end
         | 
| 312 399 |  | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
                #
         | 
| 316 | 
            -
                # enum lex_state_bits {
         | 
| 317 | 
            -
                #     EXPR_BEG_bit,    /* ignore newline, +/- is a sign. */
         | 
| 318 | 
            -
                #     EXPR_END_bit,    /* newline significant, +/- is an operator. */
         | 
| 319 | 
            -
                #     EXPR_ENDARG_bit,    /* ditto, and unbound braces. */
         | 
| 320 | 
            -
                #     EXPR_ENDFN_bit,    /* ditto, and unbound braces. */
         | 
| 321 | 
            -
                #     EXPR_ARG_bit,    /* newline significant, +/- is an operator. */
         | 
| 322 | 
            -
                #     EXPR_CMDARG_bit,    /* newline significant, +/- is an operator. */
         | 
| 323 | 
            -
                #     EXPR_MID_bit,    /* newline significant, +/- is an operator. */
         | 
| 324 | 
            -
                #     EXPR_FNAME_bit,    /* ignore newline, no reserved words. */
         | 
| 325 | 
            -
                #     EXPR_DOT_bit,    /* right after `.' or `::', no reserved words. */
         | 
| 326 | 
            -
                #     EXPR_CLASS_bit,    /* immediate after `class', no here document. */
         | 
| 327 | 
            -
                #     EXPR_LABEL_bit,    /* flag bit, label is allowed. */
         | 
| 328 | 
            -
                #     EXPR_LABELED_bit,    /* flag bit, just after a label. */
         | 
| 329 | 
            -
                #     EXPR_FITEM_bit,    /* symbol literal as FNAME. */
         | 
| 330 | 
            -
                #     EXPR_MAX_STATE
         | 
| 331 | 
            -
                # };
         | 
| 332 | 
            -
                def on_comment(body)
         | 
| 333 | 
            -
                  sexp = { type: :@comment, body: body.chomp, start: lineno, end: lineno }
         | 
| 334 | 
            -
             | 
| 335 | 
            -
                  case RipperJS.lex_state_name(state)
         | 
| 336 | 
            -
                  when 'EXPR_END', 'EXPR_ARG|EXPR_LABELED', 'EXPR_ENDFN'
         | 
| 337 | 
            -
                    last_sexp.merge!(comments: [sexp])
         | 
| 338 | 
            -
                  when 'EXPR_CMDARG', 'EXPR_END|EXPR_ENDARG', 'EXPR_ENDARG', 'EXPR_ARG',
         | 
| 339 | 
            -
                       'EXPR_FNAME|EXPR_FITEM', 'EXPR_CLASS', 'EXPR_END|EXPR_LABEL'
         | 
| 340 | 
            -
                    inline_comments << sexp
         | 
| 341 | 
            -
                  when 'EXPR_BEG|EXPR_LABEL', 'EXPR_MID'
         | 
| 342 | 
            -
                    inline_comments << sexp.merge!(break: true)
         | 
| 343 | 
            -
                  when 'EXPR_DOT'
         | 
| 344 | 
            -
                    last_sexp.merge!(comments: [sexp.merge!(break: true)])
         | 
| 345 | 
            -
                  end
         | 
| 346 | 
            -
             | 
| 347 | 
            -
                  sexp
         | 
| 348 | 
            -
                end
         | 
| 400 | 
            +
                  def on_string_literal(string)
         | 
| 401 | 
            +
                    heredoc = heredoc_stack[-1]
         | 
| 349 402 |  | 
| 350 | 
            -
             | 
| 403 | 
            +
                    if heredoc && string[:type] != :heredoc && heredoc[:type] == :heredoc
         | 
| 404 | 
            +
                      heredoc_stack.pop
         | 
| 405 | 
            +
                      string.merge!(heredoc.slice(:type, :beging, :ending, :start, :end))
         | 
| 406 | 
            +
                    else
         | 
| 407 | 
            +
                      super
         | 
| 408 | 
            +
                    end
         | 
| 409 | 
            +
                  end
         | 
| 410 | 
            +
                end
         | 
| 411 | 
            +
              )
         | 
| 351 412 |  | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 354 | 
            -
             | 
| 355 | 
            -
             | 
| 356 | 
            -
             | 
| 413 | 
            +
              # These are the event types that contain _actual_ string content. If there is
         | 
| 414 | 
            +
              # an encoding magic comment at the top of the file, ripper will actually
         | 
| 415 | 
            +
              # change into that encoding for the storage of the string. This will break
         | 
| 416 | 
            +
              # everything, so we need to force the encoding back into UTF-8 so that
         | 
| 417 | 
            +
              # the JSON library won't break.
         | 
| 418 | 
            +
              prepend(
         | 
| 419 | 
            +
                Module.new do
         | 
| 420 | 
            +
                  private
         | 
| 357 421 |  | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 422 | 
            +
                  %w[comment ident tstring_content].each do |event|
         | 
| 423 | 
            +
                    define_method(:"on_#{event}") do |body|
         | 
| 424 | 
            +
                      super(body.force_encoding('UTF-8'))
         | 
| 360 425 | 
             
                    end
         | 
| 361 426 | 
             
                  end
         | 
| 362 427 | 
             
                end
         | 
| 363 | 
            -
               | 
| 428 | 
            +
              )
         | 
| 364 429 |  | 
| 365 430 | 
             
              # Handles __END__ syntax, which allows individual scripts to keep content
         | 
| 366 | 
            -
              # after the main ruby code that can be read through DATA.
         | 
| 367 | 
            -
               | 
| 368 | 
            -
             | 
| 369 | 
            -
             | 
| 370 | 
            -
             | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 431 | 
            +
              # after the main ruby code that can be read through DATA. Which looks like:
         | 
| 432 | 
            +
              #
         | 
| 433 | 
            +
              # foo.bar
         | 
| 434 | 
            +
              #
         | 
| 435 | 
            +
              # __END__
         | 
| 436 | 
            +
              # some other content that isn't read by ripper normally
         | 
| 437 | 
            +
              prepend(
         | 
| 438 | 
            +
                Module.new do
         | 
| 439 | 
            +
                  def initialize(source, *args)
         | 
| 440 | 
            +
                    super(source, *args)
         | 
| 441 | 
            +
                    @source = source
         | 
| 442 | 
            +
                    @ending = nil
         | 
| 443 | 
            +
                  end
         | 
| 373 444 |  | 
| 374 | 
            -
             | 
| 375 | 
            -
             | 
| 376 | 
            -
             | 
| 445 | 
            +
                  def self.prepended(base)
         | 
| 446 | 
            +
                    base.attr_reader :source, :ending
         | 
| 447 | 
            +
                  end
         | 
| 377 448 |  | 
| 378 | 
            -
             | 
| 449 | 
            +
                  private
         | 
| 379 450 |  | 
| 380 | 
            -
             | 
| 381 | 
            -
             | 
| 382 | 
            -
             | 
| 451 | 
            +
                  def on___end__(body)
         | 
| 452 | 
            +
                    @ending = super(source.split("\n")[lineno..-1].join("\n"))
         | 
| 453 | 
            +
                  end
         | 
| 383 454 |  | 
| 384 | 
            -
             | 
| 385 | 
            -
             | 
| 455 | 
            +
                  def on_program(*body)
         | 
| 456 | 
            +
                    super(*body).tap { |sexp| sexp[:body][0][:body] << ending if ending }
         | 
| 457 | 
            +
                  end
         | 
| 386 458 | 
             
                end
         | 
| 387 | 
            -
               | 
| 459 | 
            +
              )
         | 
| 388 460 |  | 
| 389 | 
            -
              # Adds the used quote type onto string nodes.
         | 
| 390 | 
            -
               | 
| 391 | 
            -
             | 
| 461 | 
            +
              # Adds the used quote type onto string nodes. This is necessary because we're
         | 
| 462 | 
            +
              # going to have to stick to whatever quote the user chose if there are escape
         | 
| 463 | 
            +
              # sequences within the string. For example, if you have '\n' we can't switch
         | 
| 464 | 
            +
              # to double quotes without changing what it means.
         | 
| 465 | 
            +
              prepend(
         | 
| 466 | 
            +
                Module.new do
         | 
| 467 | 
            +
                  private
         | 
| 392 468 |  | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 469 | 
            +
                  def on_tstring_end(quote)
         | 
| 470 | 
            +
                    last_sexp.merge!(quote: quote)
         | 
| 471 | 
            +
                  end
         | 
| 396 472 |  | 
| 397 | 
            -
             | 
| 398 | 
            -
             | 
| 473 | 
            +
                  def on_label_end(quote)
         | 
| 474 | 
            +
                    last_sexp.merge!(quote: quote[0]) # quote is ": or ':
         | 
| 475 | 
            +
                  end
         | 
| 399 476 | 
             
                end
         | 
| 400 | 
            -
               | 
| 477 | 
            +
              )
         | 
| 401 478 |  | 
| 402 479 | 
             
              # Normally access controls are reported as vcall nodes. This module creates a
         | 
| 403 | 
            -
              # new node type to explicitly track those nodes instead | 
| 404 | 
            -
               | 
| 405 | 
            -
             | 
| 406 | 
            -
             | 
| 407 | 
            -
                   | 
| 408 | 
            -
                end
         | 
| 409 | 
            -
             | 
| 410 | 
            -
                def self.prepended(base)
         | 
| 411 | 
            -
                  base.attr_reader :lines
         | 
| 412 | 
            -
                end
         | 
| 413 | 
            -
             | 
| 414 | 
            -
                private
         | 
| 480 | 
            +
              # new node type to explicitly track those nodes instead, so that the printer
         | 
| 481 | 
            +
              # can add new lines as necessary.
         | 
| 482 | 
            +
              prepend(
         | 
| 483 | 
            +
                Module.new do
         | 
| 484 | 
            +
                  KEYWORDS = %w[private protected public].freeze
         | 
| 415 485 |  | 
| 416 | 
            -
             | 
| 417 | 
            -
             | 
| 418 | 
            -
                     | 
| 419 | 
            -
             | 
| 420 | 
            -
                      next
         | 
| 421 | 
            -
                    end
         | 
| 486 | 
            +
                  def initialize(source, *args)
         | 
| 487 | 
            +
                    super(source, *args)
         | 
| 488 | 
            +
                    @lines = source.split("\n")
         | 
| 489 | 
            +
                  end
         | 
| 422 490 |  | 
| 423 | 
            -
             | 
| 491 | 
            +
                  def self.prepended(base)
         | 
| 492 | 
            +
                    base.attr_reader :lines
         | 
| 424 493 | 
             
                  end
         | 
| 425 | 
            -
                end
         | 
| 426 | 
            -
              end
         | 
| 427 | 
            -
            end
         | 
| 428 494 |  | 
| 429 | 
            -
             | 
| 430 | 
            -
              private
         | 
| 495 | 
            +
                  private
         | 
| 431 496 |  | 
| 432 | 
            -
             | 
| 433 | 
            -
             | 
| 434 | 
            -
             | 
| 435 | 
            -
             | 
| 436 | 
            -
             | 
| 497 | 
            +
                  def on_vcall(ident)
         | 
| 498 | 
            +
                    super(ident).tap do |sexp|
         | 
| 499 | 
            +
                      if !KEYWORDS.include?(ident[:body]) ||
         | 
| 500 | 
            +
                         ident[:body] != lines[lineno - 1].strip
         | 
| 501 | 
            +
                        next
         | 
| 502 | 
            +
                      end
         | 
| 437 503 |  | 
| 438 | 
            -
             | 
| 439 | 
            -
             | 
| 440 | 
            -
                   | 
| 441 | 
            -
                  { type: event, body: body, start: min || lineno, end: lineno }
         | 
| 504 | 
            +
                      sexp.merge!(type: :access_ctrl)
         | 
| 505 | 
            +
                    end
         | 
| 506 | 
            +
                  end
         | 
| 442 507 | 
             
                end
         | 
| 443 | 
            -
               | 
| 444 | 
            -
             | 
| 445 | 
            -
              prepend Layer::Lists
         | 
| 446 | 
            -
              prepend Layer::StartLine
         | 
| 447 | 
            -
              prepend Layer::InlineComments
         | 
| 448 | 
            -
              prepend Layer::BlockComments
         | 
| 449 | 
            -
              prepend Layer::Heredocs
         | 
| 450 | 
            -
              prepend Layer::Encoding
         | 
| 451 | 
            -
              prepend Layer::Ending
         | 
| 452 | 
            -
              prepend Layer::Strings
         | 
| 453 | 
            -
              prepend Layer::AccessControls
         | 
| 508 | 
            +
              )
         | 
| 454 509 |  | 
| 455 510 | 
             
              # When the only statement inside of a `def` node is a `begin` node, then you
         | 
| 456 511 | 
             
              # can safely replace the body of the `def` with the body of the `begin`. For
         | 
| @@ -529,6 +584,10 @@ class RipperJS < Ripper | |
| 529 584 | 
             
              )
         | 
| 530 585 | 
             
            end
         | 
| 531 586 |  | 
| 587 | 
            +
            # If this is the main file we're executing, then most likely this is being
         | 
| 588 | 
            +
            # executed from the parse.js spawn. In that case, read the ruby source from
         | 
| 589 | 
            +
            # stdin and report back the AST over stdout.
         | 
| 590 | 
            +
             | 
| 532 591 | 
             
            if $0 == __FILE__
         | 
| 533 592 | 
             
              builder = RipperJS.new($stdin.read)
         | 
| 534 593 | 
             
              response = builder.parse
         |