codeowner_validator 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/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
- data/.github/ISSUE_TEMPLATE/config.yml +6 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- data/.github/pull_request_template.md +32 -0
- data/.github/workflows/cd.yml +53 -0
- data/.github/workflows/ci.yml +39 -0
- data/.gitignore +23 -0
- data/.rspec +4 -0
- data/.rubocop.yml +926 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.vscode/launch.json +53 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +40 -0
- data/CONTRIBUTORS.md +3 -0
- data/Gemfile +20 -0
- data/LICENSE +205 -0
- data/NOTICE +13 -0
- data/README.md +160 -0
- data/Rakefile +8 -0
- data/bin/bundle-audit +29 -0
- data/bin/bundler-audit +29 -0
- data/bin/codeowner_validator +8 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/codeowner_validator.gemspec +41 -0
- data/lib/codeowner_validator/cli/validator_cli.rb +64 -0
- data/lib/codeowner_validator/code_owners.rb +308 -0
- data/lib/codeowner_validator/common/command.rb +40 -0
- data/lib/codeowner_validator/common/logging.rb +86 -0
- data/lib/codeowner_validator/common/tasks/base.rb +62 -0
- data/lib/codeowner_validator/group/comment/error.rb +21 -0
- data/lib/codeowner_validator/group/comment/info.rb +21 -0
- data/lib/codeowner_validator/group/comment/verbose.rb +21 -0
- data/lib/codeowner_validator/group/comment/warn.rb +21 -0
- data/lib/codeowner_validator/group/comment.rb +55 -0
- data/lib/codeowner_validator/helpers/config_helper.rb +73 -0
- data/lib/codeowner_validator/helpers/utility_helper.rb +30 -0
- data/lib/codeowner_validator/lists/whitelist.rb +145 -0
- data/lib/codeowner_validator/tasks/duplicate_checker.rb +32 -0
- data/lib/codeowner_validator/tasks/file_exists_checker.rb +35 -0
- data/lib/codeowner_validator/tasks/missing_assignment_checker.rb +32 -0
- data/lib/codeowner_validator/tasks/syntax_checker.rb +32 -0
- data/lib/codeowner_validator/validator.rb +76 -0
- data/lib/codeowner_validator/version.rb +11 -0
- data/lib/codeowner_validator.rb +32 -0
- data/lib/codeowners/checker/group/line.rb +12 -0
- metadata +204 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/group/comment'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Group
|
7
|
+
module Comment
|
8
|
+
# Public: An error comment response
|
9
|
+
class Error
|
10
|
+
include Comment
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @see CodeownerValidator::Group::Comment.match?
|
14
|
+
def match?(type)
|
15
|
+
Comment::TYPE_ERROR == type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/group/comment'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Group
|
7
|
+
module Comment
|
8
|
+
# Public: An info comment response
|
9
|
+
class Info
|
10
|
+
include Comment
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @see CodeownerValidator::Group::Comment.match?
|
14
|
+
def match?(type)
|
15
|
+
Comment::TYPE_INFO == type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/group/comment'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Group
|
7
|
+
module Comment
|
8
|
+
# Public: An info comment response
|
9
|
+
class Verbose
|
10
|
+
include Comment
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @see CodeownerValidator::Group::Comment.match?
|
14
|
+
def match?(type)
|
15
|
+
Comment::TYPE_VERBOSE == type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/group/comment'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Group
|
7
|
+
module Comment
|
8
|
+
# Public: A warn comment response
|
9
|
+
class Warn
|
10
|
+
include Comment
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @see CodeownerValidator::Group::Comment.match?
|
14
|
+
def match?(type)
|
15
|
+
Comment::TYPE_WARN == type
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CodeownerValidator
|
4
|
+
module Group
|
5
|
+
# Public: Object for storing base comment information that can be rendered appropriately by the consumer
|
6
|
+
module Comment
|
7
|
+
TYPE_VERBOSE = 1
|
8
|
+
TYPE_INFO = 2
|
9
|
+
TYPE_WARN = 3
|
10
|
+
TYPE_ERROR = 4
|
11
|
+
|
12
|
+
# to keep track of hierarchical structure of comments and allow grouping
|
13
|
+
attr_accessor :parent
|
14
|
+
attr_reader :comment
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Public: Creates an instance of the comment for usage.
|
18
|
+
#
|
19
|
+
# @param [Hash] _args The hash of arguments accepted
|
20
|
+
# @option _args [String] :comment The comment to create
|
21
|
+
# @option _args [Integer] :type The comment type (info, warn, error, verbose)
|
22
|
+
#
|
23
|
+
# @return [Info|]
|
24
|
+
def build(comment:, type: TYPE_INFO, **_args)
|
25
|
+
subclasses.each do |klass|
|
26
|
+
return klass.new(comment) if klass.match?(type)
|
27
|
+
end
|
28
|
+
raise "Type '#{type}' not supported" # rubocop:disable Style/ImplicitRuntimeError
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Returns <true> if the type of object requested is supported by the object; otherwise, <false>
|
32
|
+
#
|
33
|
+
# @param [Integer] type The type of comment to match on a subclass
|
34
|
+
def match?(type); end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# returns the available subclasses to the base object
|
39
|
+
def subclasses
|
40
|
+
[Info, Warn, Error, Verbose]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Creates an instance of the comment
|
45
|
+
#
|
46
|
+
# @param [String] comment The string text of the comment
|
47
|
+
def initialize(comment)
|
48
|
+
@comment = comment
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# require all subclasses
|
55
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'comment', '**/*.rb'), &method(:require))
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-prompt'
|
4
|
+
require 'tty-spinner'
|
5
|
+
|
6
|
+
module CodeownerValidator
|
7
|
+
# Public: A configuration helper designed to assist in simple abstraction of TTY:Prompt requests
|
8
|
+
# and assign those responses to the base module's configuration
|
9
|
+
class ConfigHelper
|
10
|
+
class << self
|
11
|
+
# Public: An abstraction onto the TTY:Prompt.ask to allow requesting information from the user's input
|
12
|
+
# and saving that input within the base module's configuration
|
13
|
+
#
|
14
|
+
# @param [Hash] args The arguments in which to accept
|
15
|
+
# @option args [String] :ident The identifier to the config to store
|
16
|
+
# @option args [String] :prompt The prompt to display to the user
|
17
|
+
# @option args [String] :default The default value to display
|
18
|
+
# @option args [Boolean] :required Indicates if the requested ask required input from the user
|
19
|
+
# @option args [Boolean] :force_ask Indicates to ignore previous ask and perform again
|
20
|
+
# @option args [Boolean] :mask Indicates that the response should be hidden
|
21
|
+
def ask(ident:, prompt:, required: false, force_ask: false, mask: false, **args)
|
22
|
+
opts =
|
23
|
+
{}.tap do |h|
|
24
|
+
h[:required] = required
|
25
|
+
h[:default] = args[:default] if args[:default]
|
26
|
+
end
|
27
|
+
|
28
|
+
# return if either not being forced to ask or the information has been previously captured
|
29
|
+
return if !force_ask && ::CodeownerValidator.respond_to?(ident)
|
30
|
+
|
31
|
+
tty_prompt = ::TTY::Prompt.new
|
32
|
+
response =
|
33
|
+
tty_prompt.collect do
|
34
|
+
if mask
|
35
|
+
key(ident.to_sym).mask(
|
36
|
+
prompt,
|
37
|
+
opts
|
38
|
+
)
|
39
|
+
else
|
40
|
+
key(ident.to_sym).ask(
|
41
|
+
prompt,
|
42
|
+
opts
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
::CodeownerValidator.configure! response
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: An abstraction onto the TTY:Prompt.select to allow requesting information from the user's input
|
51
|
+
# and saving that input within the base module's configuration
|
52
|
+
#
|
53
|
+
# @param [Hash] args The arguments in which to accept
|
54
|
+
# @option args [String] :ident The identifier to the config to store
|
55
|
+
# @option args [String] :prompt The prompt to display to the user
|
56
|
+
# @option args [Boolean] :force_ask Indicates to ignore previous ask and perform again
|
57
|
+
# @option args [Boolean] :choices The array of choices available for selection by the user
|
58
|
+
def select(ident:, prompt:, force_ask: false, choices:, **args)
|
59
|
+
# return if either not being forced to ask or the information has been previously captured
|
60
|
+
return if !force_ask && ::CodeownerValidator.respond_to?(ident)
|
61
|
+
|
62
|
+
tty_prompt = ::TTY::Prompt.new
|
63
|
+
response = tty_prompt.select(
|
64
|
+
prompt,
|
65
|
+
choices,
|
66
|
+
args
|
67
|
+
)
|
68
|
+
|
69
|
+
::CodeownerValidator.configure! ident => response
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/ImplicitRuntimeError
|
4
|
+
module CodeownerValidator
|
5
|
+
# Public: A utility helper to provide common methods for reuse across multiple
|
6
|
+
# classes
|
7
|
+
module UtilityHelper
|
8
|
+
# Provides a way to change the current working directory to a different folder location.
|
9
|
+
# This ability can ease the reference of file references when working with multiple
|
10
|
+
# repository locations.
|
11
|
+
#
|
12
|
+
# @raise [RuntimeError] if the folder location does not exist.
|
13
|
+
def in_folder(folder)
|
14
|
+
raise "The folder location '#{folder}' does not exists" unless File.directory?(folder)
|
15
|
+
|
16
|
+
if defined?(Bundler)
|
17
|
+
Bundler.with_clean_env do
|
18
|
+
Dir.chdir folder do
|
19
|
+
yield
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Dir.chdir folder do
|
24
|
+
yield
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
# rubocop:enable Style/ImplicitRuntimeError
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CodeownerValidator
|
4
|
+
module Lists
|
5
|
+
# Manage whitelist file reading
|
6
|
+
class Whitelist
|
7
|
+
attr_reader :repo_path
|
8
|
+
|
9
|
+
# Public: Initialized with a provided file
|
10
|
+
#
|
11
|
+
# @param [String] filename The filename to initialize the list with
|
12
|
+
# @param [String] repo_path The repository base path utilized for evaluation of a .gitignore
|
13
|
+
def initialize(filename: nil, repo_path: nil)
|
14
|
+
@filename = filename
|
15
|
+
@repo_path = repo_path
|
16
|
+
|
17
|
+
# to avoid instance variable not initialized warnings
|
18
|
+
@whitelist_file = nil
|
19
|
+
@whitelist_file_paths = nil
|
20
|
+
@git_ignore_file = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Checks for existence configuration for the whitelist
|
24
|
+
#
|
25
|
+
# @return <true> if found; otherwise, <false>
|
26
|
+
def exist?
|
27
|
+
!pathspec.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Returns <true> if the file supplied has been whitelisted; otherwise, <false>
|
31
|
+
#
|
32
|
+
# @param [String] filename The file to evaluate if has been whitelisted
|
33
|
+
# @return <true> if the file supplied has been whitelisted; otherwise, <false>
|
34
|
+
def whitelisted?(filename)
|
35
|
+
# if no items in the whitelist, assume all items are whitelisted
|
36
|
+
return true if pathspec.empty?
|
37
|
+
|
38
|
+
listed?(filename)
|
39
|
+
end
|
40
|
+
|
41
|
+
# add a `to_proc` method that allows instances of this class to be passed as a block
|
42
|
+
# for easier chaining executions
|
43
|
+
def to_proc
|
44
|
+
proc { |item| whitelisted?(item) }
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# search for the .gitignore file if provided a repo path
|
50
|
+
def git_ignore_file
|
51
|
+
# if no repo, there can be no discovery of a file
|
52
|
+
return unless repo_path
|
53
|
+
|
54
|
+
return @git_ignore_file if @git_ignore_file
|
55
|
+
|
56
|
+
file = File.join(repo_path, '.gitignore')
|
57
|
+
@git_ignore_file = file if File.exist?(file)
|
58
|
+
end
|
59
|
+
|
60
|
+
# search for an actual file within the repository named 'CODEOWNERS_WHITELIST'
|
61
|
+
def whitelist_file
|
62
|
+
# if no repo, there can be no discovery of a file
|
63
|
+
return unless repo_path
|
64
|
+
|
65
|
+
return @whitelist_file if @whitelist_file
|
66
|
+
|
67
|
+
whitelist_file_paths.each do |path|
|
68
|
+
current_file_path = File.join(repo_path, path)
|
69
|
+
return current_file_path if File.exist?(current_file_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# locations to search for the repository driven codeowners whitelist file
|
76
|
+
def whitelist_file_paths
|
77
|
+
return @whitelist_file_paths if @whitelist_file_paths
|
78
|
+
|
79
|
+
@whitelist_file_paths = %w[CODEOWNERS_WHITELIST .github/CODEOWNERS_WHITELIST]
|
80
|
+
|
81
|
+
# allow customization of the locations to search for the file
|
82
|
+
if ENV['CODEOWNER_WHITELIST_FILE_PATHS']
|
83
|
+
ENV['CODEOWNER_WHITELIST_FILE_PATHS']&.split(',')&.each(&:strip!)&.each do |str|
|
84
|
+
@whitelist_file_paths << str
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
@whitelist_file_paths
|
89
|
+
end
|
90
|
+
|
91
|
+
# checks for the match of the filename
|
92
|
+
def listed?(filename)
|
93
|
+
pathspec.match(filename)
|
94
|
+
end
|
95
|
+
|
96
|
+
# the pathspec is a combination of files and variables:
|
97
|
+
# * CODEOWNERS_WHITELIST - if provided a repo_path, file that exists in either the root repo path
|
98
|
+
# or alongside the CODEOWNERS file
|
99
|
+
# * CODEOWNERS_WHITELIST - env variable of comma separated
|
100
|
+
# * .gitignore - if provided a repo_path, this file is automatically added
|
101
|
+
def pathspec
|
102
|
+
# to avoid instance variable @pathspec not initialized must check if defined prior
|
103
|
+
return @pathspec if defined?(@pathspec) && @pathspec
|
104
|
+
|
105
|
+
@pathspec = PathSpec.new([])
|
106
|
+
|
107
|
+
# first, add repo driven codeowners whitelist file
|
108
|
+
@pathspec.add ::File.readlines(whitelist_file).map(&:chomp) if whitelist_file
|
109
|
+
|
110
|
+
# second, add provided file
|
111
|
+
@pathspec.add ::File.readlines(@filename).map(&:chomp) if @filename && File.exist?(@filename)
|
112
|
+
|
113
|
+
# third, add items from the CODEOWNERS_WHITELIST
|
114
|
+
if ENV['CODEOWNERS_WHITELIST']
|
115
|
+
new_items =
|
116
|
+
[].tap do |ar|
|
117
|
+
ENV['CODEOWNERS_WHITELIST']&.split(',')&.each(&:strip!)&.each do |str|
|
118
|
+
ar << str
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
@pathspec.add new_items
|
123
|
+
end
|
124
|
+
|
125
|
+
# last, add items from the .gitignore. this must always be last because
|
126
|
+
# of the check for empty specs at this point in which will require inclusion of
|
127
|
+
# all files prior to adding items to exclude.
|
128
|
+
|
129
|
+
# if a gitignore exists, add each reference
|
130
|
+
if git_ignore_file
|
131
|
+
# first, check if existing pathspec evaluation is empty, if yes, need to add all files '**'
|
132
|
+
# because the pathspec evaluation will only make evaluations based on the inclusive=false so need
|
133
|
+
# to first include all files to provide an entry to inclusive=true
|
134
|
+
@pathspec.add ['**'] if @pathspec.empty?
|
135
|
+
|
136
|
+
@pathspec.add ::File.readlines(git_ignore_file).map do |line|
|
137
|
+
"!#{line.chomp}"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@pathspec
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/tasks/base'
|
4
|
+
require 'codeowner_validator/group/comment'
|
5
|
+
|
6
|
+
module CodeownerValidator
|
7
|
+
module Tasks
|
8
|
+
# Public: The duplicate checker executes an evaluation on the code owners file looking for duplicate
|
9
|
+
# pattern references
|
10
|
+
class DuplicateChecker < Base
|
11
|
+
include ::CodeownerValidator::Group
|
12
|
+
|
13
|
+
# @see ::CodeownerValidator::Tasks::Base.summary
|
14
|
+
def summary
|
15
|
+
'Executing Duplicated Pattern Checker'
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see ::CodeownerValidator::Tasks::Base.comments
|
19
|
+
def comments
|
20
|
+
comments = []
|
21
|
+
|
22
|
+
codeowners.duplicated_patterns.each do |key, value|
|
23
|
+
msg = "Pattern '#{key}' is defined #{value.size} times on lines " \
|
24
|
+
"#{value.map(&:line_number).join(', ')}"
|
25
|
+
comments << Comment.build(comment: msg, type: Comment::TYPE_ERROR)
|
26
|
+
end
|
27
|
+
|
28
|
+
comments
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/tasks/base'
|
4
|
+
require 'codeowner_validator/group/comment'
|
5
|
+
|
6
|
+
module CodeownerValidator
|
7
|
+
module Tasks
|
8
|
+
# Public: The file existence checker executes an evaluation on the code owners file looking for references
|
9
|
+
# to non-existent files within the repository
|
10
|
+
class FileExistsChecker < Base
|
11
|
+
include ::CodeownerValidator::Group
|
12
|
+
|
13
|
+
# @see ::CodeownerValidator::Tasks::Base.summary
|
14
|
+
def summary
|
15
|
+
'Executing File Exists Checker'
|
16
|
+
end
|
17
|
+
|
18
|
+
# @see ::CodeownerValidator::Tasks::Base.comments
|
19
|
+
def comments
|
20
|
+
comments = []
|
21
|
+
|
22
|
+
codeowners.invalid_reference_lines.each do |line|
|
23
|
+
file_name = line.pattern? ? line.pattern : line
|
24
|
+
msg = "line #{line.line_number}: '#{file_name}' does not match any files in the repository"
|
25
|
+
comments << Comment.build(
|
26
|
+
comment: msg,
|
27
|
+
type: Comment::TYPE_ERROR
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
comments
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/tasks/base'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Tasks
|
7
|
+
# Public: The missing assignment checker executes an evaluation on the code owners file looking for files
|
8
|
+
# within the repository that are not noted as been assigned by the codeowners file
|
9
|
+
class MissingAssignmentChecker < Base
|
10
|
+
include ::CodeownerValidator::Group
|
11
|
+
|
12
|
+
# @see ::CodeownerValidator::Tasks::Base.summary
|
13
|
+
def summary
|
14
|
+
'Executing Missing Assignment Checker'
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see ::CodeownerValidator::Tasks::Base.comments
|
18
|
+
def comments
|
19
|
+
comments = []
|
20
|
+
|
21
|
+
codeowners.missing_assignments.each do |file|
|
22
|
+
comments << Comment.build(
|
23
|
+
comment: "File '#{file}' is missing from the code owners file",
|
24
|
+
type: Comment::TYPE_ERROR
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
comments
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/tasks/base'
|
4
|
+
|
5
|
+
module CodeownerValidator
|
6
|
+
module Tasks
|
7
|
+
# Public: The syntax checker executes an evaluation on the code owners file looking for missing assignment
|
8
|
+
# within the file itself
|
9
|
+
class SyntaxChecker < Base
|
10
|
+
include ::CodeownerValidator::Group
|
11
|
+
|
12
|
+
# @see ::CodeownerValidator::Tasks::Base.summary
|
13
|
+
def summary
|
14
|
+
'Executing Valid Syntax Checker'
|
15
|
+
end
|
16
|
+
|
17
|
+
# @see ::CodeownerValidator::Tasks::Base.comments
|
18
|
+
def comments
|
19
|
+
comments = []
|
20
|
+
|
21
|
+
codeowners.unrecognized_assignments.each do |line|
|
22
|
+
comments << Comment.build(
|
23
|
+
comment: "line #{line.line_number}: Missing owner, at least one owner is required",
|
24
|
+
type: Comment::TYPE_ERROR
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
comments
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'codeowner_validator/common/tasks/base'
|
4
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'tasks', '**/*.rb'), &method(:require))
|
5
|
+
require 'codeowner_validator/group/comment'
|
6
|
+
|
7
|
+
module CodeownerValidator
|
8
|
+
# Public: The validator is utilized for execution of the tasks associated to the code owner validation tasks.
|
9
|
+
# It has the option to either execute a validation and automatically output the stdout or return an [Array]
|
10
|
+
# of comments for the consumer to do as they please. The CLI execution will route all comments to stdout.
|
11
|
+
class Validator < ::CodeownerValidator::Tasks::Base
|
12
|
+
include ::CodeownerValidator::Group
|
13
|
+
|
14
|
+
# Public: Creates an instance of the executor with provided tasks
|
15
|
+
#
|
16
|
+
# @param [Hash] options The user provided options from the command line
|
17
|
+
# @options options [Array] :tasks Array of tasks that should be specifically executed by the merger
|
18
|
+
def initialize(tasks: [], **options)
|
19
|
+
super
|
20
|
+
@options = options
|
21
|
+
|
22
|
+
# allow defining what tasks the merger should execute
|
23
|
+
@tasks = tasks.map { |task_class| task_class.new(**@options) } unless tasks.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: Returns an array of summaries associated to all tasks that are to be executed
|
27
|
+
#
|
28
|
+
# @return [Array] An array of strings describing the tasks that are to be executed
|
29
|
+
def summary
|
30
|
+
tasks.map { |t| " * #{t.summary}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Performs the execution of all tasks
|
34
|
+
def validate
|
35
|
+
log_verbose(%w[Started:] + summary)
|
36
|
+
|
37
|
+
in_repo_folder do
|
38
|
+
tasks&.each(&:execute)
|
39
|
+
end
|
40
|
+
|
41
|
+
log_info 'VALIDATION complete! 🌟'
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Performs the execution of all tasks and returns the comments to be interpreted
|
45
|
+
def comments
|
46
|
+
comments = []
|
47
|
+
|
48
|
+
in_repo_folder do
|
49
|
+
tasks&.each do |task|
|
50
|
+
parent = Comment.build(
|
51
|
+
comment: task.summary,
|
52
|
+
type: Comment::TYPE_VERBOSE
|
53
|
+
)
|
54
|
+
task.comments.each do |comment|
|
55
|
+
comment.parent = parent
|
56
|
+
comments << comment
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
comments.group_by(&:parent)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def tasks
|
67
|
+
@tasks ||=
|
68
|
+
[
|
69
|
+
Tasks::DuplicateChecker,
|
70
|
+
Tasks::SyntaxChecker,
|
71
|
+
Tasks::FileExistsChecker,
|
72
|
+
Tasks::MissingAssignmentChecker
|
73
|
+
].compact.map { |task_class| task_class.new(@options) } # rubocop:disable Performance/ChainArrayAllocation
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'codeowners/checker'
|
5
|
+
# pull in monkeypatch for codeowners-checker
|
6
|
+
require_relative 'codeowners/checker/group/line'
|
7
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'codeowner_validator', '**/*.rb'), &method(:require))
|
8
|
+
|
9
|
+
# Public: The code owner validator space is utilized for validations against
|
10
|
+
# the code owner file for a given repository.
|
11
|
+
module CodeownerValidator
|
12
|
+
class << self
|
13
|
+
# Public: Provides the ability to configure instance variables within the module. If the
|
14
|
+
# method already exists, the value provide will be overwritten.
|
15
|
+
#
|
16
|
+
# @params [Hash] attrs The key/value paired items to be configured on the module.
|
17
|
+
def configure!(attrs = {})
|
18
|
+
attrs.each do |name, value|
|
19
|
+
name = name.to_s.to_sym
|
20
|
+
# protect against multiple executions
|
21
|
+
singleton_class.instance_eval { attr_accessor name } unless self.class.method_defined?(name)
|
22
|
+
send("#{name}=", value)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Mail CLI
|
28
|
+
class CLI < Thor
|
29
|
+
desc 'validate', 'validates the codeowners file'
|
30
|
+
subcommand 'validate', ValidatorCLI
|
31
|
+
end
|
32
|
+
end
|