Module: Orthoses::Utils

Defined in:
lib/orthoses/utils.rb,
lib/orthoses/utils/type_list.rb,
lib/orthoses/utils/underscore.rb

Defined Under Namespace

Modules: Underscore Classes: TypeList

Constant Summary collapse

UNBOUND_NAME_METHOD =
Module.instance_method(:name)

Class Method Summary collapse

Class Method Details

.attached_module_name(mod) ⇒ Object



176
177
178
179
180
181
182
183
184
185
# File 'lib/orthoses/utils.rb', line 176

def self.attached_module_name(mod)
  if mod.respond_to?(:attached_object)
    attached_object = mod.attached_object
    (attached_object&.is_a?(Module) && attached_object.name) || nil
  else
    # e.g. `Minitest::Spec::DSL#to_s` may return `nil` with `#to_s`
    m = mod.to_s&.match(/#<Class:([\w:]+)>/)
    m && m[1]
  end
end

.each_const_recursive(root, cache: {}, on_error: nil, &block) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/orthoses/utils.rb', line 15

def self.each_const_recursive(root, cache: {}, on_error: nil, &block)
  root.constants(false).sort.each do |const|
    val = root.const_get(const)
    next if cache[const].equal?(val)
    cache[const] = val
    next if val.equal?(root)
    block.call(root, const, val) if block
    if val.respond_to?(:constants)
      each_const_recursive(val, cache: cache, on_error: on_error, &block)
    end
  rescue LoadError, StandardError => err
    Orthoses.logger.warn("Orthoses::Utils.each_const_recursive: #{err.class}: #{err.message} on #{err.backtrace.first}")
    if on_error
      on_error.call(ConstLoadError.new(root: root, const: const, error: err))
    end
  end
end

.known_type_params(name) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/orthoses/utils.rb', line 187

def self.known_type_params(name)
  type_name =
    case name
    when String
      RBS::TypeName.parse(name).absolute!
    when Module
      module_to_type_name(name).absolute!
    when RBS::TypeName
      name.absolute!
    else
      raise TypeError, "#{name.class} is not supported yet"
    end
  rbs_environment.class_decls[type_name]&.then do |entry|
    entry.type_params
  end
end

.module_name(mod) ⇒ Object

Want to get the module name even if the method is overwritten. e.g.) Rails::Info



162
163
164
165
# File 'lib/orthoses/utils.rb', line 162

def self.module_name(mod)
  return nil unless mod
  UNBOUND_NAME_METHOD.bind(mod).call
end

.module_to_type_name(mod) ⇒ Object



167
168
169
170
171
172
173
174
# File 'lib/orthoses/utils.rb', line 167

def self.module_to_type_name(mod)
  name = Utils.module_name(mod)
  if name && !name.empty?
    RBS::TypeName.parse(name)
  else
    nil
  end
end

.new_storeObject



204
205
206
# File 'lib/orthoses/utils.rb', line 204

def self.new_store
  Hash.new { |h, k| h[k] = Content.new(name: k) }
end

.object_to_rbs(object, strict: false) ⇒ Object



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
# File 'lib/orthoses/utils.rb', line 91

