diff --git a/lib/linguist/blob_helper.rb b/lib/linguist/blob_helper.rb index 369a46ce..a53d4a68 100644 --- a/lib/linguist/blob_helper.rb +++ b/lib/linguist/blob_helper.rb @@ -150,6 +150,8 @@ module Linguist # Generated source code is supressed in diffs and is ignored by # langauge statistics. # + # Requires Blob#data + # # Includes: # - XCode project XML files # - Minified JavaScript @@ -158,6 +160,8 @@ module Linguist def generated? if ['.xib', '.nib', '.pbxproj'].include?(pathname.extname) true + elsif generated_coffeescript? + true elsif pathname.extname == '.js' # JS is minified if any lines are longer than 1000c lines.any? { |l| l.length > 1000 } @@ -166,6 +170,42 @@ module Linguist end end + # Internal: Is the blob JS generated by CoffeeScript? + # + # Requires Blob#data + # + # CoffeScript is meant to output JS that would be difficult to + # tell if it was generated or not. Look for a number of patterns + # outputed by the CS compiler. + # + # Return true or false + def generated_coffeescript? + return unless pathname.extname == '.js' + + if lines[0] == '(function() {' && # First line is module closure opening + lines[-2] == '}).call(this);' && # Second to last line closes module closure + lines[-1] == '' # Last line is blank + + score = 0 + + lines.each do |line| + if line =~ /var / + # Underscored temp vars are likely to be Coffee + score += 1 * line.gsub(/(_fn|_i|_len|_ref|_results)/).count + + # bind and extend functions are very Coffee specific + score += 3 * line.gsub(/(__bind|__extends|__hasProp|__indexOf|__slice)/).count + end + end + + # Require a score of 3. This is fairly arbitrary. Consider + # tweaking later. + score >= 3 + else + false + end + end + # Public: Should the blob be indexed for searching? # # Excluded: diff --git a/test/fixtures/blob/coffee/classes.coffee b/test/fixtures/blob/coffee/classes.coffee new file mode 100644 index 00000000..2b2a718c --- /dev/null +++ b/test/fixtures/blob/coffee/classes.coffee @@ -0,0 +1,21 @@ +class Animal + constructor: (@name) -> + + move: (meters) -> + alert @name + " moved " + meters + "m." + +class Snake extends Animal + move: -> + alert "Slithering..." + super 5 + +class Horse extends Animal + move: -> + alert "Galloping..." + super 45 + +sam = new Snake "Sammy the Python" +tom = new Horse "Tommy the Palomino" + +sam.move() +tom.move() diff --git a/test/fixtures/blob/coffee/classes.js b/test/fixtures/blob/coffee/classes.js new file mode 100644 index 00000000..2e4353ff --- /dev/null +++ b/test/fixtures/blob/coffee/classes.js @@ -0,0 +1,46 @@ +(function() { + var Animal, Horse, Snake, sam, tom; + var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { + for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor; + child.__super__ = parent.prototype; + return child; + }; + Animal = (function() { + function Animal(name) { + this.name = name; + } + Animal.prototype.move = function(meters) { + return alert(this.name + " moved " + meters + "m."); + }; + return Animal; + })(); + Snake = (function() { + __extends(Snake, Animal); + function Snake() { + Snake.__super__.constructor.apply(this, arguments); + } + Snake.prototype.move = function() { + alert("Slithering..."); + return Snake.__super__.move.call(this, 5); + }; + return Snake; + })(); + Horse = (function() { + __extends(Horse, Animal); + function Horse() { + Horse.__super__.constructor.apply(this, arguments); + } + Horse.prototype.move = function() { + alert("Galloping..."); + return Horse.__super__.move.call(this, 45); + }; + return Horse; + })(); + sam = new Snake("Sammy the Python"); + tom = new Horse("Tommy the Palomino"); + sam.move(); + tom.move(); +}).call(this); diff --git a/test/fixtures/blob/coffee/empty.coffee b/test/fixtures/blob/coffee/empty.coffee new file mode 100644 index 00000000..e69de29b diff --git a/test/fixtures/blob/coffee/empty.js b/test/fixtures/blob/coffee/empty.js new file mode 100644 index 00000000..f5e757a8 --- /dev/null +++ b/test/fixtures/blob/coffee/empty.js @@ -0,0 +1,3 @@ +(function() { + +}).call(this); diff --git a/test/fixtures/blob/coffee/hello.coffee b/test/fixtures/blob/coffee/hello.coffee new file mode 100644 index 00000000..4b65a573 --- /dev/null +++ b/test/fixtures/blob/coffee/hello.coffee @@ -0,0 +1 @@ +console.log "Hello, World!" diff --git a/test/fixtures/blob/coffee/hello.js b/test/fixtures/blob/coffee/hello.js new file mode 100644 index 00000000..c1c58ec2 --- /dev/null +++ b/test/fixtures/blob/coffee/hello.js @@ -0,0 +1,3 @@ +(function() { + console.log("Hello, World!"); +}).call(this); diff --git a/test/fixtures/blob/coffee/intro.coffee b/test/fixtures/blob/coffee/intro.coffee new file mode 100644 index 00000000..0db65dd2 --- /dev/null +++ b/test/fixtures/blob/coffee/intro.coffee @@ -0,0 +1,28 @@ +# Assignment: +number = 42 +opposite = true + +# Conditions: +number = -42 if opposite + +# Functions: +square = (x) -> x * x + +# Arrays: +list = [1, 2, 3, 4, 5] + +# Objects: +math = + root: Math.sqrt + square: square + cube: (x) -> x * square x + +# Splats: +race = (winner, runners...) -> + print winner, runners + +# Existence: +alert "I knew it!" if elvis? + +# Array comprehensions: +cubes = (math.cube num for num in list) diff --git a/test/fixtures/blob/coffee/intro.js b/test/fixtures/blob/coffee/intro.js new file mode 100644 index 00000000..f774102f --- /dev/null +++ b/test/fixtures/blob/coffee/intro.js @@ -0,0 +1,37 @@ +(function() { + var cubes, list, math, num, number, opposite, race, square; + var __slice = Array.prototype.slice; + number = 42; + opposite = true; + if (opposite) { + number = -42; + } + square = function(x) { + return x * x; + }; + list = [1, 2, 3, 4, 5]; + math = { + root: Math.sqrt, + square: square, + cube: function(x) { + return x * square(x); + } + }; + race = function() { + var runners, winner; + winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return print(winner, runners); + }; + if (typeof elvis !== "undefined" && elvis !== null) { + alert("I knew it!"); + } + cubes = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + num = list[_i]; + _results.push(math.cube(num)); + } + return _results; + })(); +}).call(this); diff --git a/test/test_blob.rb b/test/test_blob.rb index ce6a5c5f..e5ebb80b 100644 --- a/test/test_blob.rb +++ b/test/test_blob.rb @@ -116,6 +116,15 @@ class TestBlob < Test::Unit::TestCase # Minified JS assert !blob("jquery-1.6.1.js").generated? assert blob("jquery-1.6.1.min.js").generated? + + # CoffeScript JS + + # These examples are to basic to tell + assert !blob("coffee/empty.js").generated? + assert !blob("coffee/hello.js").generated? + + assert blob("coffee/intro.js").generated? + assert blob("coffee/classes.js").generated? end def test_vendored