codeowner_validator 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.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
|