deep-cover 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +127 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/cov +43 -0
  12. data/bin/gemcov +8 -0
  13. data/bin/selfcov +21 -0
  14. data/bin/setup +8 -0
  15. data/bin/testall +88 -0
  16. data/deep_cover.gemspec +44 -0
  17. data/exe/deep-cover +6 -0
  18. data/future_read_me.md +108 -0
  19. data/lib/deep-cover.rb +1 -0
  20. data/lib/deep_cover.rb +11 -0
  21. data/lib/deep_cover/analyser.rb +24 -0
  22. data/lib/deep_cover/analyser/base.rb +51 -0
  23. data/lib/deep_cover/analyser/branch.rb +20 -0
  24. data/lib/deep_cover/analyser/covered_code_source.rb +31 -0
  25. data/lib/deep_cover/analyser/function.rb +12 -0
  26. data/lib/deep_cover/analyser/ignore_uncovered.rb +19 -0
  27. data/lib/deep_cover/analyser/node.rb +11 -0
  28. data/lib/deep_cover/analyser/per_char.rb +20 -0
  29. data/lib/deep_cover/analyser/per_line.rb +23 -0
  30. data/lib/deep_cover/analyser/statement.rb +31 -0
  31. data/lib/deep_cover/analyser/subset.rb +24 -0
  32. data/lib/deep_cover/auto_run.rb +49 -0
  33. data/lib/deep_cover/autoload_tracker.rb +75 -0
  34. data/lib/deep_cover/backports.rb +9 -0
  35. data/lib/deep_cover/base.rb +55 -0
  36. data/lib/deep_cover/builtin_takeover.rb +2 -0
  37. data/lib/deep_cover/cli/debugger.rb +93 -0
  38. data/lib/deep_cover/cli/deep_cover.rb +49 -0
  39. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +105 -0
  40. data/lib/deep_cover/config.rb +52 -0
  41. data/lib/deep_cover/core_ext/autoload_overrides.rb +40 -0
  42. data/lib/deep_cover/core_ext/coverage_replacement.rb +26 -0
  43. data/lib/deep_cover/core_ext/load_overrides.rb +24 -0
  44. data/lib/deep_cover/core_ext/require_overrides.rb +36 -0
  45. data/lib/deep_cover/coverage.rb +198 -0
  46. data/lib/deep_cover/covered_code.rb +138 -0
  47. data/lib/deep_cover/custom_requirer.rb +93 -0
  48. data/lib/deep_cover/node.rb +8 -0
  49. data/lib/deep_cover/node/arguments.rb +50 -0
  50. data/lib/deep_cover/node/assignments.rb +250 -0
  51. data/lib/deep_cover/node/base.rb +99 -0
  52. data/lib/deep_cover/node/begin.rb +25 -0
  53. data/lib/deep_cover/node/block.rb +53 -0
  54. data/lib/deep_cover/node/boolean.rb +22 -0
  55. data/lib/deep_cover/node/branch.rb +28 -0
  56. data/lib/deep_cover/node/case.rb +94 -0
  57. data/lib/deep_cover/node/collections.rb +21 -0
  58. data/lib/deep_cover/node/const.rb +10 -0
  59. data/lib/deep_cover/node/def.rb +38 -0
  60. data/lib/deep_cover/node/empty_body.rb +21 -0
  61. data/lib/deep_cover/node/exceptions.rb +74 -0
  62. data/lib/deep_cover/node/if.rb +36 -0
  63. data/lib/deep_cover/node/keywords.rb +84 -0
  64. data/lib/deep_cover/node/literals.rb +77 -0
  65. data/lib/deep_cover/node/loops.rb +72 -0
  66. data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
  67. data/lib/deep_cover/node/mixin/check_completion.rb +16 -0
  68. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +25 -0
  69. data/lib/deep_cover/node/mixin/executed_after_children.rb +13 -0
  70. data/lib/deep_cover/node/mixin/execution_location.rb +56 -0
  71. data/lib/deep_cover/node/mixin/flow_accounting.rb +63 -0
  72. data/lib/deep_cover/node/mixin/has_child.rb +138 -0
  73. data/lib/deep_cover/node/mixin/has_child_handler.rb +73 -0
  74. data/lib/deep_cover/node/mixin/has_tracker.rb +44 -0
  75. data/lib/deep_cover/node/mixin/is_statement.rb +18 -0
  76. data/lib/deep_cover/node/mixin/rewriting.rb +32 -0
  77. data/lib/deep_cover/node/mixin/wrapper.rb +13 -0
  78. data/lib/deep_cover/node/module.rb +64 -0
  79. data/lib/deep_cover/node/root.rb +18 -0
  80. data/lib/deep_cover/node/send.rb +83 -0
  81. data/lib/deep_cover/node/splat.rb +13 -0
  82. data/lib/deep_cover/node/variables.rb +14 -0
  83. data/lib/deep_cover/parser_ext/range.rb +40 -0
  84. data/lib/deep_cover/reporter.rb +6 -0
  85. data/lib/deep_cover/reporter/istanbul.rb +151 -0
  86. data/lib/deep_cover/tools.rb +18 -0
  87. data/lib/deep_cover/tools/builtin_coverage.rb +50 -0
  88. data/lib/deep_cover/tools/camelize.rb +8 -0
  89. data/lib/deep_cover/tools/dump_covered_code.rb +32 -0
  90. data/lib/deep_cover/tools/execute_sample.rb +23 -0
  91. data/lib/deep_cover/tools/format.rb +16 -0
  92. data/lib/deep_cover/tools/format_char_cover.rb +18 -0
  93. data/lib/deep_cover/tools/format_generated_code.rb +25 -0
  94. data/lib/deep_cover/tools/number_lines.rb +18 -0
  95. data/lib/deep_cover/tools/our_coverage.rb +9 -0
  96. data/lib/deep_cover/tools/require_relative_dir.rb +10 -0
  97. data/lib/deep_cover/tools/silence_warnings.rb +15 -0
  98. data/lib/deep_cover/version.rb +3 -0
  99. metadata +326 -0
