mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Improve rake stats
for JavaScript and CoffeeScript.
Ignore block comments and calculates number of functions.
This commit is contained in:
parent
15d693df93
commit
82e345dd7a
4 changed files with 393 additions and 45 deletions
|
@ -1,5 +1,10 @@
|
||||||
## Rails 4.0.0 (unreleased) ##
|
## 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
|
* 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:
|
using application template instead. See this guide for more detail:
|
||||||
http://guides.rubyonrails.org/rails_application_templates.html
|
http://guides.rubyonrails.org/rails_application_templates.html
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require 'rails/code_statistics_calculator'
|
||||||
|
|
||||||
class CodeStatistics #:nodoc:
|
class CodeStatistics #:nodoc:
|
||||||
|
|
||||||
TEST_TYPES = ['Controller tests',
|
TEST_TYPES = ['Controller tests',
|
||||||
|
@ -33,64 +35,38 @@ class CodeStatistics #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee)$/)
|
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|
|
Dir.foreach(directory) do |file_name|
|
||||||
if File.directory?(directory + "/" + file_name) and (/^\./ !~ file_name)
|
path = "#{directory}/#{file_name}"
|
||||||
newstats = calculate_directory_statistics(directory + "/" + file_name, pattern)
|
|
||||||
stats.each { |k, v| stats[k] += newstats[k] }
|
if File.directory?(path) && (/^\./ !~ file_name)
|
||||||
|
stats.add(calculate_directory_statistics(path, pattern))
|
||||||
end
|
end
|
||||||
|
|
||||||
next unless file_name =~ pattern
|
next unless file_name =~ pattern
|
||||||
|
|
||||||
comment_started = false
|
stats.add_by_file_path(path)
|
||||||
|
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
stats
|
stats
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_total
|
def calculate_total
|
||||||
total = { "lines" => 0, "codelines" => 0, "classes" => 0, "methods" => 0 }
|
@statistics.each_with_object(CodeStatisticsCalculator.new) do |pair, total|
|
||||||
@statistics.each_value { |pair| pair.each { |k, v| total[k] += v } }
|
total.add(pair.last)
|
||||||
total
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_code
|
def calculate_code
|
||||||
code_loc = 0
|
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
|
code_loc
|
||||||
end
|
end
|
||||||
|
|
||||||
def calculate_tests
|
def calculate_tests
|
||||||
test_loc = 0
|
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
|
test_loc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -105,15 +81,15 @@ class CodeStatistics #:nodoc:
|
||||||
end
|
end
|
||||||
|
|
||||||
def print_line(name, statistics)
|
def print_line(name, statistics)
|
||||||
m_over_c = (statistics["methods"] / statistics["classes"]) rescue m_over_c = 0
|
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
|
loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0
|
||||||
|
|
||||||
puts "| #{name.ljust(20)} " +
|
puts "| #{name.ljust(20)} " \
|
||||||
"| #{statistics["lines"].to_s.rjust(5)} " +
|
"| #{statistics.lines.to_s.rjust(5)} " \
|
||||||
"| #{statistics["codelines"].to_s.rjust(5)} " +
|
"| #{statistics.code_lines.to_s.rjust(5)} " \
|
||||||
"| #{statistics["classes"].to_s.rjust(7)} " +
|
"| #{statistics.classes.to_s.rjust(7)} " \
|
||||||
"| #{statistics["methods"].to_s.rjust(7)} " +
|
"| #{statistics.methods.to_s.rjust(7)} " \
|
||||||
"| #{m_over_c.to_s.rjust(3)} " +
|
"| #{m_over_c.to_s.rjust(3)} " \
|
||||||
"| #{loc_over_m.to_s.rjust(5)} |"
|
"| #{loc_over_m.to_s.rjust(5)} |"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
79
railties/lib/rails/code_statistics_calculator.rb
Normal file
79
railties/lib/rails/code_statistics_calculator.rb
Normal file
|
@ -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
|
288
railties/test/code_statistics_calculator_test.rb
Normal file
288
railties/test/code_statistics_calculator_test.rb
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
require 'abstract_unit'
|
||||||
|
require 'rails/code_statistics_calculator'
|
||||||
|
|
||||||
|
class CodeStatisticsCalculatorTest < ActiveSupport::TestCase
|
||||||
|
def setup
|
||||||
|
@code_statistics_calculator = CodeStatisticsCalculator.new
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'add statistics to another using #add' do
|
||||||
|
code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4)
|
||||||
|
@code_statistics_calculator.add(code_statistics_calculator_1)
|
||||||
|
|
||||||
|
assert_equal 1, @code_statistics_calculator.lines
|
||||||
|
assert_equal 2, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 3, @code_statistics_calculator.classes
|
||||||
|
assert_equal 4, @code_statistics_calculator.methods
|
||||||
|
|
||||||
|
code_statistics_calculator_2 = CodeStatisticsCalculator.new(2, 3, 4, 5)
|
||||||
|
@code_statistics_calculator.add(code_statistics_calculator_2)
|
||||||
|
|
||||||
|
assert_equal 3, @code_statistics_calculator.lines
|
||||||
|
assert_equal 5, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 7, @code_statistics_calculator.classes
|
||||||
|
assert_equal 9, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'accumulate statistics using #add_by_io' do
|
||||||
|
code_statistics_calculator_1 = CodeStatisticsCalculator.new(1, 2, 3, 4)
|
||||||
|
@code_statistics_calculator.add(code_statistics_calculator_1)
|
||||||
|
|
||||||
|
code = <<-'CODE'
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar; end
|
||||||
|
class A; end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
|
||||||
|
|
||||||
|
assert_equal 7, @code_statistics_calculator.lines
|
||||||
|
assert_equal 7, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 4, @code_statistics_calculator.classes
|
||||||
|
assert_equal 6, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate statistics using #add_by_file_path' do
|
||||||
|
tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
|
||||||
|
FileUtils.mkdir_p(tmp_path)
|
||||||
|
|
||||||
|
code = <<-'CODE'
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
# bar
|
||||||
|
end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
file_path = "#{tmp_path}/stats.rb"
|
||||||
|
File.open(file_path, 'w') { |f| f.write(code) }
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_file_path(file_path)
|
||||||
|
|
||||||
|
assert_equal 4, @code_statistics_calculator.lines
|
||||||
|
assert_equal 3, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.classes
|
||||||
|
assert_equal 1, @code_statistics_calculator.methods
|
||||||
|
|
||||||
|
FileUtils.rm_rf(tmp_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate number of Ruby methods' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
end
|
||||||
|
|
||||||
|
def bar; end
|
||||||
|
|
||||||
|
class Foo
|
||||||
|
def bar(abc)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
|
||||||
|
|
||||||
|
assert_equal 3, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate Ruby LOCs' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
end
|
||||||
|
|
||||||
|
# def bar; end
|
||||||
|
|
||||||
|
class A < B
|
||||||
|
end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
|
||||||
|
|
||||||
|
assert_equal 8, @code_statistics_calculator.lines
|
||||||
|
assert_equal 5, @code_statistics_calculator.code_lines
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate number of Ruby classes' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
class Foo < Bar
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Z; end
|
||||||
|
|
||||||
|
# class A
|
||||||
|
# end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
|
||||||
|
|
||||||
|
assert_equal 2, @code_statistics_calculator.classes
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'skip Ruby comments' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
=begin
|
||||||
|
class Foo
|
||||||
|
def foo
|
||||||
|
puts 'foo'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
=end
|
||||||
|
|
||||||
|
# class A
|
||||||
|
# end
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :rb)
|
||||||
|
|
||||||
|
assert_equal 10, @code_statistics_calculator.lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.classes
|
||||||
|
assert_equal 0, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate number of JS methods' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
function foo(x, y, z) {
|
||||||
|
doX();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
bar();
|
||||||
|
})
|
||||||
|
|
||||||
|
var baz = function ( x ) {
|
||||||
|
}
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :js)
|
||||||
|
|
||||||
|
assert_equal 3, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate JS LOCs' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
function foo()
|
||||||
|
alert('foo');
|
||||||
|
end
|
||||||
|
|
||||||
|
// var b = 2;
|
||||||
|
|
||||||
|
var a = 1;
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :js)
|
||||||
|
|
||||||
|
assert_equal 7, @code_statistics_calculator.lines
|
||||||
|
assert_equal 4, @code_statistics_calculator.code_lines
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'skip JS comments' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
/*
|
||||||
|
* var f = function () {
|
||||||
|
1 / 2;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// call();
|
||||||
|
//
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :js)
|
||||||
|
|
||||||
|
assert_equal 8, @code_statistics_calculator.lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.classes
|
||||||
|
assert_equal 0, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate number of CoffeeScript methods' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
square = (x) -> x * x
|
||||||
|
|
||||||
|
math =
|
||||||
|
cube: (x) -> x * square x
|
||||||
|
|
||||||
|
fill = (container, liquid = "coffee") ->
|
||||||
|
"Filling the #{container} with #{liquid}..."
|
||||||
|
|
||||||
|
$('.shopping_cart').bind 'click', (event) =>
|
||||||
|
@customer.purchase @cart
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
|
||||||
|
|
||||||
|
assert_equal 4, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate CoffeeScript LOCs' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
# Assignment:
|
||||||
|
number = 42
|
||||||
|
opposite = true
|
||||||
|
|
||||||
|
###
|
||||||
|
CoffeeScript Compiler v1.4.0
|
||||||
|
Released under the MIT License
|
||||||
|
###
|
||||||
|
|
||||||
|
# Conditions:
|
||||||
|
number = -42 if opposite
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
|
||||||
|
|
||||||
|
assert_equal 11, @code_statistics_calculator.lines
|
||||||
|
assert_equal 3, @code_statistics_calculator.code_lines
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'calculate number of CoffeeScript classes' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
class Animal
|
||||||
|
constructor: (@name) ->
|
||||||
|
|
||||||
|
move: (meters) ->
|
||||||
|
alert @name + " moved #{meters}m."
|
||||||
|
|
||||||
|
class Snake extends Animal
|
||||||
|
move: ->
|
||||||
|
alert "Slithering..."
|
||||||
|
super 5
|
||||||
|
|
||||||
|
# class Horse
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
|
||||||
|
|
||||||
|
assert_equal 2, @code_statistics_calculator.classes
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'skip CoffeeScript comments' do
|
||||||
|
code = <<-'CODE'
|
||||||
|
###
|
||||||
|
class Animal
|
||||||
|
constructor: (@name) ->
|
||||||
|
|
||||||
|
move: (meters) ->
|
||||||
|
alert @name + " moved #{meters}m."
|
||||||
|
###
|
||||||
|
|
||||||
|
# class Horse
|
||||||
|
alert 'hello'
|
||||||
|
CODE
|
||||||
|
|
||||||
|
@code_statistics_calculator.add_by_io(StringIO.new(code), :coffee)
|
||||||
|
|
||||||
|
assert_equal 10, @code_statistics_calculator.lines
|
||||||
|
assert_equal 1, @code_statistics_calculator.code_lines
|
||||||
|
assert_equal 0, @code_statistics_calculator.classes
|
||||||
|
assert_equal 0, @code_statistics_calculator.methods
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue