repo_analyzer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +103 -0
- data/.circleci/setup-rubygems.sh +3 -0
- data/.editorconfig +24 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +505 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +320 -0
- data/Guardfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +165 -0
- data/Rakefile +10 -0
- data/app/assets/config/repo_analyzer_manifest.js +1 -0
- data/app/assets/images/repo_analyzer/.keep +0 -0
- data/app/assets/stylesheets/repo_analyzer/application.css +15 -0
- data/app/clients/repo_analyzer/github_client.rb +31 -0
- data/app/controllers/repo_analyzer/application_controller.rb +4 -0
- data/app/extractors/repo_analyzer/brakeman_extractor.rb +33 -0
- data/app/extractors/repo_analyzer/circleci_extractor.rb +46 -0
- data/app/extractors/repo_analyzer/github_extractor.rb +64 -0
- data/app/extractors/repo_analyzer/power_types_extractor.rb +21 -0
- data/app/extractors/repo_analyzer/project_config_extractor.rb +53 -0
- data/app/extractors/repo_analyzer/project_info_extractor.rb +20 -0
- data/app/extractors/repo_analyzer/project_versions_extractor.rb +80 -0
- data/app/extractors/repo_analyzer/rails_best_practices_extractor.rb +36 -0
- data/app/extractors/repo_analyzer/rubocop_extractor.rb +74 -0
- data/app/extractors/repo_analyzer/tests_info_extractor.rb +87 -0
- data/app/helpers/repo_analyzer/application_helper.rb +4 -0
- data/app/jobs/repo_analyzer/application_job.rb +4 -0
- data/app/jobs/repo_analyzer/extract_project_info_job.rb +35 -0
- data/app/jobs/repo_analyzer/post_extracted_info_job.rb +28 -0
- data/app/mailers/repo_analyzer/application_mailer.rb +6 -0
- data/app/models/repo_analyzer/application_record.rb +5 -0
- data/app/utils/output_utils.rb +10 -0
- data/app/values/repo_analyzer/project_data_bridge.rb +48 -0
- data/app/views/layouts/repo_analyzer/application.html.erb +15 -0
- data/bin/rails +25 -0
- data/config/routes.rb +2 -0
- data/lib/generators/repo_analyzer/install/USAGE +5 -0
- data/lib/generators/repo_analyzer/install/install_generator.rb +19 -0
- data/lib/generators/repo_analyzer/install/templates/initializer.rb +2 -0
- data/lib/repo_analyzer/engine.rb +15 -0
- data/lib/repo_analyzer/example_class.rb +7 -0
- data/lib/repo_analyzer/version.rb +3 -0
- data/lib/repo_analyzer.rb +48 -0
- data/lib/tasks/repo_analyzer_tasks.rake +7 -0
- data/repo_analyzer.gemspec +42 -0
- data/spec/assets/test_project/README.md +1 -0
- data/spec/assets/test_project/app/commands/some_comand.rb +0 -0
- data/spec/assets/test_project/app/javascript/src/component.spec.js +0 -0
- data/spec/assets/test_project/app/javascript/src/components/component.spec.js +0 -0
- data/spec/assets/test_project/app/observers/some_observer.rb +0 -0
- data/spec/assets/test_project/old_circleci_config.yml +21 -0
- data/spec/assets/test_project/spec/commands/command1_spec.rb +0 -0
- data/spec/assets/test_project/spec/commands/command2_spec.rb +0 -0
- data/spec/assets/test_project/spec/integration/integration1_spec.rb +0 -0
- data/spec/assets/test_project/spec/system/system_test_spec.rb +0 -0
- data/spec/assets/test_project/valid_circleci_config.yml +152 -0
- data/spec/assets/test_project/valid_gem_gemfile_lock +14 -0
- data/spec/assets/test_project/valid_gemfile_lock +684 -0
- data/spec/assets/test_project/valid_makefile +35 -0
- data/spec/assets/test_project/valid_package_json +84 -0
- data/spec/assets/test_project/valid_package_json_without_vue +74 -0
- data/spec/assets/test_project/valid_rubocop_rules.yml +54 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +15 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config/application.rb +38 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +81 -0
- data/spec/dummy/config/environments/production.rb +120 -0
- data/spec/dummy/config/environments/test.rb +59 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +12 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/permissions_policy.rb +11 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +43 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/extractors/brakeman_extractor_spec.rb +73 -0
- data/spec/extractors/circleci_extractor_spec.rb +45 -0
- data/spec/extractors/github_extractor_spec.rb +77 -0
- data/spec/extractors/power_types_extractor_spec.rb +18 -0
- data/spec/extractors/project_config_extractor_spec.rb +52 -0
- data/spec/extractors/project_versions_extractor_spec.rb +63 -0
- data/spec/extractors/rails_best_practices_extractor_spec.rb +53 -0
- data/spec/extractors/rubocop_extractor_spec.rb +113 -0
- data/spec/extractors/tests_info_extractor_spec.rb +65 -0
- data/spec/fixtures/files/image.png +0 -0
- data/spec/fixtures/files/video.mp4 +0 -0
- data/spec/jobs/repo_analyzer/extract_project_info_job_spec.rb +50 -0
- data/spec/jobs/repo_analyzer/post_extracted_info_job_spec.rb +52 -0
- data/spec/rails_helper.rb +42 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/repo_analyzer_helpers.rb +37 -0
- metadata +518 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class GithubExtractor < ProjectInfoExtractor
|
3
|
+
private
|
4
|
+
|
5
|
+
def extracted_info
|
6
|
+
{
|
7
|
+
last_commit_date: last_commit_date.to_s,
|
8
|
+
last_contributors: last_contributors,
|
9
|
+
contributors: contributors
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def last_contributors
|
14
|
+
@last_contributors ||= contributors.select do |contributor|
|
15
|
+
contributors_from_commits.include?(contributor[:login])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def contributors_from_commits
|
20
|
+
@contributors_from_commits ||= last_commits.inject([]) do |users, commit|
|
21
|
+
if commit[:author]
|
22
|
+
login = commit[:author][:login]
|
23
|
+
users << login unless users.include?(login)
|
24
|
+
end
|
25
|
+
|
26
|
+
users
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def contributors
|
31
|
+
@contributors ||= project_data_bridge.contributors.map do |contributor|
|
32
|
+
build_user_info(contributor)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_user_info(data)
|
37
|
+
{
|
38
|
+
login: data[:login],
|
39
|
+
avatar_url: data[:avatar_url],
|
40
|
+
contributions: data[:contributions]
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_commits
|
45
|
+
@last_commits ||= begin
|
46
|
+
return [] if last_commit_date == :not_found
|
47
|
+
|
48
|
+
project_data_bridge.commits(
|
49
|
+
from_date: last_commit_date - 2.months,
|
50
|
+
to_date: last_commit_date + 1.day
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def last_commit_date
|
56
|
+
@last_commit_date ||= project_data_bridge.commits(
|
57
|
+
from_date: Date.current - 2.years,
|
58
|
+
to_date: Date.current
|
59
|
+
).first[:commit][:author][:date].to_date
|
60
|
+
rescue NoMethodError
|
61
|
+
:not_found
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class PowerTypesExtractor < ProjectInfoExtractor
|
3
|
+
POWERTYPES = %i{
|
4
|
+
commands
|
5
|
+
services
|
6
|
+
observers
|
7
|
+
values
|
8
|
+
utils
|
9
|
+
clients
|
10
|
+
}
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def extracted_info
|
15
|
+
POWERTYPES.inject({}) do |memo, type|
|
16
|
+
memo[type] = project_data_bridge.dir_files("app/#{type}").any?
|
17
|
+
memo
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class ProjectConfigExtractor < ProjectInfoExtractor
|
3
|
+
MAKEFILE_CMDS_REGEXP = /^([\w|-]*):/
|
4
|
+
RUBOCOP_RULES_REGEXP = %r{^([\w|/]+):}
|
5
|
+
|
6
|
+
EXCLUDED_MAKEFILE_CMDS = %w{
|
7
|
+
run
|
8
|
+
help
|
9
|
+
}
|
10
|
+
|
11
|
+
EXCLUDED_RUBOCOP_RULES = %w{
|
12
|
+
require
|
13
|
+
}
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def extracted_info
|
18
|
+
{
|
19
|
+
simplecov: simplecov?,
|
20
|
+
makefile_commands: makefile_commands,
|
21
|
+
rubocop_rules: rubocop_rules
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def simplecov?
|
26
|
+
project_data_bridge.file_exist?("spec/simplecov_config.rb")
|
27
|
+
end
|
28
|
+
|
29
|
+
def makefile_commands
|
30
|
+
extract_matches_from_content(makefile_content, MAKEFILE_CMDS_REGEXP).reject do |cmd|
|
31
|
+
EXCLUDED_MAKEFILE_CMDS.include?(cmd)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def rubocop_rules
|
36
|
+
extract_matches_from_content(rubocop_rules_content, RUBOCOP_RULES_REGEXP).reject do |rule|
|
37
|
+
EXCLUDED_RUBOCOP_RULES.include?(rule)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_matches_from_content(content, regexp)
|
42
|
+
content.to_s.scan(regexp).flatten.sort
|
43
|
+
end
|
44
|
+
|
45
|
+
def makefile_content
|
46
|
+
@makefile_content ||= project_data_bridge.file_content("Makefile")
|
47
|
+
end
|
48
|
+
|
49
|
+
def rubocop_rules_content
|
50
|
+
@rubocop_rules_content ||= project_data_bridge.file_content(".rubocop.yml")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class ProjectInfoExtractor
|
3
|
+
def initialize(project_data_bridge)
|
4
|
+
@project_data_bridge = project_data_bridge
|
5
|
+
end
|
6
|
+
|
7
|
+
def extract
|
8
|
+
key = self.class.name.underscore.split("/").last.to_sym
|
9
|
+
{ key => extracted_info.with_indifferent_access }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def extracted_info
|
15
|
+
raise RepoAnalyzer::Error.new("extracted_info method not implemented")
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :project_data_bridge
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class ProjectVersionsExtractor < ProjectInfoExtractor
|
3
|
+
CI_VERSION_REGEXP = /^version:\s(\d)/
|
4
|
+
RAILS_VERSION_REGEX = /^\s{4}rails \((\d.*)\)/
|
5
|
+
PAPERCLIP_VERSION = /^\s{4}paperclip \((\d.*)\)/
|
6
|
+
SHRINE_VERSION = /^\s{4}shrine \((\d.*)\)/
|
7
|
+
RUBOCOP_VERSION = /^\s{4}rubocop \((\d.*)\)/
|
8
|
+
SIDEKIQ_VERSION = /^\s{4}sidekiq \((\d.*)\)/
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def extracted_info
|
13
|
+
{
|
14
|
+
rails_version: gem_version(RAILS_VERSION_REGEX),
|
15
|
+
node_version: package_version("engines.node"),
|
16
|
+
vue_version: package_version("dependencies.vue"),
|
17
|
+
vue_router_version: package_version("dependencies.vue-router"),
|
18
|
+
typescript_version: package_version("dependencies.typescript"),
|
19
|
+
tailwind_version: tailwind_version,
|
20
|
+
paperclip_version: gem_version(PAPERCLIP_VERSION),
|
21
|
+
shrine_version: gem_version(SHRINE_VERSION),
|
22
|
+
sidekiq_version: gem_version(SIDEKIQ_VERSION),
|
23
|
+
circleci_version: circleci_version,
|
24
|
+
rubocop_version: gem_version(RUBOCOP_VERSION),
|
25
|
+
eslint_version: package_version("devDependencies.eslint"),
|
26
|
+
stylelint_version: package_version("devDependencies.stylelint")
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def tailwind_version
|
31
|
+
return "2" if package_json_content.to_s.include?("postcss7-compat")
|
32
|
+
|
33
|
+
package_version("dependencies.tailwindcss")
|
34
|
+
end
|
35
|
+
|
36
|
+
def gem_version(regexp)
|
37
|
+
gemfile_content&.match(regexp)&.captures&.first
|
38
|
+
end
|
39
|
+
|
40
|
+
def package_version(dependency)
|
41
|
+
content = package_json_content
|
42
|
+
return nil if content.blank?
|
43
|
+
|
44
|
+
version = JSON.parse(content)
|
45
|
+
.dig(*dependency.split(".")).to_s.split("")
|
46
|
+
.select do |value|
|
47
|
+
value == "." || int?(value)
|
48
|
+
end
|
49
|
+
|
50
|
+
version.pop if version.last == "."
|
51
|
+
return version.join("") if version.present?
|
52
|
+
|
53
|
+
nil
|
54
|
+
rescue JSON::ParserError
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def circleci_version
|
59
|
+
ci_config_content&.match(CI_VERSION_REGEXP)&.captures&.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def int?(value)
|
63
|
+
Integer(value)
|
64
|
+
rescue ArgumentError
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
def package_json_content
|
69
|
+
@package_json_content ||= project_data_bridge.file_content("package.json")
|
70
|
+
end
|
71
|
+
|
72
|
+
def gemfile_content
|
73
|
+
@gemfile_content ||= project_data_bridge.file_content("Gemfile.lock")
|
74
|
+
end
|
75
|
+
|
76
|
+
def ci_config_content
|
77
|
+
@ci_config_content ||= project_data_bridge.file_content(".circleci/config.yml")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class RailsBestPracticesExtractor < ProjectInfoExtractor
|
3
|
+
private
|
4
|
+
|
5
|
+
def extracted_info
|
6
|
+
analyzer.analyze
|
7
|
+
{ errors: errors }
|
8
|
+
end
|
9
|
+
|
10
|
+
def errors
|
11
|
+
@errors ||= analyzer.errors.map do |error|
|
12
|
+
next unless error.filename.include?(project_data_bridge.project_path)
|
13
|
+
|
14
|
+
{
|
15
|
+
file_path: get_file_path(error),
|
16
|
+
message: error.message,
|
17
|
+
type: error.type.gsub("RailsBestPractices::", ""),
|
18
|
+
url: error.url
|
19
|
+
}
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_file_path(error)
|
24
|
+
[
|
25
|
+
error.filename.gsub(%r{\A\D*#{project_data_bridge.project_path}/}, ""),
|
26
|
+
error.line_number
|
27
|
+
].reject(&:blank?).join(":")
|
28
|
+
end
|
29
|
+
|
30
|
+
def analyzer
|
31
|
+
@analyzer ||= RailsBestPractices::Analyzer.new(
|
32
|
+
project_data_bridge.project_path
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class RubocopExtractor < ProjectInfoExtractor
|
3
|
+
private
|
4
|
+
|
5
|
+
def extracted_info
|
6
|
+
{ offenses: grouped_offenses_by_severit_and_cop }
|
7
|
+
end
|
8
|
+
|
9
|
+
def grouped_offenses_by_severit_and_cop
|
10
|
+
result = {}
|
11
|
+
|
12
|
+
grouped_offenses_by_severity.each do |severity, offenses|
|
13
|
+
result[severity] = offenses.group_by do |offense|
|
14
|
+
offense.delete(:severity)
|
15
|
+
offense.delete(:cop)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def grouped_offenses_by_severity
|
23
|
+
offenses.group_by do |offense|
|
24
|
+
offense[:severity]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def offenses
|
29
|
+
rubocop_json["files"].inject([]) do |memo, file_data|
|
30
|
+
file_data["offenses"].each do |offense|
|
31
|
+
memo << {
|
32
|
+
path: offense_file_name(file_data["path"], offense),
|
33
|
+
cop: offense["cop_name"],
|
34
|
+
message: get_message(offense["message"]),
|
35
|
+
severity: offense["severity"]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
memo
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_message(message)
|
43
|
+
parts = message.to_s.split(": ")
|
44
|
+
return parts.first if parts.size == 1
|
45
|
+
|
46
|
+
parts.shift
|
47
|
+
parts.join(": ")
|
48
|
+
end
|
49
|
+
|
50
|
+
def offense_file_name(file_path, offense)
|
51
|
+
[
|
52
|
+
file_path.gsub(%r{\A\D*#{project_data_bridge.project_path}/}, ""),
|
53
|
+
offense['location']['line'].to_s,
|
54
|
+
offense['location']['column'].to_s
|
55
|
+
].join(':')
|
56
|
+
end
|
57
|
+
|
58
|
+
def rubocop_json
|
59
|
+
options, paths = RuboCop::Options.new.parse(
|
60
|
+
[
|
61
|
+
"--format", "json",
|
62
|
+
"--config", Rails.root.join(".rubocop.yml").to_s,
|
63
|
+
project_data_bridge.project_path
|
64
|
+
]
|
65
|
+
)
|
66
|
+
|
67
|
+
runner = RuboCop::Runner.new(options, RuboCop::ConfigStore.new)
|
68
|
+
rubocop_result_content = OutputUtils.with_captured_stdout { runner.run(paths) }
|
69
|
+
JSON.parse(rubocop_result_content)
|
70
|
+
rescue RuboCop::Error
|
71
|
+
{ "files" => [] }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class TestsInfoExtractor < ProjectInfoExtractor
|
3
|
+
TESTEABLE_RAILS_RESOURCES = %i{
|
4
|
+
controllers
|
5
|
+
mailers
|
6
|
+
models
|
7
|
+
jobs
|
8
|
+
policies
|
9
|
+
commands
|
10
|
+
services
|
11
|
+
observers
|
12
|
+
values
|
13
|
+
utils
|
14
|
+
clients
|
15
|
+
}
|
16
|
+
|
17
|
+
EXCLUDED_FILES = %w{
|
18
|
+
previews
|
19
|
+
application_record
|
20
|
+
application_controller
|
21
|
+
base_controller
|
22
|
+
application_job
|
23
|
+
concern
|
24
|
+
application_mailer
|
25
|
+
application_policy
|
26
|
+
default_policy
|
27
|
+
admin_user_policy
|
28
|
+
comment_policy
|
29
|
+
page_policy
|
30
|
+
}
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def extracted_info
|
35
|
+
{
|
36
|
+
swagger_tests_count: swagger_tests,
|
37
|
+
system_tests_count: system_tests_count,
|
38
|
+
jest_tests_count: jest_tests_count,
|
39
|
+
rails_code_coverage: rails_code_coverage.merge(total: coverage_total)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def swagger_tests
|
44
|
+
project_data_bridge.dir_files("spec/integration").count
|
45
|
+
end
|
46
|
+
|
47
|
+
def system_tests_count
|
48
|
+
project_data_bridge.dir_files("spec/system").count
|
49
|
+
end
|
50
|
+
|
51
|
+
def jest_tests_count
|
52
|
+
project_data_bridge.dir_files("app/javascript").count do |file|
|
53
|
+
file.include?("spec.js")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def rails_code_coverage
|
58
|
+
TESTEABLE_RAILS_RESOURCES.inject({}) do |data, resource|
|
59
|
+
tests_count = project_data_bridge.dir_files("spec/#{resource}").count do |file|
|
60
|
+
valid_file?(file)
|
61
|
+
end
|
62
|
+
files_count = project_data_bridge.dir_files("app/#{resource}").count do |file|
|
63
|
+
valid_file?(file)
|
64
|
+
end
|
65
|
+
data[resource] = { files_count: files_count, tests_count: tests_count }
|
66
|
+
coverage_total[:files_count] += files_count
|
67
|
+
coverage_total[:tests_count] += tests_count
|
68
|
+
data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_file?(file)
|
73
|
+
EXCLUDED_FILES.each do |key|
|
74
|
+
return false if file.include?(key) || !file.ends_with?(".rb")
|
75
|
+
end
|
76
|
+
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def coverage_total
|
81
|
+
@coverage_total ||= {
|
82
|
+
files_count: 0,
|
83
|
+
tests_count: 0
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class ExtractProjectInfoJob < ApplicationJob
|
3
|
+
def perform(repo_name)
|
4
|
+
project_info = {}
|
5
|
+
bridge = RepoAnalyzer::ProjectDataBridge.new(repo_name)
|
6
|
+
|
7
|
+
for_each_extractor do |extractor|
|
8
|
+
extracted_data = extractor.new(bridge).extract
|
9
|
+
project_info.merge!(extracted_data)
|
10
|
+
end
|
11
|
+
|
12
|
+
project_info
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def for_each_extractor(&block)
|
18
|
+
extractors_from_directory.each do |extractor|
|
19
|
+
next if extractor == ::RepoAnalyzer::ProjectInfoExtractor
|
20
|
+
|
21
|
+
block.call(extractor)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def extractors_from_directory
|
26
|
+
Dir[
|
27
|
+
RepoAnalyzer::Engine.root.join("app/extractors/repo_analyzer/*.rb")
|
28
|
+
].map do |path|
|
29
|
+
file_name = path.split('/').last
|
30
|
+
extractor_name = file_name.split('.').first.camelize
|
31
|
+
Object.const_get("::RepoAnalyzer::#{extractor_name}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
require 'base64'
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module RepoAnalyzer
|
7
|
+
class PostExtractedInfoJob < ApplicationJob
|
8
|
+
def perform(repo_name, project_info)
|
9
|
+
encoded_project_info = encode_project_info(project_info)
|
10
|
+
response = post_project_info(repo_name, encoded_project_info)
|
11
|
+
response.is_a?(Net::HTTPCreated)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def post_project_info(repo_name, encoded_project_info)
|
17
|
+
uri = URI(RepoAnalyzer.post_extracted_info_endpoint)
|
18
|
+
data = { repo_name: repo_name, project_info: encoded_project_info }.to_json
|
19
|
+
headers = { "Content-Type" => "application/json" }
|
20
|
+
Net::HTTP.post(uri, data, headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
def encode_project_info(project_info)
|
24
|
+
compressed_data = Zlib::Deflate.deflate(project_info.to_json)
|
25
|
+
Base64.encode64(compressed_data)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class ProjectDataBridge
|
3
|
+
EXCLUDED_FILE_NAMES = %w{. .. .keep .gitkeep}
|
4
|
+
|
5
|
+
attr_reader :project_path
|
6
|
+
|
7
|
+
def initialize(repo_name, project_path = Rails.root.to_s)
|
8
|
+
@repo_name = repo_name
|
9
|
+
@project_path = project_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def file_exist?(file_path)
|
13
|
+
File.exist?("#{project_path}/#{file_path}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def file_content(file_path)
|
17
|
+
return unless file_exist?(file_path)
|
18
|
+
|
19
|
+
File.open("#{project_path}/#{file_path}").read
|
20
|
+
end
|
21
|
+
|
22
|
+
def dir_files(dir_path)
|
23
|
+
Dir.glob("#{project_path}/#{dir_path}/**/*").reject do |file_name|
|
24
|
+
EXCLUDED_FILE_NAMES.include?(file_name)
|
25
|
+
end
|
26
|
+
rescue Errno::ENOENT
|
27
|
+
[]
|
28
|
+
end
|
29
|
+
|
30
|
+
def commits(options = {})
|
31
|
+
github_client.commits(repo_name, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def contributors
|
35
|
+
github_client.contributors(repo_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :repo_name
|
41
|
+
|
42
|
+
def github_client
|
43
|
+
@github_client ||= RepoAnalyzer::GithubClient.new(
|
44
|
+
RepoAnalyzer.github_personal_token
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/bin/rails
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/repo_analyzer/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../spec/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
|
12
|
+
|
13
|
+
require "rails"
|
14
|
+
# Pick the frameworks you want:
|
15
|
+
require "active_model/railtie"
|
16
|
+
require "active_job/railtie"
|
17
|
+
require "active_record/railtie"
|
18
|
+
require "active_storage/engine"
|
19
|
+
require "action_controller/railtie"
|
20
|
+
require "action_mailer/railtie"
|
21
|
+
require "action_view/railtie"
|
22
|
+
require "action_cable/engine"
|
23
|
+
require "sprockets/railtie"
|
24
|
+
# require "rails/test_unit/railtie"
|
25
|
+
require "rails/engine/commands"
|
data/config/routes.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class RepoAnalyzer::InstallGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('../templates', __FILE__)
|
3
|
+
|
4
|
+
def create_initializer
|
5
|
+
template "initializer.rb", "config/initializers/repo_analyzer.rb"
|
6
|
+
end
|
7
|
+
|
8
|
+
def mount_routes
|
9
|
+
line = "Rails.application.routes.draw do\n"
|
10
|
+
inject_into_file "config/routes.rb", after: line do <<-"HERE".gsub(/^ {4}/, '')
|
11
|
+
mount RepoAnalyzer::Engine => "/repo_analyzer"
|
12
|
+
HERE
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def copy_engine_migrations
|
17
|
+
rake "railties:install:migrations"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RepoAnalyzer
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace RepoAnalyzer
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, fixture: false
|
7
|
+
g.fixture_replacement :factory_bot, dir: "spec/factories"
|
8
|
+
end
|
9
|
+
|
10
|
+
initializer "initialize" do
|
11
|
+
# Require here all your engine's classes.
|
12
|
+
require_relative "./example_class"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|