jsinstrument 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +20 -0
- data/Rakefile +1 -0
- data/jsinstrument.gemspec +24 -0
- data/lib/jsinstrument.rb +2 -0
- data/lib/jsinstrument/inserting.js +135 -0
- data/lib/jsinstrument/instrumenter.rb +133 -0
- data/lib/jsinstrument/version.rb +3 -0
- data/lib/rkelly/node.rb +14 -0
- data/lib/rkelly/parent_builder.rb +120 -0
- metadata +106 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Mushan Yang
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# JSInstrument
|
2
|
+
|
3
|
+
Instrument JavaScript source code to monitor code coverage in pure Ruby
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'jsinstrument'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install jsinstrument
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jsinstrument/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "jsinstrument"
|
8
|
+
spec.version = JSInstrument::VERSION
|
9
|
+
spec.authors = ["Mushan Yang"]
|
10
|
+
spec.email = ["mushan.yang@hulu.com"]
|
11
|
+
spec.description = %q{Instrument JavaScript source code to monitor code coverage in pure Ruby}
|
12
|
+
spec.summary = %q{Instrument JavaScript source code to monitor code coverage in pure Ruby}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "rkelly", "~>1.0.7"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
end
|
data/lib/jsinstrument.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
/* automatically generated by JSInstrument */
|
2
|
+
/*
|
3
|
+
* create __coverage__ object as a global variable
|
4
|
+
*/
|
5
|
+
!function(root){
|
6
|
+
if (root && (typeof root.%{js_object_name} === 'undefined')) {
|
7
|
+
root.%{js_object_name} = {
|
8
|
+
/**
|
9
|
+
* Coverage data
|
10
|
+
*/
|
11
|
+
_data: {},
|
12
|
+
/**
|
13
|
+
* A thread is uploading data
|
14
|
+
*/
|
15
|
+
_uploading: false,
|
16
|
+
/**
|
17
|
+
* mark code line
|
18
|
+
*/
|
19
|
+
mark: function(file, lines) {
|
20
|
+
if(!this._data[file]) this._data[file] = [];
|
21
|
+
for (var i in lines) {
|
22
|
+
if(!this._data[file][lines[i]]) {
|
23
|
+
this._data[file][lines[i]] = 0;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
},
|
27
|
+
/**
|
28
|
+
* Hit a line
|
29
|
+
*/
|
30
|
+
hit: function(file, line) {
|
31
|
+
if(!this._data[file]) this._data[file] = [];
|
32
|
+
|
33
|
+
if(!this._data[file][line]) this._data[file][line] = 1;
|
34
|
+
else this._data[file][line] = this._data[file][line] + 1;
|
35
|
+
},
|
36
|
+
/**
|
37
|
+
* Push data to server
|
38
|
+
*/
|
39
|
+
upload: function(force, mark) {
|
40
|
+
/**
|
41
|
+
* Avoid dup upload
|
42
|
+
*/
|
43
|
+
if(!force && this._uploading) return;
|
44
|
+
|
45
|
+
this._uploading = true;
|
46
|
+
|
47
|
+
try {
|
48
|
+
$.post('%{upload_url}', this._serialize(this._data, mark)).success(function() {
|
49
|
+
for (var file in _$jscoverage._data) {
|
50
|
+
var length = _$jscoverage._data[file].length;
|
51
|
+
for (var line = 0; line < length; line++) {
|
52
|
+
var value = _$jscoverage._data[file][line];
|
53
|
+
if (value === undefined || value === null) continue;
|
54
|
+
_$jscoverage._data[file][line] = 0;
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}).complete(function() {
|
58
|
+
_$jscoverage._uploading = false;
|
59
|
+
});
|
60
|
+
} catch (e) {
|
61
|
+
this._uploading = false;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
/**
|
65
|
+
* serialize data for data uploading
|
66
|
+
*/
|
67
|
+
_serialize: function(data, mark) {
|
68
|
+
var json = [];
|
69
|
+
|
70
|
+
for (var file in data) {
|
71
|
+
var coverage = data[file];
|
72
|
+
|
73
|
+
var array = [];
|
74
|
+
var length = coverage.length;
|
75
|
+
for (var line = 0; line < length; line++) {
|
76
|
+
var value = coverage[line];
|
77
|
+
if (value === undefined || value === null) {
|
78
|
+
value = '';
|
79
|
+
}
|
80
|
+
array.push(value);
|
81
|
+
}
|
82
|
+
|
83
|
+
json.push(this._quote(file) + ':[' + array.join(',') + ']');
|
84
|
+
}
|
85
|
+
|
86
|
+
var result = '{' + json.join(',') + '}';
|
87
|
+
|
88
|
+
var r = {
|
89
|
+
project: '%{project_name}',
|
90
|
+
commit: '%{commit_hash}',
|
91
|
+
batch: '%{batch_text}',
|
92
|
+
mark: mark,
|
93
|
+
data: result
|
94
|
+
}
|
95
|
+
|
96
|
+
var data_compress_method = %{data_compress_method};
|
97
|
+
|
98
|
+
if (data_compress_method) {
|
99
|
+
var result_deflated = data_compress_method(result);
|
100
|
+
var result_encoded = btoa(result_deflated);
|
101
|
+
r.data_compressed = true;
|
102
|
+
r.data = result_encoded;
|
103
|
+
}
|
104
|
+
|
105
|
+
return r;
|
106
|
+
},
|
107
|
+
_quote: function(s) {
|
108
|
+
return '"' + s.replace(/[\u0000-\u001f"\\\u007f-\uffff]/g, function (c) {
|
109
|
+
switch (c) {
|
110
|
+
case '\b': return '\\b';
|
111
|
+
case '\f': return '\\f';
|
112
|
+
case '\n': return '\\n';
|
113
|
+
case '\r': return '\\r';
|
114
|
+
case '\t': return '\\t';
|
115
|
+
case '"': return '\\"';
|
116
|
+
case '\\': return '\\\\';
|
117
|
+
default: return '\\u' + this._pad(c.charCodeAt(0).toString(16));
|
118
|
+
}
|
119
|
+
}) + '"';
|
120
|
+
},
|
121
|
+
_pad: function(s) {
|
122
|
+
return '0000'.substr(s.length) + s;
|
123
|
+
},
|
124
|
+
};
|
125
|
+
|
126
|
+
// $(document).ready(function() {
|
127
|
+
// $(window).unload(function (e) {
|
128
|
+
// _$jscoverage.upload(true);
|
129
|
+
// });
|
130
|
+
// });
|
131
|
+
}
|
132
|
+
|
133
|
+
root.%{js_object_name}.mark('%{src_filename}', %{instrumented_lines});
|
134
|
+
}(this);
|
135
|
+
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'rkelly'
|
3
|
+
require 'rkelly/node'
|
4
|
+
require 'rkelly/parent_builder'
|
5
|
+
include RKelly::Nodes
|
6
|
+
|
7
|
+
module JSInstrument
|
8
|
+
class Instrumenter
|
9
|
+
|
10
|
+
attr_accessor :js_object_name, :upload_url, :project_name, :commit_hash, :batch_text, :data_compress_method
|
11
|
+
|
12
|
+
def js_object_name
|
13
|
+
@js_object_name || '__coverage__'
|
14
|
+
end
|
15
|
+
|
16
|
+
def instrument src, filename
|
17
|
+
raise 'Filename not set' unless filename
|
18
|
+
ast = RKelly::Parser.new.parse src
|
19
|
+
raise 'Parse source failed.' unless ast
|
20
|
+
ast, instrumented_lines = instrument_ast ast
|
21
|
+
instrumented_src = ast.to_ecma
|
22
|
+
|
23
|
+
inserting = File.open(File.dirname(__FILE__) + '/inserting.js').read
|
24
|
+
|
25
|
+
bindings = {
|
26
|
+
js_object_name: self.js_object_name,
|
27
|
+
upload_url: self.upload_url,
|
28
|
+
src_filename: self.filename,
|
29
|
+
project_name: self.project_name,
|
30
|
+
commit_hash: self.commit_hash,
|
31
|
+
batch_text: self.batch_text,
|
32
|
+
data_compress_method: self.data_compress_method,
|
33
|
+
instrumented_lines: instrumented_lines
|
34
|
+
}
|
35
|
+
|
36
|
+
(inserting % bindings) + instrumented_src
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def instrument_ast ast
|
41
|
+
# build parent attr for all nodes in ast
|
42
|
+
#
|
43
|
+
ast = RKelly::Visitors::ParentBuilder.new.build ast
|
44
|
+
instrumented_lines = Set.new
|
45
|
+
ast.each do |node|
|
46
|
+
parent = node.parent
|
47
|
+
line = node.line
|
48
|
+
# instrument only if we can get the line no. and we haven't instrumented it
|
49
|
+
#
|
50
|
+
if line and not instrumented_lines.member? line
|
51
|
+
if ExpressionStatementNode === node or EmptyStatementNode === node or
|
52
|
+
DoWhileNode === node or WhileNode === node or ForNode === node or
|
53
|
+
ForInNode === node or ContinueNode === node or VarDeclNode === node or
|
54
|
+
SwitchNode === node or BreakNode === node or ThrowNode === node or
|
55
|
+
ReturnNode === node or IfNode === node or VarStatementNode === node
|
56
|
+
|
57
|
+
if DoWhileNode === parent
|
58
|
+
if parent.left.eql? node
|
59
|
+
instrumented_lines.add line
|
60
|
+
parent.left = get_inserted_block node
|
61
|
+
end
|
62
|
+
elsif IfNode === parent or WhileNode === parent or
|
63
|
+
ForNode === parent or ForInNode === parent or WithNode === parent
|
64
|
+
if parent.value.eql? node
|
65
|
+
instrumented_lines.add line
|
66
|
+
parent.value = get_inserted_block node
|
67
|
+
elsif parent.else.eql? node
|
68
|
+
instrumented_lines.add line
|
69
|
+
parent.else = get_inserted_block node
|
70
|
+
end
|
71
|
+
elsif CaseBlockNode === parent or LabelNode === parent
|
72
|
+
# do nothing here
|
73
|
+
elsif SourceElementsNode === parent
|
74
|
+
if insert_func_before node
|
75
|
+
instrumented_lines.add line
|
76
|
+
end
|
77
|
+
end
|
78
|
+
elsif WithNode === node or TryNode === node
|
79
|
+
if SourceElementsNode === parent
|
80
|
+
if insert_func_before node
|
81
|
+
instrumented_lines.add line
|
82
|
+
end
|
83
|
+
end
|
84
|
+
elsif FunctionDeclNode === node or FunctionExprNode === node
|
85
|
+
if insert_func_before node
|
86
|
+
instrumented_lines.add line
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
[ast, instrumented_lines.sort]
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_insert_func_call line
|
95
|
+
ExpressionStatementNode.new(
|
96
|
+
FunctionCallNode.new(
|
97
|
+
ResolveNode.new("#{self.js_object_name}.hit"),
|
98
|
+
ArgumentsNode.new([
|
99
|
+
StringNode.new("\"#{self.src_filename}\""),
|
100
|
+
NumberNode.new(line)
|
101
|
+
])
|
102
|
+
)
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def get_inserted_block node
|
107
|
+
BlockNode.new(
|
108
|
+
SourceElementsNode.new([
|
109
|
+
get_insert_func_call(node.line),
|
110
|
+
node
|
111
|
+
])
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def insert_func_before node
|
116
|
+
# get the original node line
|
117
|
+
#
|
118
|
+
line = node.line
|
119
|
+
# backtrack until we can insert the func
|
120
|
+
#
|
121
|
+
while not SourceElementsNode === node.parent and node.parent != nil
|
122
|
+
node = node.parent
|
123
|
+
end
|
124
|
+
if node.parent == nil
|
125
|
+
false
|
126
|
+
else
|
127
|
+
index = node.parent.value.index{|x| x.eql? node}
|
128
|
+
node.parent.value.insert index, get_insert_func_call(line)
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/rkelly/node.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module RKelly
|
2
|
+
module Visitors
|
3
|
+
class ParentBuilder < Visitor
|
4
|
+
|
5
|
+
def build node
|
6
|
+
link_parents node, nil
|
7
|
+
node
|
8
|
+
end
|
9
|
+
|
10
|
+
## define methods for traverse different kinds of nodes
|
11
|
+
## consts are from RKelly::Visitors::Visitor
|
12
|
+
|
13
|
+
TERMINAL_NODES.each do |type|
|
14
|
+
define_method(:"visit_#{type}Node") { |o| o.value }
|
15
|
+
end
|
16
|
+
|
17
|
+
BINARY_NODES.each do |type|
|
18
|
+
define_method(:"visit_#{type}Node") do |o|
|
19
|
+
[o.left && link_parents(o.left, o), o.value && link_parents(o.value, o)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
ARRAY_VALUE_NODES.each do |type|
|
24
|
+
define_method(:"visit_#{type}Node") do |o|
|
25
|
+
o.value && o.value.map { |v| v ? link_parents(v, o) : nil }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
NAME_VALUE_NODES.each do |type|
|
30
|
+
define_method(:"visit_#{type}Node") do |o|
|
31
|
+
[o.name.to_s.to_sym, o.value ? link_parents(o.value, o) : nil]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
SINGLE_VALUE_NODES.each do |type|
|
36
|
+
define_method(:"visit_#{type}Node") do |o|
|
37
|
+
link_parents(o.value, o) if o.value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
PREFIX_POSTFIX_NODES.each do |type|
|
42
|
+
define_method(:"visit_#{type}Node") do |o|
|
43
|
+
link_parents(o.operand, o)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
CONDITIONAL_NODES.each do |type|
|
49
|
+
define_method(:"visit_#{type}Node") do |o|
|
50
|
+
[ link_parents(o.conditions, o),
|
51
|
+
link_parents(o.value, o),
|
52
|
+
o.else ? link_parents(o.else, o) : nil
|
53
|
+
]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
FUNC_CALL_NODES.each do |type|
|
57
|
+
define_method(:"visit_#{type}Node") do |o|
|
58
|
+
[link_parents(o.value, o), link_parents(o.arguments, o)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
FUNC_DECL_NODES.each do |type|
|
62
|
+
define_method(:"visit_#{type}Node") do |o|
|
63
|
+
[
|
64
|
+
o.value ? o.value : nil,
|
65
|
+
o.arguments.map { |x| link_parents(x, o) },
|
66
|
+
link_parents(o.function_body, o)
|
67
|
+
]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit_ForNode(o)
|
72
|
+
[
|
73
|
+
o.init ? link_parents(o.init, o) : nil,
|
74
|
+
o.test ? link_parents(o.test, o) : nil,
|
75
|
+
o.counter ? link_parents(o.counter, o) : nil,
|
76
|
+
link_parents(o.value, o)
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_ForInNode(o)
|
81
|
+
[
|
82
|
+
link_parents(o.left, o),
|
83
|
+
link_parents(o.right, o),
|
84
|
+
link_parents(o.value, o)
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_TryNode(o)
|
89
|
+
[
|
90
|
+
link_parents(o.value, o),
|
91
|
+
o.catch_var ? o.catch_var : nil,
|
92
|
+
o.catch_block ? link_parents(o.catch_block, o) : nil,
|
93
|
+
o.finally_block ? link_parents(o.finally_block, o) : nil
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
def visit_BracketAccessorNode(o)
|
98
|
+
[
|
99
|
+
link_parents(o.value, o),
|
100
|
+
link_parents(o.accessor, o)
|
101
|
+
]
|
102
|
+
end
|
103
|
+
|
104
|
+
def visit_DotAccessorNode(o)
|
105
|
+
link_parents(o.value, o)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def link_parents node, parent
|
110
|
+
node.parent = parent
|
111
|
+
|
112
|
+
# call the visit method to traverse potential subnodes depends on the node class
|
113
|
+
# error if the node is not in the predefined list
|
114
|
+
#
|
115
|
+
raise "No visit method for '#{node.class}'" unless self.respond_to?("visit_#{node.class.name.split(/::/)[-1]}")
|
116
|
+
self.send(:"visit_#{node.class.name.split(/::/)[-1]}", node)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jsinstrument
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mushan Yang
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-10-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rkelly
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: bundler
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '1.3'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '1.3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Instrument JavaScript source code to monitor code coverage in pure Ruby
|
63
|
+
email:
|
64
|
+
- mushan.yang@hulu.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- jsinstrument.gemspec
|
75
|
+
- lib/jsinstrument.rb
|
76
|
+
- lib/jsinstrument/inserting.js
|
77
|
+
- lib/jsinstrument/instrumenter.rb
|
78
|
+
- lib/jsinstrument/version.rb
|
79
|
+
- lib/rkelly/node.rb
|
80
|
+
- lib/rkelly/parent_builder.rb
|
81
|
+
homepage: ''
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 1.8.25
|
103
|
+
signing_key:
|
104
|
+
specification_version: 3
|
105
|
+
summary: Instrument JavaScript source code to monitor code coverage in pure Ruby
|
106
|
+
test_files: []
|