Skip to content

Instantly share code, notes, and snippets.

@jbodah
Created January 7, 2016 18:48
Show Gist options
  • Save jbodah/3c77eded917a520bc0c4 to your computer and use it in GitHub Desktop.
Save jbodah/3c77eded917a520bc0c4 to your computer and use it in GitHub Desktop.
list undocumented overrides
require 'method_source'
require 'set'
def load_environment!
load 'config/environment.rb'
end
BASE_DIR = File.expand_path('../', Dir.pwd)
pre_objects = Set.new(ObjectSpace.each_object(Module))
load_environment!
require 'colorize'
Object.class_eval do
# Call from UnboundMethod or Method
def calls_super?
!!source[/super/]
rescue
true
end
def override_comment?
!!comment[/@override/]
rescue
true
end
def defined_in_backupify?
!!source_location[0][/^#{Regexp.escape(BASE_DIR)}/]
rescue
true
end
end
Module.class_eval do
def owned_instance_methods
instance_methods.select {|m| instance_method(m).owner == self}
end
end
# Format:
# Module => { method_symbol => overridden_implementations_array }
backupify_mod_to_missing_override_map = {}
ObjectSpace.each_object(Module).each do |mod|
begin
next if pre_objects.include?(mod) # Don't analyze base modules
ancestors = mod.ancestors
next if ancestors.none?
ancestor_imeth_map = (ancestors - [mod]).reduce({}) do |memo, anc|
anc.owned_instance_methods.each do |imeth|
memo[imeth] ||= []
memo[imeth] << anc
end
memo
end
# Array of method_symbols
missing_overrides = []
mod.owned_instance_methods.each do |imeth|
next if ancestor_imeth_map[imeth].nil?
m = mod.instance_method(imeth)
#next if m.calls_super?
next if m.override_comment?
next if !m.defined_in_backupify?
missing_overrides << imeth
end
next if missing_overrides.empty?
backupify_mod_to_missing_override_map[mod] = missing_overrides.reduce({}) do |memo, sym|
memo[sym] = ancestor_imeth_map[sym]
memo
end
puts mod.name
puts missing_overrides.map {|m| "\t- ##{m.to_s.green} overrides #{ancestor_imeth_map[m].map(&:name).map(&:yellow).join(', ')}"}.join("\n")
puts
rescue => e
puts "#{mod}: #{e.to_s}".red
next
end
end
if ENV['AUTOADD_DOC_LINE']
def add_override_comment(file, lines, def_line_idx)
curr = def_line_idx
until curr == 0 || !lines[curr - 1][/^\s*#{Regexp.escape('#')}/] # until previous line is not a comment
curr -= 1
end
indent = lines[curr][/^\s*/]
is_adding_to_existing_comment = !!lines[curr][/^\s*#/]
lines.insert(curr, indent + "#\n") if is_adding_to_existing_comment # add extra comment line if was comment
lines.insert(curr, indent + "# @override\n")
File.open(file, 'w') {|f| f.write lines.join}
end
backupify_mod_to_missing_override_map.each do |mod, missing_overrides|
begin
missing_overrides.keys.each do |sym|
m = mod.instance_method(sym)
next if m.override_comment? # skip if we already wound up adding an override comment (e.g. dynamic getter/setter definitions)
file_defined_in, source_line = m.source_location
source_line = source_line - 1 # Deal with 0-indexing
raise "#{file_defined_in} not in #{BASE_DIR}" unless file_defined_in[/^#{Regexp.escape(BASE_DIR)}/]
lines = File.read(file_defined_in).each_line.to_a
puts "Sanity check: #{lines[source_line]}".green # sanity check: should be def line
raise "Couldn't find method definition: #{lines[source_line]}" unless lines[source_line][/(def |define|attr)/]
unless ENV['DRY_RUN']
add_override_comment(file_defined_in, lines, source_line)
# Reload the file so we can refesh Method#source_location
load file_defined_in
end
end
rescue => e
puts "#{mod}: #{e.to_s}".red
next
end
end
end
vcount = backupify_mod_to_missing_override_map.map {|mod, missing_overrides| missing_overrides.count}.reduce(:+) || 0
puts "Violations found: #{vcount}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment