Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
camertron committed Jun 24, 2019
0 parents commit deb5aa0
Show file tree
Hide file tree
Showing 29 changed files with 882 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Gemfile.lock
.DS_Store
pkg/
*.gem
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# 1.0.0
* Birthday!
8 changes: 8 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
source 'https://rubygems.org'

gemspec

group :development do
gem 'pry-byebug'
gem 'rspec'
end
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2015 Cameron C. Dutro

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
14 changes: 14 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
require 'bundler'
require 'rspec/core/rake_task'
require 'rubygems/package_task'

require 'gelauto'

Bundler::GemHelper.install_tasks

task default: :spec

desc 'Run specs'
RSpec::Core::RakeTask.new do |t|
t.pattern = './spec/**/*_spec.rb'
end
52 changes: 52 additions & 0 deletions bin/gelauto
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#! /usr/bin/env ruby

require 'gelauto'
require 'gli'

module Gelauto
module CLI
extend GLI::App

program_desc 'Automatically annotate methods with Sorbet type signatures.'

version Gelauto::VERSION

subcommand_option_handling :normal
default_command :run

desc 'Run the given command with Gelauto and optionally annotate files.'
command :run do |c|
c.desc 'Write discovered type signatures into Ruby files.'
c.default_value false
c.switch [:a, :annotate]

c.action do |global_options, options, args|
paths, _, cmd = args.chunk_while { |arg1, arg2| arg1 != '--' && arg2 != '--' }.to_a
Gelauto.paths += paths

exe = Gelauto::CLIUtils.which(cmd[0]) || cmd[0]
cmd.shift

old_argv = ARGV.dup

begin
Gelauto.setup
ARGV.replace(cmd)
load exe
ensure
Gelauto.teardown
ARGV.replace(old_argv)

if options[:annotate]
Gelauto.each_absolute_path do |path|
Gelauto.annotate_file(path)
Gelauto::Logger.info("Annotated #{path}")
end
end
end
end
end
end
end

exit Gelauto::CLI.run(ARGV)
13 changes: 13 additions & 0 deletions example/image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Image
attr_reader :path, :width, :height

def initialize(path, width, height)
@path = path
@width = width
@height = height
end

def aspect_ratio
width.to_f / height
end
end
13 changes: 13 additions & 0 deletions example/spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'gelauto/rspec'
require 'pry-byebug'

require_relative './image.rb'
Gelauto.files << File.join(__dir__, 'image.rb')

describe Image do
subject(:image) { described_class.new('foo.jpg', 800, 400) }

it 'calculates aspect ratio correctly' do
expect(image.aspect_ratio).to eq(2.0)
end
end
21 changes: 21 additions & 0 deletions gelauto.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
$:.unshift File.join(File.dirname(__FILE__), 'lib')
require 'gelauto/version'

Gem::Specification.new do |s|
s.name = 'gelauto'
s.version = ::Gelauto::VERSION
s.authors = ['Cameron Dutro']
s.email = ['[email protected]']
s.homepage = 'http://github.com/camertron/gelauto'

s.description = s.summary = 'Automatically annotate your code with Sorbet type definitions by running your code.'
s.platform = Gem::Platform::RUBY

s.add_dependency 'parser', '~> 2.6'
s.add_dependency 'gli', '~> 2.0'

s.executables << 'gelauto'

s.require_path = 'lib'
s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'README.md', 'Rakefile', 'gelauto.gemspec']
end
125 changes: 125 additions & 0 deletions lib/gelauto.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
require 'logger'
require 'parser/current'

module Gelauto
autoload :ArgList, 'gelauto/arg_list'
autoload :ArrayType, 'gelauto/array_type'
autoload :BooleanType, 'gelauto/boolean_type'
autoload :CLIUtils, 'gelauto/cli_utils'
autoload :GenericType, 'gelauto/generic_type'
autoload :HashType, 'gelauto/hash_type'
autoload :Logger, 'gelauto/logger'
autoload :MethodDef, 'gelauto/method_def'
autoload :MethodIndex, 'gelauto/method_index'
autoload :Type, 'gelauto/type'
autoload :TypeSet, 'gelauto/type_set'
autoload :Var, 'gelauto/var'

class << self
attr_accessor :paths
attr_writer :logger

def setup
enable_traces
index_methods
end

def teardown
disable_traces
end

def discover
setup
yield
ensure
teardown
end

def method_index
@method_index ||= MethodIndex.new
end

def paths
@paths ||= []
end

def each_absolute_path
return to_enum(__method__) unless block_given?

paths.each do |path|
yield File.expand_path(path)
end
end

def register_type(type, handler)
types[type] = handler
end

def types
@types ||= Hash.new(Gelauto::Type)
end

def introspect(obj)
Gelauto.types[obj.class].introspect(obj)
end

def annotate_file(path)
annotated_code = Gelauto.method_index.annotate(path, File.read(path))
File.write(path, annotated_code)
end

def logger
@logger ||= ::Logger.new(STDOUT)
end

private

def enable_traces
call_trace.enable
return_trace.enable
end

def disable_traces
call_trace.disable
return_trace.disable
end

def index_methods
each_absolute_path.with_index do |path, idx|
begin
method_index.index_methods_in(
path, Parser::CurrentRuby.parse(File.read(path))
)

Gelauto::Logger.info("Indexed #{idx + 1}/#{paths.size} paths")
rescue Parser::SyntaxError => e
Gelauto::Logger.error("Syntax error in #{path}, skipping")
end
end
end

def call_trace
@call_trace ||= TracePoint.new(:call) do |tp|
if md = method_index.find(tp.path, tp.lineno)
md.args.each do |arg|
var = tp.binding.local_variable_get(arg.name)
arg.types << Gelauto.introspect(var)
end
end
end
end

def return_trace
@return_trace ||= TracePoint.new(:return) do |tp|
if md = method_index.find(tp.path, tp.lineno)
md.return_types << Gelauto.introspect(tp.return_value)
end
end
end
end
end

Gelauto.register_type(::Hash, Gelauto::HashType)
Gelauto.register_type(::Array, Gelauto::ArrayType)
Gelauto.register_type(::TrueClass, Gelauto::BooleanType)
Gelauto.register_type(::FalseClass, Gelauto::BooleanType)
32 changes: 32 additions & 0 deletions lib/gelauto/arg_list.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Gelauto
class ArgList
include Enumerable

attr_reader :args

def initialize(args = [])
@args = args
end

def <<(arg)
args << arg
end

def [](idx)
args[idx]
end

def empty?
args.empty?
end

def each(&block)
args.each(&block)
end

def to_sig
return '' if args.empty?
args.map(&:to_sig).join(', ')
end
end
end
25 changes: 25 additions & 0 deletions lib/gelauto/array_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Gelauto
class ArrayType < GenericType
def self.introspect(obj)
new.tap do |var|
obj.each { |elem| var[:elem] << Gelauto.introspect(elem) }
end
end

def initialize
super(::Array, [:elem])
end

def to_sig
if self[:elem].empty?
'T::Array'
else
"T::Array[#{self[:elem].to_sig}]"
end
end

def merge!(other)
self[:elem].merge!(other[:elem])
end
end
end
7 changes: 7 additions & 0 deletions lib/gelauto/boolean_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Gelauto
class BooleanType < Type
def to_sig
'T::Boolean'
end
end
end
18 changes: 18 additions & 0 deletions lib/gelauto/cli_utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Gelauto
module CLIUtils
EXTS = (ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']).freeze

def which(cmd)
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
EXTS.each do |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
end
end

nil
end

extend self
end
end
24 changes: 24 additions & 0 deletions lib/gelauto/generic_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'set'

module Gelauto
class GenericType
attr_reader :ruby_type, :generic_args

def initialize(ruby_type, generic_arg_names = [])
@ruby_type = ruby_type
@generic_args = {}

generic_arg_names.each_with_object({}) do |generic_arg_name, ret|
generic_args[generic_arg_name] = TypeSet.new
end
end

def [](generic_arg_name)
generic_args[generic_arg_name]
end

def to_sig
raise NotImplementedError, "please define #{__method__} in derived classes"
end
end
end
Loading

0 comments on commit deb5aa0

Please sign in to comment.