deep-cover 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/deep-cover/deep-cover.svg?branch=master)](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
|