Skip to content

Commit

Permalink
Merge pull request #9072 from htanata/statistics
Browse files Browse the repository at this point in the history
Improve `rake stats` for JavaScript and CoffeeScript
  • Loading branch information
dhh committed Feb 25, 2013
2 parents 4e286bf + 82e345d commit add8b51
Show file tree
Hide file tree
Showing 4 changed files with 394 additions and 46 deletions.
5 changes: 5 additions & 0 deletions railties/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##

* Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments
and calculates number of functions.

*Hendy Tanata*

* Ability to use a custom builder by passing `--builder` (or `-b`) has been removed. Consider
using application template instead. See this guide for more detail:
http://guides.rubyonrails.org/rails_application_templates.html
Expand Down
68 changes: 22 additions & 46 deletions railties/lib/rails/code_statistics.rb
@@ -1,3 +1,5 @@
require 'rails/code_statistics_calculator'

class CodeStatistics #:nodoc:

TEST_TYPES = ['Controller tests',
Expand Down Expand Up @@ -33,64 +35,38 @@ def calculate_statistics
end

def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/)
stats = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
stats = CodeStatisticsCalculator.new

Dir.foreach(directory) do |file_name|
if File.directory?(directory + "/" + file_name) and (/^\./ !~ file_name)
newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
stats.each { |k, v| stats[k] += newstats[k] }
path = "#{directory}/#{file_name}"

if File.directory?(path) && (/^\./ !~ file_name)
stats.add(calculate_directory_statistics(path, pattern))
end

next unless file_name =~ pattern

comment_started = false

case file_name
when /.*\.js$/
comment_pattern = /^\s*\/\//
else
comment_pattern = /^\s*#/
end

File.open(directory + "/" + file_name) do |f|
while line = f.gets
stats["lines"] += 1
if(comment_started)
if line =~ /^=end/
comment_started = false
end
next
else
if line =~ /^=begin/
comment_started = true
next
end
end
stats["classes"] += 1 if line =~ /^\s*class\s+[_A-Z]/
stats["methods"] += 1 if line =~ /^\s*def\s+[_a-z]/
stats["codelines"] += 1 unless line =~ /^\s*$/ || line =~ comment_pattern
end
end
stats.add_by_file_path(path)
end

stats
end

def calculate_total
total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
@statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
total
@statistics.each_with_object(CodeStatisticsCalculator.new) do |pair, total|
total.add(pair.last)
end
end

def calculate_code
code_loc = 0
@statistics.each { |k, v| code_loc += v['codelines'] unless TEST_TYPES.include? k }
@statistics.each { |k, v| code_loc += v.code_lines unless TEST_TYPES.include? k }
code_loc
end

def calculate_tests
test_loc = 0
@statistics.each { |k, v| test_loc += v['codelines'] if TEST_TYPES.include? k }
@statistics.each { |k, v| test_loc += v.code_lines if TEST_TYPES.include? k }
test_loc
end

Expand All @@ -105,15 +81,15 @@ def print_splitter
end

def print_line(name, statistics)
m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
loc_over_m = (statistics["codelines"] / statistics["methods"]) - 2 rescue loc_over_m = 0

puts "| #{name.ljust(20)} " +
"| #{statistics["lines"].to_s.rjust(5)} " +
"| #{statistics["codelines"].to_s.rjust(5)} " +
"| #{statistics["classes"].to_s.rjust(7)} " +
"| #{statistics["methods"].to_s.rjust(7)} " +
"| #{m_over_c.to_s.rjust(3)} " +
m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0
loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0

puts "| #{name.ljust(20)} " \
"| #{statistics.lines.to_s.rjust(5)} " \
"| #{statistics.code_lines.to_s.rjust(5)} " \
"| #{statistics.classes.to_s.rjust(7)} " \
"| #{statistics.methods.to_s.rjust(7)} " \
"| #{m_over_c.to_s.rjust(3)} " \
"| #{loc_over_m.to_s.rjust(5)} |"
end

Expand Down
79 changes: 79 additions & 0 deletions railties/lib/rails/code_statistics_calculator.rb
@@ -0,0 +1,79 @@
class CodeStatisticsCalculator #:nodoc:
attr_reader :lines, :code_lines, :classes, :methods

PATTERNS = {
rb: {
line_comment: /^\s*#/,
begin_block_comment: /^=begin/,
end_block_comment: /^=end/,
class: /^\s*class\s+[_A-Z]/,
method: /^\s*def\s+[_a-z]/,
},
js: {
line_comment: %r{^\s*//},
begin_block_comment: %r{^\s*/\*},
end_block_comment: %r{\*/},
method: /function(\s+[_a-zA-Z][\da-zA-Z]*)?\s*\(/,
},
coffee: {
line_comment: /^\s*#/,
begin_block_comment: /^\s*###/,
end_block_comment: /^\s*###/,
class: /^\s*class\s+[_A-Z]/,
method: /[-=]>/,
}
}

def initialize(lines = 0, code_lines = 0, classes = 0, methods = 0)
@lines = lines
@code_lines = code_lines
@classes = classes
@methods = methods
end

def add(code_statistics_calculator)
@lines += code_statistics_calculator.lines
@code_lines += code_statistics_calculator.code_lines
@classes += code_statistics_calculator.classes
@methods += code_statistics_calculator.methods
end

def add_by_file_path(file_path)
File.open(file_path) do |f|
self.add_by_io(f, file_type(file_path))
end
end

def add_by_io(io, file_type)
patterns = PATTERNS[file_type] || {}

comment_started = false

while line = io.gets
@lines += 1

if comment_started
if patterns[:end_block_comment] && line =~ patterns[:end_block_comment]
comment_started = false
end
next
else
if patterns[:begin_block_comment] && line =~ patterns[:begin_block_comment]
comment_started = true
next
end
end

@classes += 1 if patterns[:class] && line =~ patterns[:class]
@methods += 1 if patterns[:method] && line =~ patterns[:method]
if line !~ /^\s*$/ && (patterns[:line_comment].nil? || line !~ patterns[:line_comment])
@code_lines += 1
end
end
end

private
def file_type(file_path)
File.extname(file_path).sub(/\A\./, '').downcase.to_sym
end
end

0 comments on commit add8b51

Please sign in to comment.