File: ripper.rb

package info (click to toggle)
ruby-test-prof 1.6.0%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,448 kB
  • sloc: ruby: 13,093; sh: 4; makefile: 4
file content (128 lines) | stat: -rw-r--r-- 3,318 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# frozen_string_literal: true

require "ripper"

module TestProf
  module RSpecStamp
    module Parser
      class Ripper
        def parse(code)
          sexp = ::Ripper.sexp(code)
          return unless sexp

          # sexp has the following format:
          # [:program,
          #   [
          #     [
          #       :command,
          #       [:@ident, "it", [1, 0]],
          #       [:args_add_block, [ ... ]]
          #     ]
          #   ]
          # ]
          #
          # or
          #
          # [:program,
          #   [
          #     [
          #       :vcall,
          #       [:@ident, "it", [1, 0]]
          #     ]
          #   ]
          # ]
          res = Result.new

          fcall = sexp[1][0][1]
          args_block = sexp[1][0][2]

          if fcall.first == :fcall
            fcall = fcall[1]
          elsif fcall.first == :var_ref
            res.fname = [parse_const(fcall), sexp[1][0][3][1]].join(".")
            args_block = sexp[1][0][4]
          end

          res.fname ||= fcall[1]

          return res if args_block.nil?

          args_block = args_block[1] if args_block.first == :arg_paren

          args = args_block[1]

          if args.first.first == :string_literal
            res.desc = parse_literal(args.shift)
          elsif args.first.first == :var_ref || args.first.first == :const_path_ref
            res.desc_const = parse_const(args.shift)
          end

          parse_arg(res, args.shift) until args.empty?

          res
        end

        private

        def parse_arg(res, arg)
          if arg.first == :symbol_literal
            res.add_tag parse_literal(arg)
          elsif arg.first == :bare_assoc_hash
            parse_hash(res, arg[1])
          end
        end

        def parse_hash(res, hash_arg)
          hash_arg.each do |(_, label, val)|
            res.add_htag label[1][0..-2].to_sym, parse_value(val)
          end
        end

        # Expr of the form:
        #  bool - [:var_ref, [:@kw, "true", [1, 24]]]
        #  string - [:string_literal, [:string_content, [...]]]
        #  int - [:@int, "3", [1, 52]]]]
        def parse_value(expr)
          case expr.first
          when :var_ref
            expr[1][1] == "true"
          when :@int
            expr[1].to_i
          when :@float
            expr[1].to_f
          else
            parse_literal(expr)
          end
        end

        # Expr of the form:
        #  [:string_literal, [:string_content, [:@tstring_content, "is", [1, 4]]]]
        def parse_literal(expr)
          val = expr[1][1][1]
          val = val.to_sym if expr[0] == :symbol_literal ||
            expr[0] == :assoc_new
          val
        end

        # Expr of the form:
        #  [:var_ref, [:@const, "User", [1, 9]]]
        #
        #  or
        #
        #  [:const_path_ref, [:const_path_ref, [:var_ref,
        #    [:@const, "User", [1, 17]]],
        #    [:@const, "Guest", [1, 23]]],
        #    [:@const, "Collection", [1, 30]]
        def parse_const(expr)
          if expr.first == :var_ref
            expr[1][1]
          elsif expr.first == :@const
            expr[1]
          elsif expr.first == :const_path_ref
            expr[1..].map(&method(:parse_const)).join("::")
          end
        end
      end
    end
  end
end