mudguard 0.1.6 → 0.2.2
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 +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 +81 -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 -58
- 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 +2 -1
- metadata +30 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9eb5bc544f7c9cfd9d472be7531d3dcf0c9f66ef53b30333ffa60fdd09092e6a
|
|
4
|
+
data.tar.gz: 4e3a47b2512035f09678e8b1b3670a90c1f150f995ca57b9b13ce569cadc6eda
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3e0818bd960ef30419b87e98169e7dbfe791ae1c5a9be458100c21a4a2dcc9cb5d615a9449d929ac1d0b45bc23059d4e218950df0fbdc737064f9a8198d0ddd6
|
|
7
|
+
data.tar.gz: d099a3fe5e9f98a7c6cc02882d73177d142dc77bbc39d28d48d550ff1967082f60c0b4afbcdf021c574fa506c581bf28f6b465b74e3d9262411eeedd513f4e57
|
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.2)
|
|
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,81 @@
|
|
|
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
|
|
11
|
+
.flat_map(&:find_consts)
|
|
12
|
+
.each_with_object(Hash.new { |h, k| h[k] = {} }) do |c, a|
|
|
13
|
+
path = split_hierarchy(c)
|
|
14
|
+
const_name = path.last
|
|
15
|
+
module_names = path.take(path.count - 1)
|
|
16
|
+
sub_module = module_names.reduce(a) { |h, m| h[m] }
|
|
17
|
+
sub_module[const_name] = {} unless sub_module.key?(const_name)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resolve(module_name, const_name)
|
|
22
|
+
raise Error, "const_name is undefined" if const_name.empty?
|
|
23
|
+
|
|
24
|
+
path = split_hierarchy(module_name)
|
|
25
|
+
if module_name.empty?
|
|
26
|
+
# not in a module therefor const can only be defined in the root module (::)
|
|
27
|
+
qualified_path(const_name)
|
|
28
|
+
else
|
|
29
|
+
# analyse module hierarchy to find fully qualified const name
|
|
30
|
+
# resolve_in_modules(const_name, path)
|
|
31
|
+
const_path = const_name.split(SEPARATOR).drop(1)
|
|
32
|
+
find_const_deeper("", path, @consts, const_path) || qualified_path(const_name)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
SEPARATOR = "::"
|
|
39
|
+
|
|
40
|
+
def find_const_deeper(current_module, remaining_modules, consts, const_path)
|
|
41
|
+
return if consts.nil?
|
|
42
|
+
|
|
43
|
+
if remaining_modules.any?
|
|
44
|
+
# Move deeper toward target module
|
|
45
|
+
next_module = remaining_modules.first
|
|
46
|
+
next_remaining = remaining_modules.drop(1)
|
|
47
|
+
next_consts = consts[next_module]
|
|
48
|
+
found_const = find_const_deeper(next_module, next_remaining, next_consts, const_path)
|
|
49
|
+
return join_path(current_module, found_const) if found_const
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
find_const(current_module, consts, const_path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def find_const(current_module, consts, const_path)
|
|
56
|
+
const_name = const_path.first
|
|
57
|
+
if const_path.length == 1 && consts.key?(const_name)
|
|
58
|
+
# const defined in current_module
|
|
59
|
+
return join_path(current_module, const_name)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# backward search (along const_path only)
|
|
63
|
+
next_path = const_path.drop(1)
|
|
64
|
+
found_const = find_const_deeper(const_name, next_path, consts[const_name], next_path)
|
|
65
|
+
found_const ? join_path(current_module, found_const) : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def join_path(module_name, const_name)
|
|
69
|
+
"#{module_name}#{SEPARATOR}#{const_name}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def qualified_path(const_name)
|
|
73
|
+
const_name =~ /^#{SEPARATOR}/ ? const_name : "#{SEPARATOR}#{const_name}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def split_hierarchy(module_name)
|
|
77
|
+
module_name.split(SEPARATOR).drop(1)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
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,93 +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)
|
|
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
|
-
|
|
42
|
+
def find_mod_dependencies(consts)
|
|
43
|
+
visitor = DependencyVisitor.new(consts: consts)
|
|
44
|
+
visit_ast(visitor)
|
|
45
|
+
visitor.dependencies
|
|
53
46
|
end
|
|
54
47
|
|
|
55
|
-
def
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
else
|
|
60
|
-
[]
|
|
61
|
-
end
|
|
48
|
+
def find_consts
|
|
49
|
+
visitor = ConstVisitor.new
|
|
50
|
+
visit_ast(visitor)
|
|
51
|
+
visitor.consts
|
|
62
52
|
end
|
|
63
53
|
|
|
64
|
-
def
|
|
65
|
-
|
|
66
|
-
process(children[1], module_name)
|
|
54
|
+
def location?(glob)
|
|
55
|
+
@location == glob
|
|
67
56
|
end
|
|
68
57
|
|
|
69
|
-
|
|
70
|
-
process(children[1], module_name)
|
|
71
|
-
end
|
|
58
|
+
private
|
|
72
59
|
|
|
73
|
-
|
|
74
|
-
return nil if children.nil?
|
|
60
|
+
SYNTAX_ERROR = "error"
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
82
71
|
end
|
|
72
|
+
root.nil? ? SYNTAX_ERROR : root
|
|
83
73
|
end
|
|
84
74
|
|
|
85
|
-
def
|
|
86
|
-
|
|
75
|
+
def code
|
|
76
|
+
@code ||= @code_loader.call
|
|
87
77
|
end
|
|
88
78
|
|
|
89
|
-
def
|
|
90
|
-
|
|
79
|
+
def visit_ast(visitor)
|
|
80
|
+
return if ast == SYNTAX_ERROR
|
|
81
|
+
|
|
82
|
+
SourceProcessor.new(location: @location).process(ast, visitor)
|
|
91
83
|
end
|
|
92
84
|
end
|
|
93
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
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
|
|
13
13
|
spec.summary = "mudguard helps your ruby project not becoming a "\
|
|
14
14
|
"'Big ball of mud'"
|
|
15
|
-
spec.homepage = "https://
|
|
15
|
+
spec.homepage = "https://github.com/Enceradeira/mudguard"
|
|
16
16
|
spec.license = "MIT"
|
|
17
17
|
|
|
18
18
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
@@ -40,4 +40,5 @@ Gem::Specification.new do |spec|
|
|
|
40
40
|
spec.add_development_dependency "rubocop", "~>0.80"
|
|
41
41
|
|
|
42
42
|
spec.add_dependency "parser", "~>2.7"
|
|
43
|
+
spec.add_dependency "rake", "~> 13.0"
|
|
43
44
|
end
|
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.2
|
|
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,26 +187,36 @@ 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
|
|
187
212
|
- mudguard.gemspec
|
|
188
213
|
- pkg/mudguard-0.1.0.gem
|
|
189
|
-
homepage: https://
|
|
214
|
+
homepage: https://github.com/Enceradeira/mudguard
|
|
190
215
|
licenses:
|
|
191
216
|
- MIT
|
|
192
217
|
metadata:
|
|
193
|
-
homepage_uri: https://
|
|
194
|
-
source_code_uri: https://
|
|
218
|
+
homepage_uri: https://github.com/Enceradeira/mudguard
|
|
219
|
+
source_code_uri: https://github.com/Enceradeira/mudguard
|
|
195
220
|
post_install_message:
|
|
196
221
|
rdoc_options: []
|
|
197
222
|
require_paths:
|