jsinstrument 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|