Class: RubyLint::Inspector

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby-lint/inspector.rb

Overview

The Inspector class is a debugging related class primarily used for making it easy to display a list of methods/constants of a given source constant.

Note that this class is considered to be a private API and as such may change without any notice.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(constant) ⇒ Inspector

Returns a new instance of Inspector

Parameters:



21
22
23
24
25
26
27
28
29
# File 'lib/ruby-lint/inspector.rb', line 21

def initialize(constant)
  @constant_name = constant.to_s

  if constant.is_a?(String)
    @constant = resolve_constant(constant)
  else
    @constant = constant
  end
end

Instance Attribute Details

#constantClass (readonly)

Returns:

  • (Class)


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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/ruby-lint/inspector.rb', line 15

class Inspector
  attr_reader :constant, :constant_name

  ##
  # @param [String|Class] constant
  #
  def initialize(constant)
    @constant_name = constant.to_s

    if constant.is_a?(String)
      @constant = resolve_constant(constant)
    else
      @constant = constant
    end
  end

  ##
  # Returns an Array containing all constants and their child constants
  # (recursively).
  #
  # @param [Class] source
  # @param [Array] ignore
  # @return [Array<String>]
  #
  def inspect_constants(source = constant, ignore = [])
    names          = []
    source_name    = source.name
    have_children  = []
    include_source = source != Object

    if include_source and !ignore.include?(source_name)
      names  << source_name
      ignore << source_name
    end

    source.constants.each do |name|
      next if skip_constant?(source, name)

      full_name = include_source ? "#{source_name}::#{name}" : name.to_s

      # In certain cases this code tries to load a constant that apparently
      # *is* defined but craps out upon error (e.g. Bundler::Specification).
      begin
        constant = source.const_get(name)
      rescue Exception => error
        warn error.message
        next
      end

      # Skip those that we've already processed.
      if ignore.include?(full_name) or source == constant
        next
      end

      names         << full_name
      ignore        << full_name
      have_children << constant if process_child_constants?(constant)
    end

    have_children.each do |const|
      names |= inspect_constants(const, ignore)
    end

    # Reject every constant that, if we should include the source name, was
    # not defined under that constant. This applies on for example Rubinius
    # since `Range::Enumerator` is a constant that points to
    # `Enumerable::Enumerator`.
    if include_source
      names = names.select { |name| name.start_with?(source_name) }
    end

    return names
  end

  ##
  # Returns an Array containing all method objects sorted by their names.
  #
  # @return [Array]
  #
  def inspect_methods
    return [] unless constant.respond_to?(:methods)

    methods = get_methods.map do |name|
      method_information(:method, name)
    end

    return methods.sort_by(&:name)
  end

  ##
  # Returns an Array containing all instance methods sorted by their names.
  #
  # @return [Array]
  #
  def inspect_instance_methods
    return [] unless constant.respond_to?(:instance_methods)

    methods = get_methods(:instance_methods).map do |name|
      method_information(:instance_method, name)
    end

    return methods.sort_by(&:name)
  end

  ##
  # Returns the modules that are included in the constant.
  #
  # @return [Array]
  #
  def inspect_modules
    modules = []

    if constant.respond_to?(:ancestors)
      parent = inspect_superclass

      # Take all the modules included *directly* into the constant.
      modules = constant.ancestors.take_while do |ancestor|
        parent && ancestor != parent
      end

      # Get rid of non Module instances and modules that don't have a name.
      modules = modules.select do |mod|
        mod.instance_of?(Module) && mod.name
      end
    end

    return modules
  end

  ##
  # Returns the superclass of the current constant or `nil` if there is none.
  #
  # @return [Mixed]
  #
  def inspect_superclass
    parent = nil

    if constant.respond_to?(:superclass) \
    and constant.superclass \
    and constant.superclass.name
      return constant.superclass
    end

    return parent
  end

  ##
  # Gets the methods of the current constant minus those defined in Object.
  #
  # @param [Symbol] getter
  # @return [Array]
  #
  def get_methods(getter = :methods)
    parent = inspect_superclass || Object
    diff   = constant.__send__(getter, false) -
      parent.__send__(getter, false)

    methods = diff | constant.__send__(getter, false)

    # If the constant manually defines the initialize method (= private)
    # we'll also want to include it.
    if include_initialize?(getter)
      methods = methods | [:initialize]
    end

    return methods
  end

  private

  ##
  # @param [Symbol] getter
  # @return [TrueClass|FalseClass]
  #
  def include_initialize?(getter)
    return getter == :instance_methods \
      && constant.is_a?(Class) \
      && constant.private_instance_methods(false).include?(:initialize) \
      && constant.instance_method(:initialize).source_location
  end

  ##
  # @param [Module|Class] const
  # @param [Symbol] child_name
  # @return [TrueClass|FalseClass]
  #
  def skip_constant?(const, child_name)
    # Module and Class defines the same child constants as Object but in a
    # recursive manner. This is a bit of a dirty way to prevent this code
    # from going into an infinite loop.
    if const == Module or const == Class
      return true
    end

    # Config is deprecated and triggers a warning.
    if const == Object and child_name == :Config
      return true
    end

    return !const.const_defined?(child_name)
  end

  ##
  # @param [Class] constant
  # @return [TrueClass|FalseClass]
  #
  def process_child_constants?(constant)
    return constant.respond_to?(:constants) && !constant.constants.empty?
  end

  ##
  # Returns the method object for the given type and name.
  #
  # @param [Symbol] type
  # @param [Symbol] name
  # @return [UnboundMethod]
  #
  def method_information(type, name)
    return constant.__send__(type, name)
  end

  ##
  # Converts a String based constant path into an actual constant.
  #
  # @param [String] constant
  # @return [Class]
  # @raise [ArgumentError] Raised when one of the segments doesn't exist.
  #
  def resolve_constant(constant)
    current = Object
    final   = nil

    constant.split('::').each do |segment|
      if current.const_defined?(segment)
        current = final = current.const_get(segment)
      else
        raise(
          ArgumentError,
          "Constant #{segment} does not exist in #{constant}"
        )
      end
    end

    return final
  end