def self.object_to_rbs(object, strict: false)
  case object
  when Class, Module
    if name = module_name(object)
      "singleton(#{name})"
    else
      "untyped"
    end
  when Integer, Symbol, String
    if strict
      object.inspect
    else
      module_name(object.class) || 'untyped'
    end
  when true, false
    if strict
      object.inspect
    else
      "bool"
    end
  when nil
    "nil"
  when Set
    if object.empty?
      "Set[untyped]"
    else
      ary = object.map do |o|
        object_to_rbs(o, strict: strict)
      end
      "Set[#{ary.uniq.join(' | ')}]"
    end
  when Array
    if object.empty?
      "Array[untyped]"
    else
      ary = object.map do |o|
        object_to_rbs(o, strict: strict)
      end
      if strict
        "[#{ary.join(', ')}]"
      else
        "Array[#{TypeList.new(ary).inject}]"
      end
    end
  when Hash
    if object.empty?
      "Hash[untyped, untyped]"
    else
      if strict && object.keys.all? { |key| key.is_a?(Symbol) && /\A\w+\z/.match?(key) }
        "{ #{object.map { |k, v| "#{k}: #{object_to_rbs(v, strict: strict)}" }.join(', ')} }"
      else
        keys = object.keys.map { |k| object_to_rbs(k, strict: strict) }
        values = object.values.map { |v| object_to_rbs(v, strict: strict) }
        "Hash[#{TypeList.new(keys).inject}, #{TypeList.new(values).inject}]"
      end
    end
  when Range
    type = object_to_rbs(object.begin || object.end, strict: false)
    "Range[#{type}]"
  when ARGF
    # see also https://github.com/ruby/rbs/pull/975
    'untyped'
  else
    module_name(object.class) || 'untyped'
  end
end

.rbs_defined_class?(name, library: nil, collection: true) ⇒ Boolean

Returns:

  • (Boolean)


41
42
43
44
45
46
# File 'lib/orthoses/utils.rb', line 41

def self.rbs_defined_class?(name, library: nil, collection: true)
  return false if name.start_with?("#<")
  env = rbs_environment(library: library, collection: collection)
  target = rbs_type_name(name)
  env.class_decls.has_key?(target)
end

.rbs_defined_const?(name, library: nil, collection: true) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
36
37
38
39
# File 'lib/orthoses/utils.rb', line 33

def self.rbs_defined_const?(name, library: nil, collection: true)
  return false if name.start_with?("#<")
  env = rbs_environment(library: library, collection: collection)
  name = name.sub(/Object::/, '')
  target = rbs_type_name(name)
  env.constant_decls.has_key?(target)
end

.rbs_environment(library: nil, collection: true, cache: true) ⇒ Object



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
# File 'lib/orthoses/utils.rb', line 53

def self.rbs_environment(library: nil, collection: true, cache: true)
  @env_cache ||= {}
  if cache && hit = @env_cache[[library, collection]]
    return hit
  end

  loader = RBS::EnvironmentLoader.new

  if collection
    config_path = RBS::Collection::Config.find_config_path || RBS::Collection::Config::PATH || raise("needs rbs_collection.yaml")
    lock_path = RBS::Collection::Config.to_lockfile_path(config_path)
    if lock_path.file?
      loader.add_collection(RBS::Collection::Config::Lockfile.from_lockfile(
        lockfile_path: lock_path,
        data: YAML.load_file(lock_path.to_s)
      ))
    else
      Orthoses.logger.debug("rbs_collection.lock.yaml not found in #{lock_path}")
    end
  end

  case library
  when "stdlib"
    RBS::Repository::DEFAULT_STDLIB_ROOT.each_entry do |path|
      lib = path.to_s
      loader.add(library: lib.to_s, version: nil) unless lib == "." || lib == ".."
    end
  else
    Array(library).each do |lib|
      loader.add(library: lib.to_s, version: nil)
    end
  end

  RBS::Environment.from_loader(loader).resolve_type_names.tap do |env|
    @env_cache[[library, collection]] = env if cache
  end
end

.rbs_type_name(name) ⇒ Object



48
49
50
51
# File 'lib/orthoses/utils.rb', line 48

def self.rbs_type_name(name)
  name = "::#{name}" if !name.start_with?("::")
  RBS::Namespace.parse(name).to_type_name
end

.unautoload!Object



8
9
10
11
12
13
# File 'lib/orthoses/utils.rb', line 8

def self.unautoload!
  warn "`Orthoses::Utils.unautoload!` is deprecated. please use `Orthoses::Autoload` middleware instead."
  ObjectSpace.each_object(Module) do |mod|
    each_const_recursive(mod)
  end
end