deep-cover 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +127 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/cov +43 -0
- data/bin/gemcov +8 -0
- data/bin/selfcov +21 -0
- data/bin/setup +8 -0
- data/bin/testall +88 -0
- data/deep_cover.gemspec +44 -0
- data/exe/deep-cover +6 -0
- data/future_read_me.md +108 -0
- data/lib/deep-cover.rb +1 -0
- data/lib/deep_cover.rb +11 -0
- data/lib/deep_cover/analyser.rb +24 -0
- data/lib/deep_cover/analyser/base.rb +51 -0
- data/lib/deep_cover/analyser/branch.rb +20 -0
- data/lib/deep_cover/analyser/covered_code_source.rb +31 -0
- data/lib/deep_cover/analyser/function.rb +12 -0
- data/lib/deep_cover/analyser/ignore_uncovered.rb +19 -0
- data/lib/deep_cover/analyser/node.rb +11 -0
- data/lib/deep_cover/analyser/per_char.rb +20 -0
- data/lib/deep_cover/analyser/per_line.rb +23 -0
- data/lib/deep_cover/analyser/statement.rb +31 -0
- data/lib/deep_cover/analyser/subset.rb +24 -0
- data/lib/deep_cover/auto_run.rb +49 -0
- data/lib/deep_cover/autoload_tracker.rb +75 -0
- data/lib/deep_cover/backports.rb +9 -0
- data/lib/deep_cover/base.rb +55 -0
- data/lib/deep_cover/builtin_takeover.rb +2 -0
- data/lib/deep_cover/cli/debugger.rb +93 -0
- data/lib/deep_cover/cli/deep_cover.rb +49 -0
- data/lib/deep_cover/cli/instrumented_clone_reporter.rb +105 -0
- data/lib/deep_cover/config.rb +52 -0
- data/lib/deep_cover/core_ext/autoload_overrides.rb +40 -0
- data/lib/deep_cover/core_ext/coverage_replacement.rb +26 -0
- data/lib/deep_cover/core_ext/load_overrides.rb +24 -0
- data/lib/deep_cover/core_ext/require_overrides.rb +36 -0
- data/lib/deep_cover/coverage.rb +198 -0
- data/lib/deep_cover/covered_code.rb +138 -0
- data/lib/deep_cover/custom_requirer.rb +93 -0
- data/lib/deep_cover/node.rb +8 -0
- data/lib/deep_cover/node/arguments.rb +50 -0
- data/lib/deep_cover/node/assignments.rb +250 -0
- data/lib/deep_cover/node/base.rb +99 -0
- data/lib/deep_cover/node/begin.rb +25 -0
- data/lib/deep_cover/node/block.rb +53 -0
- data/lib/deep_cover/node/boolean.rb +22 -0
- data/lib/deep_cover/node/branch.rb +28 -0
- data/lib/deep_cover/node/case.rb +94 -0
- data/lib/deep_cover/node/collections.rb +21 -0
- data/lib/deep_cover/node/const.rb +10 -0
- data/lib/deep_cover/node/def.rb +38 -0
- data/lib/deep_cover/node/empty_body.rb +21 -0
- data/lib/deep_cover/node/exceptions.rb +74 -0
- data/lib/deep_cover/node/if.rb +36 -0
- data/lib/deep_cover/node/keywords.rb +84 -0
- data/lib/deep_cover/node/literals.rb +77 -0
- data/lib/deep_cover/node/loops.rb +72 -0
- data/lib/deep_cover/node/mixin/can_augment_children.rb +65 -0
- data/lib/deep_cover/node/mixin/check_completion.rb +16 -0
- data/lib/deep_cover/node/mixin/child_can_be_empty.rb +25 -0
- data/lib/deep_cover/node/mixin/executed_after_children.rb +13 -0
- data/lib/deep_cover/node/mixin/execution_location.rb +56 -0
- data/lib/deep_cover/node/mixin/flow_accounting.rb +63 -0
- data/lib/deep_cover/node/mixin/has_child.rb +138 -0
- data/lib/deep_cover/node/mixin/has_child_handler.rb +73 -0
- data/lib/deep_cover/node/mixin/has_tracker.rb +44 -0
- data/lib/deep_cover/node/mixin/is_statement.rb +18 -0
- data/lib/deep_cover/node/mixin/rewriting.rb +32 -0
- data/lib/deep_cover/node/mixin/wrapper.rb +13 -0
- data/lib/deep_cover/node/module.rb +64 -0
- data/lib/deep_cover/node/root.rb +18 -0
- data/lib/deep_cover/node/send.rb +83 -0
- data/lib/deep_cover/node/splat.rb +13 -0
- data/lib/deep_cover/node/variables.rb +14 -0
- data/lib/deep_cover/parser_ext/range.rb +40 -0
- data/lib/deep_cover/reporter.rb +6 -0
- data/lib/deep_cover/reporter/istanbul.rb +151 -0
- data/lib/deep_cover/tools.rb +18 -0
- data/lib/deep_cover/tools/builtin_coverage.rb +50 -0
- data/lib/deep_cover/tools/camelize.rb +8 -0
- data/lib/deep_cover/tools/dump_covered_code.rb +32 -0
- data/lib/deep_cover/tools/execute_sample.rb +23 -0
- data/lib/deep_cover/tools/format.rb +16 -0
- data/lib/deep_cover/tools/format_char_cover.rb +18 -0
- data/lib/deep_cover/tools/format_generated_code.rb +25 -0
- data/lib/deep_cover/tools/number_lines.rb +18 -0
- data/lib/deep_cover/tools/our_coverage.rb +9 -0
- data/lib/deep_cover/tools/require_relative_dir.rb +10 -0
- data/lib/deep_cover/tools/silence_warnings.rb +15 -0
- data/lib/deep_cover/version.rb +3 -0
- metadata +326 -0
data/deep_cover.gemspec
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "deep_cover/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "deep-cover"
|
8
|
+
spec.version = DeepCover::VERSION
|
9
|
+
spec.authors = ["Marc-André Lafortune", "Maxime Lapointe"]
|
10
|
+
spec.email = ["github@marc-andre.ca", "hunter_spawn@hotmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Write a short summary, because Rubygems requires one.}
|
13
|
+
spec.description = %q{Write a longer description or delete this line.}
|
14
|
+
spec.homepage = "http://github.com"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
### Runtime dependencies
|
25
|
+
spec.add_runtime_dependency 'parser'
|
26
|
+
spec.add_runtime_dependency 'backports', '>= 3.10.1'
|
27
|
+
spec.add_runtime_dependency 'binding_of_caller'
|
28
|
+
|
29
|
+
# CLI
|
30
|
+
spec.add_runtime_dependency "term-ansicolor"
|
31
|
+
spec.add_runtime_dependency "highline"
|
32
|
+
spec.add_runtime_dependency "ruby-progressbar", '<1.9.0'
|
33
|
+
spec.add_runtime_dependency 'with_progress'
|
34
|
+
spec.add_runtime_dependency 'slop', '~> 4.0'
|
35
|
+
|
36
|
+
# While in 0.x
|
37
|
+
spec.add_runtime_dependency 'pry'
|
38
|
+
|
39
|
+
### Dev dependencies
|
40
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
41
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
42
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
43
|
+
spec.add_development_dependency 'activesupport', "~> 4.0"
|
44
|
+
end
|
data/exe/deep-cover
ADDED
data/future_read_me.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
[](https://travis-ci.org/deep-cover/deep-cover)
|
2
|
+
|
3
|
+
# DeepCover
|
4
|
+
|
5
|
+
Deep Cover aims to be an improved replacement for the built-in Coverage library.
|
6
|
+
|
7
|
+
It will report a more accurate picture of your code usage. In particular a line is considered covered if and only if it is entirely executed:
|
8
|
+
|
9
|
+
foo if false # => This is considered covered by builtin coverage, even though `foo` might not even exist
|
10
|
+
|
11
|
+
Optionally, branch coverage will detect if some branches are never taken. In the following example, `test_foo` only provides values for `x` that respond to `:to_s`, thus the implicit `else` is never tested (i.e. a value of `x` that does not respond to `:to_s`)
|
12
|
+
|
13
|
+
def foo(x)
|
14
|
+
x = x.to_s if x.respond_to? :to_s
|
15
|
+
# ...
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_foo
|
19
|
+
assert_equal something, foo(42)
|
20
|
+
assert_equal something_else, foo(:hello)
|
21
|
+
end
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add to your Gemfile:
|
26
|
+
|
27
|
+
gem 'deep-cover'
|
28
|
+
|
29
|
+
Then run:
|
30
|
+
|
31
|
+
bundle
|
32
|
+
|
33
|
+
### Builtin Coverage (including SimpleCov) users
|
34
|
+
|
35
|
+
Before you require `coverage` or `simplecov`, do a `require 'deep-cover/takeover'`.
|
36
|
+
|
37
|
+
For example, the `test/test_helper.rb` file for `simplecov` users will look like
|
38
|
+
|
39
|
+
```
|
40
|
+
require 'deep-cover/takeover'
|
41
|
+
require 'simplecov'
|
42
|
+
SimpleCov.start
|
43
|
+
# rest of `test_helper.rb`
|
44
|
+
```
|
45
|
+
|
46
|
+
### Quick and dirty for Rails app or Gem:
|
47
|
+
|
48
|
+
Assuming your default `rake` task is set to execute all tests, you can try:
|
49
|
+
|
50
|
+
`deep-cover /path/to/rails/app/or/gem`
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
### Configuration
|
55
|
+
|
56
|
+
`configure` is used to specify how specific `DeepCover` should be and which files it should analyse. The following code reflects the default settings:
|
57
|
+
|
58
|
+
```
|
59
|
+
DeepCover.configure do
|
60
|
+
cover_paths %w[app lib]
|
61
|
+
require_all_expression_in_line
|
62
|
+
require_all_branches(false)
|
63
|
+
accept_uncovered_raise(false)
|
64
|
+
accept_uncovered_default_arguments(false)
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
### Low level usage
|
69
|
+
|
70
|
+
```
|
71
|
+
# Setup
|
72
|
+
require 'deep-cover'
|
73
|
+
DeepCover.configure { require_all_branches }
|
74
|
+
# Cover
|
75
|
+
DeepCover.cover do
|
76
|
+
require 'my_file_to_cover'
|
77
|
+
require 'my_other_file_to_cover'
|
78
|
+
end
|
79
|
+
require 'this_file_wont_be_covered'
|
80
|
+
tests.run()
|
81
|
+
DeepCover.report
|
82
|
+
```
|
83
|
+
|
84
|
+
## Development
|
85
|
+
|
86
|
+
After checking out the repo, run `bundle` to install dependencies. Then, run `rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
87
|
+
|
88
|
+
For detailed analysis:
|
89
|
+
|
90
|
+
`deep-cover -e "some.ruby(:code).here"`
|
91
|
+
|
92
|
+
To run one of the specs in `spec`:
|
93
|
+
|
94
|
+
`deep-cover -t boolean`
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
Please ask questions on StackOverflow.com. Maintainers monitor the tag `deep-cover.rb`.
|
99
|
+
|
100
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/deep-cover/deep-cover. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
101
|
+
|
102
|
+
## License
|
103
|
+
|
104
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
105
|
+
|
106
|
+
## Code of Conduct
|
107
|
+
|
108
|
+
Everyone interacting in the DeepCover project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/deep-cover/deep-cover/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/deep-cover.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'deep_cover'
|
data/lib/deep_cover.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module DeepCover
|
2
|
+
require 'parser'
|
3
|
+
require_relative 'deep_cover/backports'
|
4
|
+
require_relative 'deep_cover/tools'
|
5
|
+
require_relative_dir 'deep_cover/parser_ext'
|
6
|
+
require_relative_dir 'deep_cover', except: %w[auto_run builtin_takeover]
|
7
|
+
|
8
|
+
extend Base
|
9
|
+
extend Config::Setter
|
10
|
+
end
|
11
|
+
DeepCover::GLOBAL_BINDING = binding
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'node'
|
2
|
+
require_relative 'covered_code'
|
3
|
+
|
4
|
+
module DeepCover
|
5
|
+
# An analyser works on a subset of the original Node AST.
|
6
|
+
# The Root node is always considered part of the subset.
|
7
|
+
# One can iterate this subset with `each_node`, or ask
|
8
|
+
# the analyser for information about a node's children
|
9
|
+
# (i.e. with respect to this subset), or runs for any node
|
10
|
+
# in this subset.
|
11
|
+
|
12
|
+
# An analyser can summarize information with `results`.
|
13
|
+
# While CoveredCodeSource is based on a CoveredCode, all
|
14
|
+
# other analysers are based on another source analyser.
|
15
|
+
|
16
|
+
class Analyser
|
17
|
+
end
|
18
|
+
|
19
|
+
require_relative_dir 'analyser'
|
20
|
+
|
21
|
+
Analyser.include Analyser::IgnoreUncovered, Analyser::Base
|
22
|
+
Node.include Analyser::CoveredCodeSource::NodeExtension
|
23
|
+
CoveredCode.include Analyser::CoveredCodeSource::CoveredCodeExtension
|
24
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module DeepCover
|
2
|
+
module Analyser::Base
|
3
|
+
attr_reader :source, :options
|
4
|
+
|
5
|
+
def initialize(source, **options)
|
6
|
+
@source = source.to_analyser
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
# Basic coercion mechanism
|
11
|
+
def to_analyser
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Looking exclusively at our subset of nodes, returns the node's direct descendants
|
16
|
+
def node_children(node)
|
17
|
+
@source.node_children(node)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the number of runs of the node (assumed to be in our subset)
|
21
|
+
def node_runs(node)
|
22
|
+
@source.node_runs(node)
|
23
|
+
end
|
24
|
+
|
25
|
+
def node_runs_map
|
26
|
+
each_node.map do |node, sub_statements|
|
27
|
+
[node, node_runs(node)]
|
28
|
+
end.to_h
|
29
|
+
end
|
30
|
+
|
31
|
+
# Analyser-specific output
|
32
|
+
def results
|
33
|
+
node_runs_map
|
34
|
+
end
|
35
|
+
|
36
|
+
# Iterates on nodes in the subset.
|
37
|
+
# Yields the node and it's children (within the subset)
|
38
|
+
def each_node(from = covered_code.root, &block)
|
39
|
+
return to_enum(:each_node) unless block_given?
|
40
|
+
children = node_children(from)
|
41
|
+
yield from, children unless from.is_a?(Node::Root)
|
42
|
+
children.each do |child|
|
43
|
+
each_node(child, &block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def covered_code
|
48
|
+
@source.covered_code
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'subset'
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
class Analyser::Branch < Analyser
|
5
|
+
include Analyser::Subset
|
6
|
+
SUBSET_CLASSES = [Node::Branch]
|
7
|
+
|
8
|
+
def results
|
9
|
+
each_node.map do |node, _children|
|
10
|
+
branches_runs = node.branches.map{|b| [b, node_runs(b)]}.to_h
|
11
|
+
[node, branches_runs]
|
12
|
+
end.to_h
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_trivial_if?(node)
|
16
|
+
parent = node.parent
|
17
|
+
parent.is_a?(Node::If) && parent.condition.is_a?(Node::SingletonLiteral)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DeepCover
|
2
|
+
class Analyser::CoveredCodeSource < Analyser
|
3
|
+
attr_reader :covered_code
|
4
|
+
|
5
|
+
def initialize(covered_code)
|
6
|
+
@covered_code = covered_code
|
7
|
+
end
|
8
|
+
|
9
|
+
# Looking exclusively at our subset of nodes, returns the node's direct descendants
|
10
|
+
def node_children(node)
|
11
|
+
node.children_nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the number of runs of the node (assumed to be in our subset)
|
15
|
+
def node_runs(node)
|
16
|
+
node.execution_count if node.executable?
|
17
|
+
end
|
18
|
+
|
19
|
+
module NodeExtension
|
20
|
+
def to_analyser
|
21
|
+
Analyser::CoveredCodeSource.new(covered_code)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module CoveredCodeExtension
|
26
|
+
def to_analyser
|
27
|
+
Analyser::CoveredCodeSource.new(self)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DeepCover
|
2
|
+
module Analyser::IgnoreUncovered
|
3
|
+
def initialize(source, ignore_uncovered: [], **options)
|
4
|
+
super
|
5
|
+
@allow_filters = Array(ignore_uncovered)
|
6
|
+
.map{|kind| :"is_#{kind}?"}
|
7
|
+
.select{|name| respond_to?(name) }
|
8
|
+
.map{|name| method(name)} # So was tempted to write `.map(&method(:method))`!
|
9
|
+
end
|
10
|
+
|
11
|
+
def node_runs(node)
|
12
|
+
runs = super
|
13
|
+
if runs == 0 && @allow_filters.any?{ |f| f[node] }
|
14
|
+
runs = nil
|
15
|
+
end
|
16
|
+
runs
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module DeepCover
|
2
|
+
class Analyser::PerChar < Analyser
|
3
|
+
# Returns an array of characters for each line of code.
|
4
|
+
# Each character is either ' ' (executed), '-' (not executable) or 'x' (not covered)
|
5
|
+
def results
|
6
|
+
buffer = covered_code.buffer
|
7
|
+
bc = buffer.source_lines.map{|line| '-' * line.size}
|
8
|
+
each_node do |node, _children|
|
9
|
+
runs = node_runs(node)
|
10
|
+
if runs != nil
|
11
|
+
node.proper_range.each do |pos|
|
12
|
+
bc[buffer.line_for_position(pos)-1][buffer.column_for_position(pos)] = runs > 0 ? ' ' : 'x'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
bc.zip(buffer.source_lines){|cov, line| cov[line.size..-1] = ''} # remove extraneous character for end lines, in any
|
17
|
+
bc
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DeepCover
|
2
|
+
class Analyser::PerLine < Analyser
|
3
|
+
# Returns an array of runs, one per line.
|
4
|
+
def results
|
5
|
+
disallow_partial = !options.fetch(:allow_partial, true)
|
6
|
+
line_hits = Array.new(covered_code.nb_lines)
|
7
|
+
each_node do |node, _children|
|
8
|
+
if (runs = node_runs(node))
|
9
|
+
node.executed_locs.each do |loc|
|
10
|
+
lineno = loc.line - 1
|
11
|
+
if disallow_partial
|
12
|
+
line_hits[lineno] = 0 if runs == 0
|
13
|
+
next if line_hits[lineno] == 0
|
14
|
+
end
|
15
|
+
line_hits[lineno] = [line_hits[lineno] || 0, runs].max
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
line_hits
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'subset'
|
2
|
+
|
3
|
+
module DeepCover
|
4
|
+
class Analyser::Statement < Analyser
|
5
|
+
include Analyser::Subset
|
6
|
+
# Returns a map of Range => runs
|
7
|
+
def results
|
8
|
+
each_node.map do |node, _sub_statements|
|
9
|
+
[node.expression, node_runs(node)]
|
10
|
+
end.to_h
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def in_subset?(node, parent)
|
16
|
+
is_statement = node.is_statement
|
17
|
+
if node.expression.nil?
|
18
|
+
false
|
19
|
+
elsif is_statement != :if_incompatible
|
20
|
+
is_statement
|
21
|
+
else
|
22
|
+
!compatible_runs?(node_runs(parent), node_runs(node))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def compatible_runs?(expression_runs, sub_expression_runs)
|
27
|
+
sub_expression_runs.nil? ||
|
28
|
+
(sub_expression_runs == 0) == (expression_runs == 0)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module DeepCover
|
2
|
+
# A module to create a subset from a criteria called `in_subset?`
|
3
|
+
# Including classes can refine it, or specify SUBSET_CLASSES
|
4
|
+
module Analyser::Subset
|
5
|
+
def node_children(node)
|
6
|
+
find_children(node)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def find_children(from, parent = from)
|
11
|
+
@source.node_children(from).flat_map do |node|
|
12
|
+
if in_subset?(node, parent)
|
13
|
+
[node]
|
14
|
+
else
|
15
|
+
find_children(node, parent)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def in_subset?(node, _parent)
|
21
|
+
self.class::SUBSET_CLASSES.any?{|klass| node.is_a?(klass)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|