end

#constant_nameString (readonly)

Returns:



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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/ruby-lint/inspector.rb', line 15

class Inspector
  attr_reader :constant, :constant_name

  ##
  # @param [String|Class] constant
  #
  def initialize(constant)
    @constant_name = constant.to_s

    if constant.is_a?(String)
      @constant = resolve_constant(constant)
    else
      @constant = constant
    end
  end

  ##
  # Returns an Array containing all constants and their child constants
  # (recursively).
  #
  # @param [Class] source
  # @param [Array] ignore
  # @return [Array<String>]
  #
  def inspect_constants(source = constant, ignore = [])
    names          = []
    source_name    = source.name
    have_children  = []
    include_source = source != Object

    if include_source and !ignore.include?(source_name)
      names  << source_name
      ignore << source_name
    end

    source.constants.each do |name|
      next if skip_constant?(source, name)

      full_name = include_source ? "#{source_name}::#{name}" : name.to_s

      # In certain cases this code tries to load a constant that apparently
      # *is* defined but craps out upon error (e.g. Bundler::Specification).
      begin
        constant = source.const_get(name)
      rescue Exception => error
        warn error.message
        next
      end

      # Skip those that we've already processed.
      if ignore.include?(full_name) or source == constant
        next
      end

      names         << full_name
      ignore        << full_name
      have_children << constant if process_child_constants?(constant)
    end

    have_children.each do |const|
      names |= inspect_constants(const, ignore)
    end

    # Reject every constant that, if we should include the source name, was
    # not defined under that constant. This applies on for example Rubinius
    # since `Range::Enumerator` is a constant that points to
    # `Enumerable::Enumerator`.
    if include_source
      names = names.select { |name| name.start_with?(source_name) }
    end

    return names
  end

  ##
  # Returns an Array containing all method objects sorted by their names.
  #
  # @return [Array]
  #
  def inspect_methods
    return [] unless constant.respond_to?(:methods)

    methods = get_methods.map do |name|
      method_information(:method, name)
    end

    return methods.sort_by(&:name)
  end

  ##
  # Returns an Array containing all instance methods sorted by their names.
  #
  # @return [Array]
  #
  def inspect_instance_methods
    return [] unless constant.respond_to?(:instance_methods)

    methods = get_methods(:instance_methods).map do |name|
      method_information(:instance_method, name)
    end

    return methods.sort_by(&:name)
  end

  ##
  # Returns the modules that are included in the constant.
  #
  # @return [Array]
  #
  def inspect_modules
    modules = []

    if constant.respond_to?(:ancestors)
      parent = inspect_superclass

      # Take all the modules included *directly* into the constant.
      modules = constant.ancestors.take_while do |ancestor|
        parent && ancestor != parent
      end

      # Get rid of non Module instances and modules that don't have a name.
      modules = modules.select do |mod|
        mod.instance_of?(Module) && mod.name
      end
    end

    return modules
  end

  ##
  # Returns the superclass of the current constant or `nil` if there is none.
  #
  # @return [Mixed]
  #
  def inspect_superclass
    parent = nil

    if constant.respond_to?(:superclass) \
    and constant.superclass \
    and constant.superclass.name
      return constant.superclass
    end

    return parent
  end

  ##
  # Gets the methods of the current constant minus those defined in Object.
  #
  # @param [Symbol] getter
  # @return [Array]
  #
  def get_methods(getter = :methods)
    parent = inspect_superclass || Object
    diff   = constant.__send__(getter, false) -
      parent.__send__(getter, false)

    methods = diff | constant.__send__(getter, false)

    # If the constant manually defines the initialize method (= private)
    # we'll also want to include it.
    if include_initialize?(getter)
      methods = methods | [:initialize]
    end

    return methods
  end

  private

  ##
  # @param [Symbol] getter
  # @return [TrueClass|FalseClass]
  #
  def include_initialize?(getter)
    return getter == :instance_methods \
      && constant.is_a?(Class) \
      && constant.private_instance_methods(false).include?(:initialize) \
      && constant.instance_method(:initialize).source_location
  end

  ##
  # @param [Module|Class] const
  # @param [Symbol] child_name
  # @return [TrueClass|FalseClass]
  #
  def skip_constant?(const, child_name)
    # Module and Class defines the same child constants as Object but in a
    # recursive manner. This is a bit of a dirty way to prevent this code
    # from going into an infinite loop.
    if const == Module or const == Class
      return true
    end

    # Config is deprecated and triggers a warning.
    if const == Object and child_name == :Config
      return true
    end

    return !const.const_defined?(child_name)
  end

  ##
  # @param [Class] constant
  # @return [TrueClass|FalseClass]
  #
  def process_child_constants?(constant)
    return constant.respond_to?(:constants) && !constant.constants.empty?
  end

  ##
  # Returns the method object for the given type and name.
  #
  # @param [Symbol] type
  # @param [Symbol] name
  # @return [UnboundMethod]
  #
  def method_information(type, name)
    return constant.__send__(type, name)
  end

  ##
  # Converts a String based constant path into an actual constant.
  #
  # @param [String] constant
  # @return [Class]
  # @raise [ArgumentError] Raised when one of the segments doesn't exist.
  #
  def resolve_constant(constant)
    current = Object
    final   = nil

    constant.split('::').each do |segment|
      if current.const_defined?(segment)
        current = final = current.const_get(segment)
      else
        raise(
          ArgumentError,
          "Constant #{segment} does not exist in #{constant}"
        )
      end
    end

    return final
  end
