Last active
October 5, 2016 11:48
-
-
Save scotchi/29ee874a4416029f23519e5af1e5f4e1 to your computer and use it in GitHub Desktop.
Tool to parse out params in usage in a Rails controller and add a line to specify which strong params are allowed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'parser/current' | |
module Parser | |
module AST | |
class Node | |
def find(type, found: [], &block) | |
if self.type == type && (!block || block.call(self)) | |
found.push(self) | |
else | |
children.each { |c| c.find(type, found: found, &block) if c.is_a?(self.class) } | |
end | |
found | |
end | |
def name | |
case type | |
when :class | |
children[0].children[1] | |
when :def, :sym | |
children[0] | |
when :send | |
children[1] | |
end | |
end | |
def function?(name = nil) | |
type == :send && (!name || self.name == name) | |
end | |
def target | |
type == :send && children[0] | |
end | |
def arguments | |
type == :send && children.drop(2) | |
end | |
end | |
end | |
end | |
def params_in_method(method, ignore: []) | |
first_children_of_or = method.find(:or).map { |c| c.children.first } | |
params = method.find(:send) do |f| | |
f.name == :[] && f.target.function?(:params) | |
end | |
required = Set.new | |
permitted = Set.new | |
params.each do |p| | |
next unless p.arguments.size == 1 && p.arguments.first.type == :sym | |
name = p.arguments.first.name | |
next if ignore.include?(name) | |
if first_children_of_or.include?(p) | |
permitted.add(name) unless required.include?(name) | |
else | |
required.add(name) | |
permitted.delete(name) | |
end | |
end | |
{ :required => required.to_a, :permitted => permitted.to_a } | |
end | |
def method_has_params_statement?(method) | |
method.find(:send) do |f| | |
f.target&.name == :params && [ :require, :permit ].include?(f.name) | |
end.empty? | |
end | |
def params_statement(params) | |
required = params[:required] | |
permitted = params[:permitted] | |
return '' if required.empty? && permitted.empty? | |
statement = 'params' | |
statement += ".require(#{required.map(&:inspect).join(', ')})" unless required.empty? | |
statement += ".permit(#{permitted.map(&:inspect).join(', ')})" unless permitted.empty? | |
statement | |
end | |
def write_params_statements(file, statements) | |
code = File.read(file) | |
statements.each do |method, statement| | |
escaped = Regexp.escape(method) | |
code.sub!(/(([\t ]*)def\s+#{escaped}([\( ]|$).*\n)/, "\\1\\2 #{statement}\n") | |
end | |
File.write(file, code) | |
end | |
def controller_class(controller) | |
controller.sub(/.*\//, '').split('_').map(&:capitalize).join + 'Controller' | |
end | |
def route_params(dir) | |
routes = Dir.chdir(dir) do | |
`rake routes 2>&1`.split("\n").drop(2).map { |l| l.sub(/.*?\//, '').split(/\s+/, 2) } | |
end | |
routes.select! { |_, controller| controller.include?('#') } | |
params = {} | |
routes.each do |route, controller| | |
name, method = controller.split('#') | |
klass = controller_class(name) | |
params[klass] ||= {} | |
params[klass][method.to_sym] = route.scan(/:\w+/).map { |s| s.sub(/^:/, '').to_sym } | |
end | |
params | |
end | |
def process(file) | |
default_route_params = route_params(File.dirname(file)) | |
tree = Parser::CurrentRuby.parse(File.read(file)) | |
tree.find(:class).each do |klass| | |
methods = klass.find(:def).select { |m| method_has_params_statement?(m) } | |
statements = methods.map do |method| | |
ignored = default_route_params.dig(klass.name.to_s)&.dig(method.name) || [] | |
statement = params_statement(params_in_method(method, ignore: ignored)) | |
puts "#{klass.name}##{method.name}\n #{statement}" unless statement.empty? | |
[ method.name, statement ] | |
end.to_h.reject { |_, v| v.empty? } | |
write_params_statements(file, statements) | |
end | |
end | |
ARGV.each { |f| process(f) } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment