mudguard 0.1.7 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.mudguard.yml +6 -0
- data/.rubocop.yml +2 -0
- data/Gemfile.lock +25 -23
- data/Guardfile +1 -1
- data/README.md +62 -9
- data/lib/mudguard/application/application.rb +19 -5
- data/lib/mudguard/domain/const_visitor.rb +20 -0
- data/lib/mudguard/domain/consts.rb +84 -0
- data/lib/mudguard/domain/dependencies.rb +47 -0
- data/lib/mudguard/domain/dependency.rb +2 -4
- data/lib/mudguard/domain/dependency_visitor.rb +32 -0
- data/lib/mudguard/domain/policies.rb +27 -26
- data/lib/mudguard/domain/source.rb +50 -70
- data/lib/mudguard/domain/source_policies.rb +8 -0
- data/lib/mudguard/domain/source_processor.rb +92 -0
- data/lib/mudguard/domain/texts.rb +42 -0
- data/lib/mudguard/infrastructure/cli/controller.rb +50 -8
- data/lib/mudguard/infrastructure/cli/notification_adapter.rb +11 -3
- data/lib/mudguard/infrastructure/persistence/.mudguard.template.yml +6 -0
- data/lib/mudguard/infrastructure/persistence/policy_file.rb +28 -5
- data/lib/mudguard/infrastructure/persistence/project_repository.rb +31 -0
- data/lib/mudguard/infrastructure/persistence/ruby_files.rb +9 -7
- data/lib/mudguard/infrastructure/rake/task.rb +28 -0
- data/lib/mudguard/version.rb +1 -1
- data/mudguard.gemspec +1 -0
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44d7f2ff1e9b179d3d64e1263ab13cb217497fce8dd174f5b0ee5ef4cdf1c67f
|
4
|
+
data.tar.gz: 6597fed3f89fa210951650bec390b8c6a5196db0806b3e5b2daaba9406693d21
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0eecb391426607e7650a7da55c2f470acf06b44da2327c7853c1215dc2a46fcdb592cedf3a7f383d57772b16017eb5f2b0c9edb0b87c4dc1d9d3821749a7a1a1
|
7
|
+
data.tar.gz: d69e2ec7b320c04a6e01475f7973992204f08653d84316865f903363b72e51928c15f98601c1b0e2a9009f712612556e0fc33811bf271c707d6cbcc92def2eba
|
data/.gitignore
CHANGED
data/.mudguard.yml
ADDED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mudguard (0.
|
4
|
+
mudguard (0.2.3)
|
5
5
|
parser (~> 2.7)
|
6
|
+
rake (~> 13.0)
|
6
7
|
|
7
8
|
GEM
|
8
9
|
remote: https://rubygems.org/
|
9
10
|
specs:
|
10
11
|
ast (2.4.0)
|
11
|
-
byebug (11.
|
12
|
+
byebug (11.1.3)
|
12
13
|
coderay (1.1.2)
|
13
14
|
diff-lcs (1.3)
|
14
15
|
ffi (1.12.2)
|
15
16
|
formatador (0.2.5)
|
16
|
-
guard (2.16.
|
17
|
+
guard (2.16.2)
|
17
18
|
formatador (>= 0.2.4)
|
18
19
|
listen (>= 2.7, < 4.0)
|
19
20
|
lumberjack (>= 1.0.12, < 2.0)
|
@@ -27,31 +28,30 @@ GEM
|
|
27
28
|
guard (~> 2.1)
|
28
29
|
guard-compat (~> 1.1)
|
29
30
|
rspec (>= 2.99.0, < 4.0)
|
30
|
-
jaro_winkler (1.5.4)
|
31
31
|
listen (3.2.1)
|
32
32
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
33
33
|
rb-inotify (~> 0.9, >= 0.9.10)
|
34
|
-
lumberjack (1.2.
|
35
|
-
method_source (0.
|
34
|
+
lumberjack (1.2.5)
|
35
|
+
method_source (1.0.0)
|
36
36
|
nenv (0.3.0)
|
37
37
|
notiffany (0.1.3)
|
38
38
|
nenv (~> 0.1)
|
39
39
|
shellany (~> 0.0)
|
40
40
|
parallel (1.19.1)
|
41
|
-
parser (2.7.
|
41
|
+
parser (2.7.1.3)
|
42
42
|
ast (~> 2.4.0)
|
43
|
-
pry (0.
|
44
|
-
coderay (~> 1.1
|
45
|
-
method_source (~>
|
46
|
-
pry-byebug (3.
|
43
|
+
pry (0.13.1)
|
44
|
+
coderay (~> 1.1)
|
45
|
+
method_source (~> 1.0)
|
46
|
+
pry-byebug (3.9.0)
|
47
47
|
byebug (~> 11.0)
|
48
|
-
pry (~> 0.
|
49
|
-
pry-doc (1.
|
48
|
+
pry (~> 0.13.0)
|
49
|
+
pry-doc (1.1.0)
|
50
50
|
pry (~> 0.11)
|
51
51
|
yard (~> 0.9.11)
|
52
52
|
rainbow (3.0.0)
|
53
53
|
rake (13.0.1)
|
54
|
-
rb-fsevent (0.10.
|
54
|
+
rb-fsevent (0.10.4)
|
55
55
|
rb-inotify (0.10.1)
|
56
56
|
ffi (~> 1.0)
|
57
57
|
rexml (3.2.4)
|
@@ -59,28 +59,30 @@ GEM
|
|
59
59
|
rspec-core (~> 3.9.0)
|
60
60
|
rspec-expectations (~> 3.9.0)
|
61
61
|
rspec-mocks (~> 3.9.0)
|
62
|
-
rspec-core (3.9.
|
63
|
-
rspec-support (~> 3.9.
|
64
|
-
rspec-expectations (3.9.
|
62
|
+
rspec-core (3.9.2)
|
63
|
+
rspec-support (~> 3.9.3)
|
64
|
+
rspec-expectations (3.9.2)
|
65
65
|
diff-lcs (>= 1.2.0, < 2.0)
|
66
66
|
rspec-support (~> 3.9.0)
|
67
67
|
rspec-mocks (3.9.1)
|
68
68
|
diff-lcs (>= 1.2.0, < 2.0)
|
69
69
|
rspec-support (~> 3.9.0)
|
70
|
-
rspec-support (3.9.
|
71
|
-
rubocop (0.
|
72
|
-
jaro_winkler (~> 1.5.1)
|
70
|
+
rspec-support (3.9.3)
|
71
|
+
rubocop (0.84.0)
|
73
72
|
parallel (~> 1.10)
|
74
73
|
parser (>= 2.7.0.1)
|
75
74
|
rainbow (>= 2.2.2, < 4.0)
|
76
75
|
rexml
|
76
|
+
rubocop-ast (>= 0.0.3)
|
77
77
|
ruby-progressbar (~> 1.7)
|
78
|
-
unicode-display_width (>= 1.4.0, <
|
78
|
+
unicode-display_width (>= 1.4.0, < 2.0)
|
79
|
+
rubocop-ast (0.0.3)
|
80
|
+
parser (>= 2.7.0.1)
|
79
81
|
ruby-progressbar (1.10.1)
|
80
82
|
shellany (0.0.1)
|
81
83
|
thor (1.0.1)
|
82
|
-
unicode-display_width (1.
|
83
|
-
yard (0.9.
|
84
|
+
unicode-display_width (1.7.0)
|
85
|
+
yard (0.9.25)
|
84
86
|
|
85
87
|
PLATFORMS
|
86
88
|
ruby
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
# Mudguard
|
2
2
|
|
3
|
-
|
3
|
+
**Mudguard** is a Ruby static code analyzer that investigates the coupling of modules in your source code. Mudguard
|
4
|
+
prevents your code from becoming a [Big ball of mud](http://www.laputan.org/mud/) and helps implementing and
|
5
|
+
maintaining an intended design.
|
4
6
|
|
5
|
-
|
7
|
+
Mudguard is inspired by
|
8
|
+
* [Rubocop](https://github.com/rubocop-hq/rubocop)
|
9
|
+
* Clean Architecture by Robert C. Martin (ISBN-13: 978-0-13-449416-6)
|
10
|
+
* having seen many larger code bases in a sorrow state
|
11
|
+
* my inability to efficiently break up application code into gems or rails engines
|
6
12
|
|
7
|
-
|
13
|
+
# Installation
|
8
14
|
|
9
15
|
Add this line to your application's Gemfile:
|
10
16
|
|
@@ -20,19 +26,66 @@ Or install it yourself as:
|
|
20
26
|
|
21
27
|
$ gem install mudguard
|
22
28
|
|
23
|
-
##
|
29
|
+
## Quickstart
|
30
|
+
```
|
31
|
+
$ cd my/cool/ruby/project
|
32
|
+
$ mudguard
|
33
|
+
```
|
34
|
+
|
35
|
+
Mudguard creates on it's first run a file called **.mudguard.yml** which is used to configure **design policies**
|
36
|
+
governing your code. A .mudguard.yml could look like:
|
37
|
+
```
|
38
|
+
'./**/*.rb': # all code
|
39
|
+
# all code can depend on siblings in same module or class
|
40
|
+
- ^(::.+)(::[^:]+)* -> \1(::[^:]+)*$
|
41
|
+
# all code can depend on certain types
|
42
|
+
- .* -> ::(String|SecureRandom|Container|Time|Date|JSON|Object|Hash)$
|
43
|
+
|
44
|
+
# in all code module Application can depend on module Domain
|
45
|
+
- ^::Application(::[^:]+)* -> ::(Domain)(::[^:]+)*$
|
46
|
+
# in all code module Infrastructure can depend on module Application
|
47
|
+
- ^::Infrastructure(::[^:]+)* -> ::(Application)(::[^:]+)*$
|
48
|
+
|
49
|
+
'spec/**/*.rb': # spec code
|
50
|
+
# Only in test code can we use RSpec, Timecop and Exception
|
51
|
+
- .* -> ::(RSpec|Timecop|Exception)$
|
52
|
+
```
|
53
|
+
|
54
|
+
## The .mudguard.yml
|
55
|
+
The .mudguard.yml defines scopes and policies. A policy defines which dependencies are inside that scope permissible:
|
56
|
+
```
|
57
|
+
scope1:
|
58
|
+
- policy 1
|
59
|
+
- policy 2
|
60
|
+
scope2:
|
61
|
+
- policy 3
|
62
|
+
```
|
63
|
+
The **scope** is a [glob-pattern](https://en.wikipedia.org/wiki/Glob_(programming)) and defines to which files a set
|
64
|
+
of policies apply. For example a value `lib/docker/**/*.rb` defines a scope containing all Ruby files inside
|
65
|
+
folder lib/docker.
|
24
66
|
|
25
|
-
|
67
|
+
A **policy** is a [regular expression](https://ruby-doc.org/core-2.5.1/Regexp.html) matching one or a set of
|
68
|
+
dependencies that are permissible inside that scope. Mudguard represents a **dependency** as a symbol in form
|
69
|
+
of `X -> Y` meaning "X depends on Y". See following examples:
|
26
70
|
|
27
|
-
|
71
|
+
| Policy | matched Dependency| Explanation |
|
72
|
+
| --- | --- | --- |
|
73
|
+
| `^::A -> ::B$` | `::A -> ::B` |Module A can depend on module or constant B |
|
74
|
+
| `^::Infrastructure -> ::Rails$` | `::Infrastructure -> ::Rails` |Module Infrastructure can depend on Rails |
|
75
|
+
| `^::Infrastructure(::[^:]+)* -> ::ActiveRecord$` | `::Infrastructure::Logger -> ::ActiveRecord` or `::Infrastructure::Persistence -> ::ActiveRecord`|Module Infrastructure and its submodules can depend on ActiveRecord |
|
76
|
+
| `.* -> ::Exception$` | `::Api::Gateway -> ::Exception` or `::Logger -> ::Gateway` |Any module can depend on class Exception |
|
28
77
|
|
29
|
-
|
78
|
+
Any dependency for which Mudguard doesn't find a matching policy is reported as an impermissible dependency.
|
30
79
|
|
31
|
-
|
80
|
+
## Outlook
|
81
|
+
Using regular expressions as policies is very flexible but difficult to use. Furthermore certain patterns used in
|
82
|
+
the regular expressions do repeat. It might be useful to replace regular expressions in future with a specific language
|
83
|
+
to describe permissible dependencies. Any thoughts on that?
|
32
84
|
|
33
85
|
## Contributing
|
34
86
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
87
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Enceradeira/mudguard.
|
88
|
+
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.
|
36
89
|
|
37
90
|
## License
|
38
91
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../infrastructure/persistence/
|
4
|
-
require_relative "../
|
3
|
+
require_relative "../infrastructure/persistence/project_repository"
|
4
|
+
require_relative "../domain/policies"
|
5
5
|
|
6
6
|
# Contains methods to check if your project is a bit muddy
|
7
7
|
module Mudguard
|
@@ -9,10 +9,24 @@ module Mudguard
|
|
9
9
|
module Application
|
10
10
|
class << self
|
11
11
|
def check(project_path, notification)
|
12
|
-
|
13
|
-
|
12
|
+
create_policies(project_path) do |policies|
|
13
|
+
policies.check(notification)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def print_allowed_dependencies(project_path, notification)
|
18
|
+
create_policies(project_path) do |policies|
|
19
|
+
policies.print_allowed_dependencies(notification)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
14
24
|
|
15
|
-
|
25
|
+
def create_policies(project_path)
|
26
|
+
repo = Infrastructure::Persistence::ProjectRepository
|
27
|
+
source_policies = repo.load_source_policies(project_path)
|
28
|
+
policies = Domain::Policies.new(source_policies: source_policies)
|
29
|
+
yield policies
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mudguard
|
4
|
+
module Domain
|
5
|
+
# Transforms AST-Nodes into Strings denoting consts
|
6
|
+
class ConstVisitor
|
7
|
+
def initialize
|
8
|
+
@consts = []
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :consts
|
12
|
+
|
13
|
+
def visit_dependency(_, __, ___); end # rubocop:disable Naming/MethodParameterName
|
14
|
+
|
15
|
+
def visit_const_declaration(_location, const_name, module_name)
|
16
|
+
@consts << "#{module_name}#{const_name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "error"
|
4
|
+
|
5
|
+
module Mudguard
|
6
|
+
module Domain
|
7
|
+
# Knows all constants of the project
|
8
|
+
class Consts
|
9
|
+
def initialize(sources:)
|
10
|
+
@consts = sources.flat_map(&:find_consts)
|
11
|
+
.each_with_object(create_hash_of_hash) do |c, a|
|
12
|
+
path = split_hierarchy(c)
|
13
|
+
const_name = path.last
|
14
|
+
module_names = path.take(path.count - 1)
|
15
|
+
sub_module = module_names.reduce(a) { |h, m| h[m] }
|
16
|
+
sub_module[const_name] = {} unless sub_module.key?(const_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve(module_name, const_name)
|
21
|
+
raise Error, "const_name is undefined" if const_name.empty?
|
22
|
+
|
23
|
+
path = split_hierarchy(module_name)
|
24
|
+
if module_name.empty?
|
25
|
+
# not in a module therefor const can only be defined in the root module (::)
|
26
|
+
qualified_path(const_name)
|
27
|
+
else
|
28
|
+
# analyse module hierarchy to find fully qualified const name
|
29
|
+
# resolve_in_modules(const_name, path)
|
30
|
+
const_path = const_name.split(SEPARATOR).drop(1)
|
31
|
+
find_const_deeper("", path, @consts, const_path) || qualified_path(const_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
SEPARATOR = "::"
|
38
|
+
|
39
|
+
def create_hash_of_hash
|
40
|
+
Hash.new { |h, k| h[k] = create_hash_of_hash }
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_const_deeper(current_module, remaining_modules, consts, const_path)
|
44
|
+
return if consts.nil? || consts.empty?
|
45
|
+
|
46
|
+
if remaining_modules.any?
|
47
|
+
# Move deeper toward target module
|
48
|
+
next_module = remaining_modules.first
|
49
|
+
next_remaining = remaining_modules.drop(1)
|
50
|
+
next_consts = consts[next_module]
|
51
|
+
found_const = find_const_deeper(next_module, next_remaining, next_consts, const_path)
|
52
|
+
return join_path(current_module, found_const) if found_const
|
53
|
+
end
|
54
|
+
|
55
|
+
find_const(current_module, consts, const_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_const(current_module, consts, const_path)
|
59
|
+
const_name = const_path.first
|
60
|
+
if const_path.length == 1 && consts.key?(const_name)
|
61
|
+
# const defined in current_module
|
62
|
+
return join_path(current_module, const_name)
|
63
|
+
end
|
64
|
+
|
65
|
+
# backward search (along const_path only)
|
66
|
+
next_path = const_path.drop(1)
|
67
|
+
found_const = find_const_deeper(const_name, next_path, consts[const_name], next_path)
|
68
|
+
found_const ? join_path(current_module, found_const) : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def join_path(module_name, const_name)
|
72
|
+
"#{module_name}#{SEPARATOR}#{const_name}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def qualified_path(const_name)
|
76
|
+
const_name =~ /^#{SEPARATOR}/ ? const_name : "#{SEPARATOR}#{const_name}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def split_hierarchy(module_name)
|
80
|
+
module_name.split(SEPARATOR).drop(1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "texts"
|
4
|
+
|
5
|
+
module Mudguard
|
6
|
+
module Domain
|
7
|
+
# Executes operation on a set of dependencies
|
8
|
+
class Dependencies
|
9
|
+
include Texts
|
10
|
+
|
11
|
+
def initialize(policies:, notification:)
|
12
|
+
@policies = policies.map { |p| /^#{p}/x }
|
13
|
+
@notification = notification
|
14
|
+
end
|
15
|
+
|
16
|
+
def check(dependencies)
|
17
|
+
select_dependencies(dependencies) do |dependency, is_allowed|
|
18
|
+
add_message(dependency, dependency_not_allowed(dependency.dependency)) unless is_allowed
|
19
|
+
!is_allowed
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def print_allowed(dependencies)
|
24
|
+
select_dependencies(dependencies) do |dependency, is_allowed|
|
25
|
+
add_message(dependency, dependency_allowed(dependency.dependency)) if is_allowed
|
26
|
+
is_allowed
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def add_message(dependency, message)
|
33
|
+
@notification.add(dependency.location, message)
|
34
|
+
end
|
35
|
+
|
36
|
+
def select_dependencies(dependencies)
|
37
|
+
dependencies.select do |dependency|
|
38
|
+
yield dependency, dependency_allowed?(dependency)
|
39
|
+
end.count
|
40
|
+
end
|
41
|
+
|
42
|
+
def dependency_allowed?(dependency)
|
43
|
+
@policies.any? { |p| dependency.match(p) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -4,6 +4,8 @@ module Mudguard
|
|
4
4
|
module Domain
|
5
5
|
# A Dependency between Modules
|
6
6
|
class Dependency
|
7
|
+
attr_reader :location, :dependency
|
8
|
+
|
7
9
|
def initialize(location: nil, dependency:)
|
8
10
|
@location = location
|
9
11
|
@dependency = dependency
|
@@ -13,10 +15,6 @@ module Mudguard
|
|
13
15
|
"{#{@location}, #{@dependency}}"
|
14
16
|
end
|
15
17
|
|
16
|
-
def to_s
|
17
|
-
"#{@location} #{@dependency}"
|
18
|
-
end
|
19
|
-
|
20
18
|
def match(policy)
|
21
19
|
@dependency.match(policy)
|
22
20
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mudguard
|
4
|
+
module Domain
|
5
|
+
# Transforms AST-Nodes into Dependencies
|
6
|
+
class DependencyVisitor
|
7
|
+
def initialize(consts:)
|
8
|
+
@consts = consts
|
9
|
+
@dependencies = []
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :dependencies
|
13
|
+
|
14
|
+
def visit_dependency(location, const_name, module_name)
|
15
|
+
qualified_const_name = @consts.resolve(module_name, const_name)
|
16
|
+
return [] unless qualified_const_name&.include?("::")
|
17
|
+
|
18
|
+
dependency = if module_name.empty?
|
19
|
+
"->#{qualified_const_name}"
|
20
|
+
else
|
21
|
+
"#{module_name}->#{qualified_const_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
@dependencies << Dependency.new(location: location, dependency: dependency)
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Naming/MethodParameterName
|
28
|
+
def visit_const_declaration(_, __, ___); end
|
29
|
+
# rubocop:enable Naming/MethodParameterName
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,47 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "dependencies"
|
4
|
+
require_relative "texts"
|
5
|
+
require_relative "consts"
|
6
|
+
|
3
7
|
module Mudguard
|
4
8
|
module Domain
|
5
9
|
# Contains the policies to be enforced
|
6
10
|
class Policies
|
7
|
-
|
8
|
-
|
11
|
+
include Texts
|
12
|
+
|
13
|
+
def initialize(source_policies: [])
|
14
|
+
@source_policies = source_policies
|
9
15
|
end
|
10
16
|
|
11
|
-
def check(
|
12
|
-
result =
|
17
|
+
def check(notification)
|
18
|
+
result = analyse(:check, notification)
|
13
19
|
|
14
|
-
count = result[:
|
15
|
-
violations = result[:
|
20
|
+
count = result[:sources_count]
|
21
|
+
violations = result[:analyser_count]
|
16
22
|
|
17
|
-
|
18
|
-
problems = pluralize("problem", violations)
|
19
|
-
notification.add("#{count} #{files} inspected, #{violations} #{problems} detected")
|
23
|
+
notification.add(nil, summary(count, violations))
|
20
24
|
violations.zero?
|
21
25
|
end
|
22
26
|
|
23
|
-
|
27
|
+
def print_allowed_dependencies(notification)
|
28
|
+
result = analyse(:print_allowed, notification)
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
end
|
30
|
+
count = result[:sources_count]
|
31
|
+
violations = result[:analyser_count]
|
28
32
|
|
29
|
-
|
30
|
-
sources.each_with_object(count: 0, violations: 0) do |source, result|
|
31
|
-
result[:count] += 1
|
32
|
-
dependencies = source.find_mod_dependencies
|
33
|
-
result[:violations] += check_dependencies(dependencies, notification)
|
34
|
-
end
|
33
|
+
notification.add(nil, dependency_summary(count, violations))
|
35
34
|
end
|
36
35
|
|
37
|
-
|
38
|
-
dependencies.reject { |d| check_dependency(d, notification) }.count
|
39
|
-
end
|
36
|
+
private
|
40
37
|
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
def analyse(method, notification)
|
39
|
+
consts = Consts.new(sources: @source_policies.map(&:source))
|
40
|
+
@source_policies.each_with_object(sources_count: 0, analyser_count: 0) do |sp, result|
|
41
|
+
analyser = Dependencies.new(policies: sp.policies, notification: notification)
|
42
|
+
dependencies = sp.source.find_mod_dependencies(consts)
|
43
|
+
result[:sources_count] += 1
|
44
|
+
result[:analyser_count] += analyser.send(method, dependencies)
|
45
|
+
end
|
45
46
|
end
|
46
47
|
end
|
47
48
|
end
|
@@ -1,105 +1,85 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
orig = $VERBOSE
|
4
|
+
begin
|
5
|
+
$VERBOSE = nil # silence warning when ruby versions are not exactly matching
|
6
|
+
require "parser/current"
|
7
|
+
ensure
|
8
|
+
$VERBOSE = orig
|
9
|
+
end
|
10
|
+
|
11
|
+
require_relative "dependency"
|
12
|
+
require_relative "dependency_visitor"
|
13
|
+
require_relative "const_visitor"
|
14
|
+
require_relative "error"
|
15
|
+
require_relative "source_processor"
|
5
16
|
|
6
17
|
module Mudguard
|
7
18
|
module Domain
|
8
19
|
# Represents a Ruby source file
|
9
20
|
class Source
|
10
|
-
def initialize(location:
|
11
|
-
@
|
21
|
+
def initialize(location:, code_loader: -> { "" })
|
22
|
+
@code_loader = code_loader
|
12
23
|
@location = location
|
13
24
|
end
|
14
25
|
|
15
26
|
def ==(other)
|
16
|
-
@
|
27
|
+
@location == other.instance_eval { @location }
|
17
28
|
end
|
18
29
|
|
19
|
-
def
|
20
|
-
@location
|
30
|
+
def hash
|
31
|
+
@location.hash
|
21
32
|
end
|
22
33
|
|
23
|
-
def
|
24
|
-
|
25
|
-
root = Parser::CurrentRuby.parse(@code)
|
26
|
-
rescue Parser::SyntaxError
|
27
|
-
return []
|
28
|
-
end
|
29
|
-
return [] if root.nil?
|
30
|
-
|
31
|
-
process(root)
|
34
|
+
def eql?(other)
|
35
|
+
@location.eql?(other.instance_eval { @location })
|
32
36
|
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
def process(node, module_name = "")
|
37
|
-
case node
|
38
|
-
when type?(:module)
|
39
|
-
process_module(node.children, module_name)
|
40
|
-
when type?(:class)
|
41
|
-
process_class(node.children, module_name)
|
42
|
-
when type?(:const)
|
43
|
-
create_dependency(module_name, node)
|
44
|
-
else
|
45
|
-
ignore_and_continue(node, module_name)
|
46
|
-
end
|
38
|
+
def inspect
|
39
|
+
@location
|
47
40
|
end
|
48
41
|
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
dependency = if module_name.empty?
|
54
|
-
const_name
|
55
|
-
else
|
56
|
-
"#{module_name}->#{const_name}"
|
57
|
-
end
|
58
|
-
location = "#{@location}:#{node.location.line}"
|
59
|
-
[Dependency.new(location: location, dependency: dependency)]
|
42
|
+
def find_mod_dependencies(consts)
|
43
|
+
visitor = DependencyVisitor.new(consts: consts)
|
44
|
+
visit_ast(visitor)
|
45
|
+
visitor.dependencies
|
60
46
|
end
|
61
47
|
|
62
|
-
def
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else
|
67
|
-
[]
|
68
|
-
end
|
48
|
+
def find_consts
|
49
|
+
visitor = ConstVisitor.new
|
50
|
+
visit_ast(visitor)
|
51
|
+
visitor.consts
|
69
52
|
end
|
70
53
|
|
71
|
-
def
|
72
|
-
|
73
|
-
module_name = if module_name.empty?
|
74
|
-
const_name
|
75
|
-
else
|
76
|
-
"#{module_name}::#{const_name}"
|
77
|
-
end
|
78
|
-
process(children[1], module_name)
|
54
|
+
def location?(glob)
|
55
|
+
@location == glob
|
79
56
|
end
|
80
57
|
|
81
|
-
|
82
|
-
process(children[1], module_name)
|
83
|
-
end
|
58
|
+
private
|
84
59
|
|
85
|
-
|
86
|
-
return nil if children.nil?
|
60
|
+
SYNTAX_ERROR = "error"
|
87
61
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
62
|
+
def ast
|
63
|
+
@ast ||= create_ast
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_ast
|
67
|
+
begin
|
68
|
+
root = Parser::CurrentRuby.parse(code)
|
69
|
+
rescue Parser::SyntaxError
|
70
|
+
return SYNTAX_ERROR
|
94
71
|
end
|
72
|
+
root.nil? ? SYNTAX_ERROR : root
|
95
73
|
end
|
96
74
|
|
97
|
-
def
|
98
|
-
|
75
|
+
def code
|
76
|
+
@code ||= @code_loader.call
|
99
77
|
end
|
100
78
|
|
101
|
-
def
|
102
|
-
|
79
|
+
def visit_ast(visitor)
|
80
|
+
return if ast == SYNTAX_ERROR
|
81
|
+
|
82
|
+
SourceProcessor.new(location: @location).process(ast, visitor)
|
103
83
|
end
|
104
84
|
end
|
105
85
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mudguard
|
4
|
+
module Domain
|
5
|
+
# Processes the interesting parts of the Ast and forwards selected nodes to the visitors
|
6
|
+
class SourceProcessor
|
7
|
+
def initialize(location:)
|
8
|
+
@location = location
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(node, visitor)
|
12
|
+
process_node(node, visitor, "")
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def process_node(node, visitor, module_name) # rubocop:disable Metrics/MethodLength
|
18
|
+
case node
|
19
|
+
when type?(:module)
|
20
|
+
process_module(node, visitor, module_name)
|
21
|
+
when type?(:class)
|
22
|
+
process_module(node, visitor, module_name)
|
23
|
+
when type?(:const)
|
24
|
+
process_const(node, visitor, module_name)
|
25
|
+
when type?(:casgn)
|
26
|
+
process_const_assignment(node, visitor, module_name)
|
27
|
+
else
|
28
|
+
ignore_and_continue(node, visitor, module_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def process_const_assignment(node, visitor, module_name)
|
33
|
+
is_explicit, is_static, const_name = find_const_name(node.children)
|
34
|
+
return unless is_static
|
35
|
+
|
36
|
+
visitor.visit_const_declaration(describe_location(node), const_name,
|
37
|
+
is_explicit ? "" : module_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_const(node, visitor, module_name)
|
41
|
+
is_explicit, is_static, const_name = find_const_name(node.children)
|
42
|
+
return unless is_static
|
43
|
+
|
44
|
+
visitor.visit_dependency(describe_location(node), const_name,
|
45
|
+
is_explicit ? "" : module_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def describe_location(node)
|
49
|
+
"#{@location}:#{node.location.line}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def ignore_and_continue(node, visitor, module_name)
|
53
|
+
return unless node.respond_to?(:children)
|
54
|
+
|
55
|
+
node.children.flat_map { |c| process_node(c, visitor, module_name) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def process_module(node, visitor, module_name)
|
59
|
+
is_explicit, is_static, const_name = find_const_name(node.children[0].children)
|
60
|
+
return unless is_static
|
61
|
+
|
62
|
+
visitor.visit_const_declaration(describe_location(node), const_name, module_name)
|
63
|
+
|
64
|
+
module_name = "#{is_explicit ? '' : module_name}#{const_name}"
|
65
|
+
node.children.drop(1).reject(&:nil?).each do |child_node|
|
66
|
+
process_node(child_node, visitor, module_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def find_const_name(children)
|
71
|
+
return [false, nil] if children.nil? || children.empty?
|
72
|
+
|
73
|
+
first_child = children[0]
|
74
|
+
is_explicit, is_static = find_const_type(first_child)
|
75
|
+
first_child_children = first_child.respond_to?(:children) ? first_child.children : nil
|
76
|
+
_, __, module_name = find_const_name(first_child_children)
|
77
|
+
const_name = children[1].to_s
|
78
|
+
[is_explicit, is_static, "#{module_name}::#{const_name}"]
|
79
|
+
end
|
80
|
+
|
81
|
+
def find_const_type(first_child)
|
82
|
+
is_explicit = type?(:cbase).call(first_child)
|
83
|
+
is_static = is_explicit || first_child.nil? || type?(:const).call(first_child)
|
84
|
+
[is_explicit, is_static]
|
85
|
+
end
|
86
|
+
|
87
|
+
def type?(type)
|
88
|
+
->(n) { n.respond_to?(:type) && n.type == type }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mudguard
|
4
|
+
module Domain
|
5
|
+
# Builds texts to be displayed to a user
|
6
|
+
module Texts
|
7
|
+
def summary(file_count, violation_count)
|
8
|
+
files = pluralize("file", file_count)
|
9
|
+
"#{file_count} #{files} inspected, #{count(violation_count)}\
|
10
|
+
bad #{dependency(violation_count)} detected"
|
11
|
+
end
|
12
|
+
|
13
|
+
def dependency_not_allowed(dependency)
|
14
|
+
"#{dependency} not allowed"
|
15
|
+
end
|
16
|
+
|
17
|
+
def dependency_summary(file_count, dependency_count)
|
18
|
+
files = pluralize("file", file_count)
|
19
|
+
"#{file_count} #{files} inspected, #{count(dependency_count)}\
|
20
|
+
good #{dependency(dependency_count)} detected"
|
21
|
+
end
|
22
|
+
|
23
|
+
def dependency_allowed(dependency)
|
24
|
+
dependency.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def dependency(count)
|
30
|
+
count <= 1 ? "dependency" : "dependencies"
|
31
|
+
end
|
32
|
+
|
33
|
+
def count(count)
|
34
|
+
count.zero? ? "no" : count.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def pluralize(word, count)
|
38
|
+
count == 1 ? word : "#{word}s"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -9,26 +9,68 @@ module Mudguard
|
|
9
9
|
module Cli
|
10
10
|
# Parses the cli arguments
|
11
11
|
class Controller
|
12
|
-
def initialize(view:)
|
12
|
+
def initialize(view:) # rubocop:disable Metrics/MethodLength
|
13
|
+
@cmd = :analyse
|
13
14
|
@view = view
|
14
|
-
@
|
15
|
+
@display_opts = {
|
16
|
+
view: view,
|
17
|
+
compressed: false
|
18
|
+
}
|
15
19
|
@parser = ::OptionParser.new do |opts|
|
16
20
|
opts.banner = "Usage: mudguard [options] [directory]"
|
17
21
|
opts.on("-h", "--help", "Prints this help") do
|
18
|
-
|
19
|
-
|
22
|
+
@cmd = :help
|
23
|
+
end
|
24
|
+
opts.on("-p", "--print", "Prints all allowed dependencies") do
|
25
|
+
@cmd = :print_allowed
|
26
|
+
end
|
27
|
+
opts.on("-c", "--compressed", "Omits printing the same dependency more than once") do
|
28
|
+
@display_opts[:compressed] = true
|
20
29
|
end
|
21
30
|
end
|
22
31
|
end
|
23
32
|
|
24
|
-
def parse!(argv)
|
33
|
+
def parse!(argv) # rubocop:disable Metrics/MethodLength
|
25
34
|
directories = @parser.parse!(argv)
|
26
35
|
|
27
|
-
|
36
|
+
case @cmd
|
37
|
+
when :print_allowed
|
38
|
+
print_allowed_dependencies(directories)
|
39
|
+
when :help
|
40
|
+
help
|
41
|
+
when :analyse
|
42
|
+
check_dependencies(directories)
|
43
|
+
else
|
44
|
+
raise StandardError, "unknown command #{@cmd}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def help
|
51
|
+
@view.print(@parser.to_s)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_dependencies(directories)
|
56
|
+
yield_directories(directories) do |directory, notification|
|
57
|
+
Mudguard::Application.check(directory, notification)
|
58
|
+
end
|
59
|
+
end
|
28
60
|
|
29
|
-
|
61
|
+
def print_allowed_dependencies(directories)
|
62
|
+
yield_directories(directories) do |directory, notification|
|
63
|
+
Mudguard::Application.print_allowed_dependencies(directory, notification)
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def yield_directories(directories)
|
69
|
+
notification = NotificationAdapter.new(**@display_opts)
|
30
70
|
directories = [Dir.pwd] if directories.empty?
|
31
|
-
directories.all?
|
71
|
+
directories.all? do |directory|
|
72
|
+
yield directory, notification
|
73
|
+
end
|
32
74
|
end
|
33
75
|
end
|
34
76
|
end
|
@@ -5,12 +5,20 @@ module Mudguard
|
|
5
5
|
module Cli
|
6
6
|
# Forwards Notification to the view for printing
|
7
7
|
class NotificationAdapter
|
8
|
-
def initialize(view:)
|
8
|
+
def initialize(view:, compressed: false)
|
9
9
|
@view = view
|
10
|
+
@compressed = compressed
|
11
|
+
@printed_texts = Set.new
|
10
12
|
end
|
11
13
|
|
12
|
-
def add(text)
|
13
|
-
@
|
14
|
+
def add(location, text)
|
15
|
+
text = if location.nil? || @compressed
|
16
|
+
text
|
17
|
+
else
|
18
|
+
"#{location} #{text}"
|
19
|
+
end
|
20
|
+
@view.print(text) unless @printed_texts.include?(text)
|
21
|
+
@printed_texts << text
|
14
22
|
end
|
15
23
|
end
|
16
24
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "yaml"
|
3
4
|
require_relative "../../domain/policies"
|
4
5
|
require_relative "../../domain/error"
|
5
6
|
|
@@ -8,14 +9,36 @@ module Mudguard
|
|
8
9
|
module Persistence
|
9
10
|
# A file containing the Mudguard-Policies
|
10
11
|
class PolicyFile
|
11
|
-
|
12
|
-
|
12
|
+
class << self
|
13
|
+
def read(project_path)
|
14
|
+
policy_file = File.join(project_path, ".mudguard.yml")
|
15
|
+
policy_exists = File.exist?(policy_file)
|
13
16
|
|
14
|
-
|
15
|
-
|
17
|
+
unless policy_exists
|
18
|
+
template_file = File.join(__dir__, ".mudguard.template.yml")
|
19
|
+
FileUtils.cp(template_file, policy_file)
|
20
|
+
end
|
21
|
+
|
22
|
+
read_yml(policy_file)
|
16
23
|
end
|
17
24
|
|
18
|
-
|
25
|
+
private
|
26
|
+
|
27
|
+
def read_yml(policy_file)
|
28
|
+
yaml_file = File.read(policy_file)
|
29
|
+
yaml = YAML.safe_load(yaml_file, [Symbol], [], policy_file) || {}
|
30
|
+
yaml.transform_values { |value| (value || []).map(&method(:unsymbolize)) }
|
31
|
+
rescue Psych::SyntaxError => e
|
32
|
+
raise Mudguard::Domain::Error, "#{policy_file} is invalid (#{e.message})"
|
33
|
+
end
|
34
|
+
|
35
|
+
def unsymbolize(dependency)
|
36
|
+
if dependency.is_a?(Symbol)
|
37
|
+
":#{dependency}"
|
38
|
+
else
|
39
|
+
dependency
|
40
|
+
end
|
41
|
+
end
|
19
42
|
end
|
20
43
|
end
|
21
44
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "policy_file"
|
4
|
+
require_relative "ruby_files"
|
5
|
+
require_relative "../../domain/source_policies"
|
6
|
+
|
7
|
+
module Mudguard
|
8
|
+
module Infrastructure
|
9
|
+
module Persistence
|
10
|
+
# Provides access to the persisted source and policies
|
11
|
+
class ProjectRepository
|
12
|
+
class << self
|
13
|
+
def load_source_policies(project_path)
|
14
|
+
file = PolicyFile.read(project_path)
|
15
|
+
scopes = file.flat_map do |patterns, policies|
|
16
|
+
sources = RubyFiles.select(project_path, patterns: [patterns])
|
17
|
+
sources.flat_map { |s| { source: s, policies: policies } }
|
18
|
+
end
|
19
|
+
|
20
|
+
sources = scopes.group_by { |e| e[:source] }
|
21
|
+
|
22
|
+
sources.map do |source, group|
|
23
|
+
policies = group.flat_map { |r| r[:policies] }.uniq
|
24
|
+
Domain::SourcePolicies.new(source: source, policies: policies)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -9,26 +9,28 @@ module Mudguard
|
|
9
9
|
# Provides access to all ruby-files of a project
|
10
10
|
class RubyFiles
|
11
11
|
class << self
|
12
|
-
def
|
12
|
+
def select(project_path, patterns: nil)
|
13
13
|
project_exists = Dir.exist?(project_path)
|
14
14
|
|
15
15
|
unless project_exists
|
16
16
|
raise Mudguard::Domain::Error, "expected project #{project_path} doesn't exists"
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
patterns = [File.join("**", "*.rb")] if patterns.nil?
|
20
|
+
enumerate_files(project_path, patterns)
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
def enumerate_files(project_path)
|
25
|
+
def enumerate_files(project_path, patterns)
|
25
26
|
project_path_name = Pathname.new(project_path)
|
26
|
-
ruby_files = File.join(project_path,
|
27
|
-
Dir.glob(ruby_files).map do |f|
|
27
|
+
ruby_files = patterns.map { |p| File.join(project_path, p) }
|
28
|
+
Dir.glob(ruby_files).select { |f| File.file?(f) }.map do |f|
|
28
29
|
file_path_name = Pathname.new(f)
|
29
30
|
diff_path = file_path_name.relative_path_from(project_path_name).to_s
|
30
|
-
Mudguard::Domain::Source.new(location: File.join("./", diff_path),
|
31
|
-
|
31
|
+
Mudguard::Domain::Source.new(location: File.join("./", diff_path),
|
32
|
+
code_loader: -> { File.read(f) })
|
33
|
+
end
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rake"
|
4
|
+
require "rake/tasklib"
|
5
|
+
require_relative "../../application/application"
|
6
|
+
require_relative "../../infrastructure/cli/notification_adapter"
|
7
|
+
require_relative "../../infrastructure/cli/view"
|
8
|
+
|
9
|
+
module Mudguard
|
10
|
+
module Infrastructure
|
11
|
+
module Rake
|
12
|
+
# Provides Mudguard Rake Tasks
|
13
|
+
class Task < ::Rake::TaskLib
|
14
|
+
def initialize(project_dir: Dir.pwd)
|
15
|
+
@project_dir = project_dir
|
16
|
+
|
17
|
+
desc "Run Mudguard"
|
18
|
+
task(:mudguard) do
|
19
|
+
view = Mudguard::Infrastructure::Cli::View.new
|
20
|
+
notification = Mudguard::Infrastructure::Cli::NotificationAdapter.new(view: view)
|
21
|
+
ok = Application.check(@project_dir, notification)
|
22
|
+
abort unless ok
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/mudguard/version.rb
CHANGED
data/mudguard.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mudguard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jorg Jenni
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -150,6 +150,20 @@ dependencies:
|
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '2.7'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '13.0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '13.0'
|
153
167
|
description:
|
154
168
|
email:
|
155
169
|
- jorg.jenni@jennius.co.uk
|
@@ -160,6 +174,7 @@ extra_rdoc_files: []
|
|
160
174
|
files:
|
161
175
|
- ".envrc"
|
162
176
|
- ".gitignore"
|
177
|
+
- ".mudguard.yml"
|
163
178
|
- ".rubocop.yml"
|
164
179
|
- ".ruby-version"
|
165
180
|
- CODE_OF_CONDUCT.md
|
@@ -172,15 +187,25 @@ files:
|
|
172
187
|
- exe/mudguard
|
173
188
|
- lib/mudguard.rb
|
174
189
|
- lib/mudguard/application/application.rb
|
190
|
+
- lib/mudguard/domain/const_visitor.rb
|
191
|
+
- lib/mudguard/domain/consts.rb
|
192
|
+
- lib/mudguard/domain/dependencies.rb
|
175
193
|
- lib/mudguard/domain/dependency.rb
|
194
|
+
- lib/mudguard/domain/dependency_visitor.rb
|
176
195
|
- lib/mudguard/domain/error.rb
|
177
196
|
- lib/mudguard/domain/policies.rb
|
178
197
|
- lib/mudguard/domain/source.rb
|
198
|
+
- lib/mudguard/domain/source_policies.rb
|
199
|
+
- lib/mudguard/domain/source_processor.rb
|
200
|
+
- lib/mudguard/domain/texts.rb
|
179
201
|
- lib/mudguard/infrastructure/cli/controller.rb
|
180
202
|
- lib/mudguard/infrastructure/cli/notification_adapter.rb
|
181
203
|
- lib/mudguard/infrastructure/cli/view.rb
|
204
|
+
- lib/mudguard/infrastructure/persistence/.mudguard.template.yml
|
182
205
|
- lib/mudguard/infrastructure/persistence/policy_file.rb
|
206
|
+
- lib/mudguard/infrastructure/persistence/project_repository.rb
|
183
207
|
- lib/mudguard/infrastructure/persistence/ruby_files.rb
|
208
|
+
- lib/mudguard/infrastructure/rake/task.rb
|
184
209
|
- lib/mudguard/version.rb
|
185
210
|
- lib/tasks/rubocop.rake
|
186
211
|
- lib/tasks/test.rake
|