end

Instance Method Details

#get_methods(getter = :methods) ⇒ Array

Gets the methods of the current constant minus those defined in Object.

Parameters:

  • getter (Symbol) (defaults to: :methods)

Returns:

  • (Array)


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/ruby-lint/inspector.rb', line 167

def get_methods(getter = :methods)
  parent = inspect_superclass || Object
  diff   = constant.__send__(getter, false) -
    parent.__send__(getter, false)

  methods = diff | constant.__send__(getter, false)

  # If the constant manually defines the initialize method (= private)
  # we'll also want to include it.
  if include_initialize?(getter)
    methods = methods | [:initialize]
  end

  return methods
end

#include_initialize?(getter) ⇒ TrueClass|FalseClass (private)

Parameters:

  • getter (Symbol)

Returns:

  • (TrueClass|FalseClass)


189
190
191
192
193
194
# File 'lib/ruby-lint/inspector.rb', line 189

def include_initialize?(getter)
  return getter == :instance_methods \
    && constant.is_a?(Class) \
    && constant.private_instance_methods(false).include?(:initialize) \
    && constant.instance_method(:initialize).source_location
end

#inspect_constants(source = constant, ignore = []) ⇒ Array<String>

Returns an Array containing all constants and their child constants (recursively).

Parameters:

  • source (Class) (defaults to: constant)
  • ignore (Array) (defaults to: [])

Returns:



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
# File 'lib/ruby-lint/inspector.rb', line 39