@@ -0,0 +1,73 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module HasChildHandler
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ def call_child_handler template, child, child_name = nil
9
+ child_name ||= self.class.child_index_to_name(child.index, children.size) rescue binding.pry
10
+ method_name = template % {name: child_name}
11
+ if respond_to?(method_name)
12
+ args = [child, child_name]
13
+ arity = method(method_name).arity
14
+ if arity >= 0
15
+ args = args[0...arity]
16
+ end
17
+ answer = send(method_name, *args)
18
+ end
19
+ answer
20
+ end
21
+ private :call_child_handler
22
+
23
+ module ClassMethods
24
+ def has_child_handler(template)
25
+ child_method_name = template % {name: 'child'}
26
+ action = template.gsub(/_%{name}/, '').gsub(/%{name}_/, '')
27
+ const_name = "#{Tools.camelize(action)}Handler"
28
+ class_eval <<-end_eval, __FILE__, __LINE__
29
+ module #{const_name} # module RewriteHandler
30
+ module ClassMethods # module ClassMethods
31
+ def has_child(#{action}: nil, **h) # def has_child(rewrite: nil, **h)
32
+ name, _types = h.first # name, _types = h.first
33
+ define_child_handler(#{template.inspect}, # define_child_handler('rewrite_%{child}',
34
+ name, #{action}) # name, rewrite)
35
+ super(**h) # super(**h)
36
+ end # end
37
+ end # end
38
+
39
+ def #{child_method_name}(child, name = nil) # def rewrite_child(child, name = nil)
40
+ call_child_handler(#{template.inspect}, child, # call_child_handler('rewrite_%{child}', child,
41
+ name) || super # name) || super
42
+ end # end
43
+ end # end
44
+ include #{const_name} # include RewriteHandler
45
+ singleton_class.prepend #{const_name}::ClassMethods # singleton_class.prepend RewriteHandler::ClassMethods
46
+ end_eval
47
+ end
48
+
49
+ def define_child_handler(template, name, action)
50
+ method_name = template % {name: name}
51
+ case action
52
+ when nil
53
+ # Nothing to do
54
+ when Symbol
55
+ define_method(method_name) do |*args|
56
+ arity = method(action).arity
57
+ if arity < 0
58
+ send(action, *args)
59
+ else
60
+ send(action, *args[0...arity])
61
+ end
62
+ end
63
+ when Proc
64
+ define_method(method_name, &action)
65
+ else
66
+ define_method(method_name) {|*| action }
67
+ end
68
+ end
69
+ private :define_child_handler
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,44 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module HasTracker
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ setup_constants(base)
7
+ end
8
+
9
+ def self.setup_constants(base)
10
+ base.const_set :TRACKERS, {}
11
+ end
12
+
13
+ def initialize(*)
14
+ @tracker_offset = covered_code.allocate_trackers(self.class::TRACKERS.size).begin
15
+ super
16
+ end
17
+
18
+ def tracker_sources
19
+ self.class::TRACKERS.map do |name, _|
20
+ [:"#{name}_tracker", send(:"#{name}_tracker_source")]
21
+ end.to_h
22
+ end
23
+
24
+ module ClassMethods
25
+ def inherited(base)
26
+ super
27
+ HasTracker.setup_constants(base)
28
+ end
29
+
30
+ def has_tracker(name)
31
+ i = self::TRACKERS[name] = self::TRACKERS.size
32
+ class_eval <<-end_eval, __FILE__, __LINE__ + 1
33
+ def #{name}_tracker_source
34
+ covered_code.tracker_source(@tracker_offset + #{i})
35
+ end
36
+ def #{name}_tracker_hits
37
+ covered_code.tracker_hits(@tracker_offset + #{i})
38
+ end
39
+ end_eval
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module IsStatement
4
+ def self.included(base)
5
+ base.has_child_handler('is_%{name}_statement')
6
+ end
7
+
8
+ def is_statement
9
+ parent.is_child_statement(self)
10
+ end
11
+
12
+ # Default child rewriting rule
13
+ def is_child_statement(child, name=nil)
14
+ :if_incompatible
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,32 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module Rewriting
4
+ def self.included(base)
5
+ base.has_child_handler('rewrite_%{name}')
6
+ end
7
+
8
+ # Code to add before and after the node for covering purposes
9
+ def rewrite
10
+ end
11
+
12
+ # Default child rewriting rule
13
+ def rewrite_child(child, name=nil)
14
+ end
15
+
16
+ def resolve_rewrite(rule, context)
17
+ rule ||= '%{node}'
18
+ sources = context.tracker_sources
19
+ rule.split('%{node}').map{|s| s % {local: covered_code.local_var, **sources} }
20
+ end
21
+
22
+ def rewrite_prefix_suffix
23
+ parent_prefix, parent_suffix = resolve_rewrite(parent.rewrite_child(self), parent)
24
+ prefix, suffix = resolve_rewrite(rewrite, self)
25
+ [
26
+ "#{parent_prefix}#{prefix}",
27
+ "#{suffix}#{parent_suffix}"
28
+ ]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ module DeepCover
2
+ module Node::Mixin
3
+ module Wrapper
4
+ def initialize(base_node, **kwargs)
5
+ super(base_node, base_children: [base_node], **kwargs)
6
+ end
7
+
8
+ def executed_loc_keys
9
+ []
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
1
+ require_relative 'const'
2
+
3
+ module DeepCover
4
+ class Node
5
+ class ModuleName < Node
6
+ has_child scope: [Node, nil]
7
+ has_child const_name: Symbol
8
+
9
+ def flow_completion_count
10
+ parent.execution_count
11
+ end
12
+
13
+ def execution_count
14
+ if scope
15
+ scope.flow_completion_count
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+
22
+ class Module < Node
23
+ check_completion
24
+ has_tracker :body_entry
25
+ has_child const: {const: ModuleName}
26
+ has_child body: Node,
27
+ can_be_empty: -> { base_node.loc.end.begin },
28
+ rewrite: '%{body_entry_tracker};%{local}=nil;%{node}',
29
+ is_statement: true,
30
+ flow_entry_count: :body_entry_tracker_hits
31
+ executed_loc_keys :keyword
32
+
33
+ def execution_count
34
+ body_entry_tracker_hits
35
+ end
36
+ end
37
+
38
+ class Class < Node
39
+ check_completion
40
+ has_tracker :body_entry
41
+ has_child const: {const: ModuleName}
42
+ has_child inherit: [Node, nil] # TODO
43
+ has_child body: Node,
44
+ can_be_empty: -> { base_node.loc.end.begin },
45
+ rewrite: '%{body_entry_tracker};%{node}',
46
+ is_statement: true,
47
+ flow_entry_count: :body_entry_tracker_hits
48
+ executed_loc_keys :keyword
49
+
50
+ def execution_count
51
+ body_entry_tracker_hits
52
+ end
53
+ end
54
+
55
+ # class << foo
56
+ class Sclass < Node
57
+ has_child object: Node
58
+ has_child body: Node,
59
+ can_be_empty: -> { base_node.loc.end.begin },
60
+ is_statement: true
61
+ # TODO
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,18 @@
1
+ module DeepCover
2
+ class Node::Root < Node
3
+ has_tracker :root
4
+ has_child main: Node,
5
+ can_be_empty: -> { ::Parser::Source::Range.new(covered_code.buffer, 0, 0) },
6
+ is_statement: true,
7
+ rewrite: -> {
8
+ "#{covered_code.trackers_setup_source};%{root_tracker};%{local}=nil;%{node}"
9
+ }
10
+ attr_reader :covered_code
11
+ alias_method :flow_entry_count, :root_tracker_hits
12
+
13
+ def initialize(child_ast, covered_code)
14
+ @covered_code = covered_code
15
+ super(nil, parent: nil, base_children: [child_ast])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,83 @@
1
+ require_relative 'literals'
2
+
3
+ module DeepCover
4
+ class Node
5
+ class MethodName < Node
6
+ has_child name: Symbol
7
+
8
+ def initialize(name, parent: raise, **kwargs)
9
+ super(parent, **kwargs, parent: parent, base_children: [name])
10
+ end
11
+
12
+ def loc_hash
13
+ # Expression is used in the rewriting
14
+ # if selector_end is present, then this won't be needed
15
+ {expression: parent.loc_hash[:selector_begin]}
16
+ end
17
+
18
+ def executable?
19
+ false
20
+ end
21
+ end
22
+
23
+ class Send < Node
24
+ check_completion
25
+ has_child receiver: [Node, nil]
26
+ has_child method_name_wrapper: {Symbol => MethodName}, rewrite: :add_opening_parentheses
27
+ has_extra_children arguments: Node, rewrite: :add_closing_parentheses
28
+ executed_loc_keys :dot, :selector_begin, :selector_end, :operator
29
+
30
+ def method_name
31
+ method_name_wrapper.name
32
+ end
33
+
34
+ def loc_hash
35
+ base = super
36
+ hash = { expression: base[:expression], begin: base[:begin], end: base[:end], dot: base[:dot]}
37
+ selector = base[:selector]
38
+
39
+ if [:[], :[]=].include?(method_name)
40
+ hash[:selector_begin] = selector.resize(1)
41
+ hash[:selector_end] = Parser::Source::Range.new(selector.source_buffer, selector.end_pos - 1, selector.end_pos)
42
+ else
43
+ hash[:selector_begin] = base[:selector]
44
+ end
45
+
46
+ hash
47
+ end
48
+
49
+ # Only need to add them to deal with ambiguous cases where a method is hidden by a local. Ex:
50
+ # raise TypeError, 'hello' #=> Works
51
+ # raise (TypeError), 'hello' #=> Simplification of what DeepCover generates, still works
52
+ # raise = 1; raise TypeError, 'hello' #=> works
53
+ # raise = 1; raise (TypeError), 'hello' #=> syntax error.
54
+ # raise = 1; raise((TypeError), 'hello'0 #=> works
55
+ def add_parentheses?
56
+ return if arguments.empty?
57
+ # No ambiguity if there is a receiver
58
+ return if receiver
59
+ # Already has parentheses
60
+ return if self.loc_hash[:begin]
61
+ true
62
+ end
63
+
64
+ def add_opening_parentheses
65
+ return unless add_parentheses?
66
+ "%{node}("
67
+ end
68
+
69
+ def add_closing_parentheses(child)
70
+ return unless add_parentheses?
71
+ return unless child.index == children.size - 1
72
+ "%{node})"
73
+ end
74
+ end
75
+
76
+ class MatchWithLvasgn < Node
77
+ check_completion
78
+ has_child receiver: Regexp
79
+ has_child compare_to: Node
80
+ # TODO: test
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ module DeepCover
2
+ class Node
3
+ class Splat < Node
4
+ check_completion inner: '[%{node}]', outer: '*%{node}'
5
+ has_child receiver: Node
6
+ end
7
+
8
+ class Kwsplat < Node
9
+ check_completion inner: '{%{node}}', outer: '**%{node}'
10
+ has_child receiver: Node
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module DeepCover
2
+ class Node
3
+ class Variable < Node
4
+ has_child var_name: Symbol
5
+ end
6
+ Ivar = Lvar = Cvar = Gvar = BackRef = Variable
7
+
8
+ # $1
9
+ class NthRef < Node
10
+ has_child n: Integer
11
+ # TODO
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ class Parser::Source::Range
2
+ def with(begin_pos: @begin_pos, end_pos: @end_pos)
3
+ Parser::Source::Range.new(@source_buffer, begin_pos, end_pos)
4
+ end
5
+
6
+ # Similar to `end`, but is just after the current Range
7
+ def succ
8
+ with begin_pos: @end_pos+1, end_pos: @end_pos+1
9
+ end
10
+
11
+ # (1...10).split(2...3, 6...8) => [1...2, 3...6, 7...10]
12
+ # Assumes inner_ranges are exclusive, and included in self
13
+ def split(*inner_ranges)
14
+ inner_ranges.sort_by!(&:begin_pos)
15
+ [self.begin, *inner_ranges, self.end]
16
+ .each_cons(2)
17
+ .map{|i, j| with(begin_pos: i.end_pos, end_pos: j.begin_pos)}
18
+ .reject(&:empty?)
19
+ end
20
+
21
+ def lstrip(pattern = /\s*/)
22
+ if (match = /^#{pattern}/.match(source))
23
+ with(begin_pos: @begin_pos + match[0].length)
24
+ else
25
+ self
26
+ end
27
+ end
28
+
29
+ def rstrip(pattern = /\s*/)
30
+ if (match = /#{pattern}$/.match(source))
31
+ with(end_pos: @end_pos - match[0].length)
32
+ else
33
+ self
34
+ end
35
+ end
36
+
37
+ def strip(pattern = /\s*/)
38
+ lstrip(pattern).rstrip(pattern)
39
+ end
40
+ end
@@ -0,0 +1,6 @@
1
+ module DeepCover
2
+ module Reporter
3
+ end
4
+ require_relative 'node'
5
+ require_relative_dir 'reporter'
6
+ end
@@ -0,0 +1,151 @@
1
+ require 'json'
2
+
3
+ module DeepCover
4
+ module Reporter
5
+ class Istanbul < Struct.new(:covered_code, :options)
6
+ # Converters has no dependency on the including class.
7
+ module Converters
8
+ def convert_range(range)
9
+ { start: {
10
+ line: range.line,
11
+ column: range.column,
12
+ },
13
+ end: {
14
+ line: range.last_line,
15
+ column: range.last_column-1, # Our ranges are exclusive, Istanbul's are inclusive
16
+ },
17
+ }
18
+ end
19
+
20
+ # [:a, :b, :c] => {'1': :a, '2': :b, '3': :c}
21
+ def convert_list(list)
22
+ list.map.with_index{ |val, i| [i.succ.to_s, val] }.to_h
23
+ end
24
+
25
+ def convert_def(node)
26
+ ends_at = node.signature.loc_hash[:end] || node.loc_hash[:name]
27
+ decl = node.loc_hash[:keyword].with(end_pos: ends_at.end_pos)
28
+ _convert_function(node, node.method_name, decl)
29
+ end
30
+
31
+ def convert_block(node)
32
+ decl = node.loc_hash[:begin]
33
+ if (args = node.args.expression)
34
+ decl = decl.join(args) rescue binding.pry
35
+ end
36
+ _convert_function(node, '(block)', decl)
37
+ end
38
+
39
+ def convert_function(node)
40
+ if node.is_a?(Node::Block)
41
+ convert_block(node)
42
+ else
43
+ convert_def(node)
44
+ end
45
+ end
46
+
47
+ def convert_branch(node, branches = node.branches)
48
+ # Currently, nyc seems to outputs the same location over and over...
49
+ loc = convert_range(node.expression)
50
+ {
51
+ loc: loc,
52
+ type: node.type,
53
+ line: node.expression.line,
54
+ locations: branches.map{|n| loc}
55
+ }
56
+ end
57
+
58
+ private
59
+ def _convert_function(node, name, decl)
60
+ loc = node.body ? node.body.expression : decl.end
61
+ {
62
+ name: name,
63
+ line: node.expression.line,
64
+ decl: convert_range(decl),
65
+ loc: convert_range(loc),
66
+ }
67
+ end
68
+ end
69
+ include Converters
70
+
71
+ def node_analyser
72
+ @node_analyser ||= Analyser::Node.new(covered_code, **options)
73
+ end
74
+
75
+ def node_runs
76
+ @node_runs ||= node_analyser.results
77
+ end
78
+
79
+ def functions
80
+ @functions ||= Analyser::Function.new(node_analyser, **options).results
81
+ end
82
+
83
+ def statements
84
+ @statements ||= Analyser::Statement.new(node_analyser, **options).results
85
+ end
86
+
87
+ def branches
88
+ @branches ||= Analyser::Branch.new(node_analyser, **options).results
89
+ end
90
+
91
+ def branch_map
92
+ branches.map do |node, branches_runs|
93
+ convert_branch(node, branches_runs.keys)
94
+ end
95
+ end
96
+
97
+ # Istanbul doesn't understand how to ignore a branch...
98
+ def zero_to_something(values)
99
+ values.map{|v| v || 1}
100
+ end
101
+
102
+ def branch_runs
103
+ branches.values.map{|r| zero_to_something(r.values) }
104
+ end
105
+
106
+ def statement_map
107
+ statements.keys.map{ |range| convert_range(range) }
108
+ end
109
+
110
+ def statement_runs
111
+ statements.values
112
+ end
113
+
114
+ def function_map
115
+ functions.keys.map{|n| convert_function(n) }
116
+ end
117
+
118
+ def function_runs
119
+ functions.values
120
+ end
121
+
122
+ def data
123
+ {
124
+ statementMap: statement_map ,
125
+ s: statement_runs,
126
+ fnMap: function_map ,
127
+ f: function_runs ,
128
+ branchMap: branch_map ,
129
+ b: branch_runs ,
130
+ }
131
+ end
132
+
133
+ def convert
134
+ { covered_code.name => {
135
+ path: covered_code.path,
136
+ **data.transform_values{|l| convert_list(l)},
137
+ } }
138
+ end
139
+
140
+ def report
141
+ convert.to_json
142
+ end
143
+
144
+ class << self
145
+ def available?
146
+ `nyc --version` >= '11.' rescue false
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end