def inspect_constants(source = constant, ignore = [])
  names          = []
  source_name    = source.name
  have_children  = []
  include_source = source != Object

  if include_source and !ignore.include?(source_name)
    names  << source_name
    ignore << source_name
  end

  source.constants.each do |name|
    next if skip_constant?(source, name)

    full_name = include_source ? "#{source_name}::#{name}" : name.to_s

    # In certain cases this code tries to load a constant that apparently
    # *is* defined but craps out upon error (e.g. Bundler::Specification).
    begin
      constant = source.const_get(name)
    rescue Exception => error
      warn error.message
      next
    end

    # Skip those that we've already processed.
    if ignore.include?(full_name) or source == constant
      next
    end

    names         << full_name
    ignore        << full_name
    have_children << constant if process_child_constants?(constant)
  end

  have_children.each do |const|
    names |= inspect_constants(const, ignore)
  end

  # Reject every constant that, if we should include the source name, was
  # not defined under that constant. This applies on for example Rubinius
  # since `Range::Enumerator` is a constant that points to
  # `Enumerable::Enumerator`.
  if include_source
    names = names.select { |name| name.start_with?(source_name) }
  end

  return names
end

#inspect_instance_methodsArray

Returns an Array containing all instance methods sorted by their names.

Returns:

  • (Array)


109
110
111
112
113
114
115
116
117
# File 'lib/ruby-lint/inspector.rb', line 109

def inspect_instance_methods
  return [] unless constant.respond_to?(:instance_methods)

  methods = get_methods(:instance_methods).map do |name|
    method_information(:instance_method, name)
  end

  return methods.sort_by(&:name)
end

#inspect_methodsArray

Returns an Array containing all method objects sorted by their names.

Returns:

  • (Array)


94
95
96
97
98
99
100
101
102
# File 'lib/ruby-lint/inspector.rb', line 94

def inspect_methods
  return [] unless constant.respond_to?(:methods)

  methods = get_methods.map do |name|
    method_information(:method, name)
  end

  return methods.sort_by(&:name)
end

#inspect_modulesArray

Returns the modules that are included in the constant.

Returns:

  • (Array)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/ruby-lint/inspector.rb', line 124

def inspect_modules
  modules = []

  if constant.respond_to?(:ancestors)
    parent = inspect_superclass

    # Take all the modules included *directly* into the constant.
    modules = constant.ancestors.take_while do |ancestor|
      parent && ancestor != parent
    end

    # Get rid of non Module instances and modules that don't have a name.
    modules = modules.select do |mod|
      mod.instance_of?(Module) && mod.name
    end
  end

  return modules
end

#inspect_superclassMixed

Returns the superclass of the current constant or nil if there is none.

Returns:

  • (Mixed)


149
150
151
152
153
154
155
156
157
158
159
# File 'lib/ruby-lint/inspector.rb', line 149

def inspect_superclass
  parent = nil

  if constant.respond_to?(:superclass) \
  and constant.superclass \
  and constant.superclass.name
    return constant.superclass
  end

  return parent
end

#method_information(type, name) ⇒ UnboundMethod (private)

Returns the method object for the given type and name.

Parameters:

  • type (Symbol)
  • name (Symbol)

Returns:

  • (UnboundMethod)


232
233
234
# File 'lib/ruby-lint/inspector.rb', line 232

def method_information(type, name)
  return constant.__send__(type, name)
end

#process_child_constants?(constant) ⇒ TrueClass|FalseClass (private)

Parameters:

  • constant (Class)

Returns:

  • (TrueClass|FalseClass)


221
222
223
# File 'lib/ruby-lint/inspector.rb', line 221

def process_child_constants?(constant)
  return constant.respond_to?(:constants) && !constant.constants.empty?
end

#resolve_constant(constant) ⇒ Class (private)

Converts a String based constant path into an actual constant.

Parameters:

Returns:

  • (Class)

Raises:

  • (ArgumentError)

    Raised when one of the segments doesn’t exist.



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/ruby-lint/inspector.rb', line 243

def resolve_constant(constant)
  current = Object
  final   = nil

  constant.split('::').each do |segment|
    if current.const_defined?(segment)
      current = final = current.const_get(segment)
    else
      raise(
        ArgumentError,
        "Constant #{segment} does not exist in #{constant}"
      )
    end
  end

  return final
end

#skip_constant?(const, child_name) ⇒ TrueClass|FalseClass (private)

Parameters:

  • const (Module|Class)
  • child_name (Symbol)

Returns:

  • (TrueClass|FalseClass)


201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/ruby-lint/inspector.rb', line 201

def skip_constant?(const, child_name)
  # Module and Class defines the same child constants as Object but in a
  # recursive manner. This is a bit of a dirty way to prevent this code
  # from going into an infinite loop.
  if const == Module or const == Class
    return true
  end

  # Config is deprecated and triggers a warning.
  if const == Object and child_name == :Config
    return true
  end

  return !const.const_defined?(child_name)
end