loading
Generated 2025-10-08T23:58:29+00:00

All Files ( 100.0% covered at 32.79 hits/line )

71 files in total.
1138 relevant lines, 1138 lines covered and 0 lines missed. ( 100.0% )
136 total branches, 136 branches covered and 0 branches missed. ( 100.0% )
File % covered Lines Relevant Lines Lines covered Lines missed Avg. Hits / Line Branch Coverage Branches Covered branches Missed branches
lib/git/lint.rb 100.00 % 24 11 11 0 1.45 100.00 % 0 0 0
lib/git/lint/../../../git-lint.gemspec 100.00 % 45 30 30 0 1.00 100.00 % 0 0 0
lib/git/lint/analyzer.rb 100.00 % 95 27 27 0 745.96 100.00 % 2 2 0
lib/git/lint/analyzers/abstract.rb 100.00 % 72 36 36 0 151.31 100.00 % 6 6 0
lib/git/lint/analyzers/commit_author_capitalization.rb 100.00 % 20 9 9 0 1.11 100.00 % 2 2 0
lib/git/lint/analyzers/commit_author_email.rb 100.00 % 20 9 9 0 1.11 100.00 % 2 2 0
lib/git/lint/analyzers/commit_author_name.rb 100.00 % 24 11 11 0 1.09 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_bullet_capitalization.rb 100.00 % 35 15 15 0 11.53 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_bullet_delimiter.rb 100.00 % 31 13 13 0 8.08 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_bullet_only.rb 100.00 % 29 12 12 0 4.17 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_leading_line.rb 100.00 % 25 12 12 0 32.17 100.00 % 4 4 0
lib/git/lint/analyzers/commit_body_line_length.rb 100.00 % 29 12 12 0 2.00 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_paragraph_capitalization.rb 100.00 % 61 19 19 0 11.26 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_phrase.rb 100.00 % 31 13 13 0 19.08 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_presence.rb 100.00 % 27 13 13 0 2.31 100.00 % 4 4 0
lib/git/lint/analyzers/commit_body_tracker_shorthand.rb 100.00 % 29 12 12 0 10.33 100.00 % 2 2 0
lib/git/lint/analyzers/commit_body_word_repeat.rb 100.00 % 31 13 13 0 12.23 100.00 % 4 4 0
lib/git/lint/analyzers/commit_signature.rb 100.00 % 24 11 11 0 1.73 100.00 % 2 2 0
lib/git/lint/analyzers/commit_subject_length.rb 100.00 % 22 10 10 0 1.10 100.00 % 2 2 0
lib/git/lint/analyzers/commit_subject_prefix.rb 100.00 % 37 19 19 0 37.16 100.00 % 6 6 0
lib/git/lint/analyzers/commit_subject_suffix.rb 100.00 % 28 13 13 0 15.15 100.00 % 4 4 0
lib/git/lint/analyzers/commit_subject_word_repeat.rb 100.00 % 20 9 9 0 1.11 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_collaborator_capitalization.rb 100.00 % 35 13 13 0 3.85 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_collaborator_email.rb 100.00 % 38 15 15 0 3.33 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_collaborator_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_collaborator_name.rb 100.00 % 39 15 15 0 3.60 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_duplicate.rb 100.00 % 25 10 10 0 1.10 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_format_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_format_value.rb 100.00 % 35 15 15 0 4.00 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_issue_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_issue_value.rb 100.00 % 35 15 15 0 4.00 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_milestone_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_milestone_value.rb 100.00 % 35 15 15 0 4.00 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_order.rb 100.00 % 38 18 18 0 5.28 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_reviewer_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_reviewer_value.rb 100.00 % 35 15 15 0 4.00 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_signer_capitalization.rb 100.00 % 35 13 13 0 3.85 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_signer_email.rb 100.00 % 38 15 15 0 3.33 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_signer_key.rb 100.00 % 35 15 15 0 5.40 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_signer_name.rb 100.00 % 39 15 15 0 3.60 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_tracker_key.rb 100.00 % 33 14 14 0 3.79 100.00 % 2 2 0
lib/git/lint/analyzers/commit_trailer_tracker_value.rb 100.00 % 35 15 15 0 4.00 100.00 % 2 2 0
lib/git/lint/cli/actions/analyze/branch.rb 100.00 % 45 23 23 0 3.17 100.00 % 2 2 0
lib/git/lint/cli/actions/analyze/commit.rb 100.00 % 45 23 23 0 3.43 100.00 % 2 2 0
lib/git/lint/cli/actions/hook.rb 100.00 % 39 20 20 0 2.95 100.00 % 2 2 0
lib/git/lint/cli/shell.rb 100.00 % 47 25 25 0 5.28 100.00 % 0 0 0
lib/git/lint/collector.rb 100.00 % 43 21 21 0 79.62 100.00 % 0 0 0
lib/git/lint/commits/hosts/circle_ci.rb 100.00 % 22 10 10 0 1.00 100.00 % 0 0 0
lib/git/lint/commits/hosts/git_hub_action.rb 100.00 % 22 10 10 0 1.00 100.00 % 0 0 0
lib/git/lint/commits/hosts/local.rb 100.00 % 22 10 10 0 1.00 100.00 % 0 0 0
lib/git/lint/commits/hosts/netlify_ci.rb 100.00 % 26 13 13 0 1.46 100.00 % 0 0 0
lib/git/lint/commits/loader.rb 100.00 % 42 18 18 0 5.83 100.00 % 8 8 0
lib/git/lint/configuration/contract.rb 100.00 % 111 103 103 0 1.00 100.00 % 0 0 0
lib/git/lint/configuration/model.rb 100.00 % 105 4 4 0 1.00 100.00 % 0 0 0
lib/git/lint/configuration/trailer.rb 100.00 % 10 4 4 0 1.00 100.00 % 0 0 0
lib/git/lint/dependencies.rb 100.00 % 9 4 4 0 1.00 100.00 % 0 0 0
lib/git/lint/errors/base.rb 100.00 % 14 6 6 0 2.50 100.00 % 0 0 0
lib/git/lint/errors/severity.rb 100.00 % 19 9 9 0 1.44 100.00 % 0 0 0
lib/git/lint/errors/sha.rb 100.00 % 14 6 6 0 1.00 100.00 % 0 0 0
lib/git/lint/kit/filter_list.rb 100.00 % 31 15 15 0 226.40 100.00 % 0 0 0
lib/git/lint/rake/register.rb 100.00 % 30 15 15 0 1.27 100.00 % 0 0 0
lib/git/lint/reporters/branch.rb 100.00 % 73 39 39 0 8.26 100.00 % 10 10 0
lib/git/lint/reporters/commit.rb 100.00 % 30 14 14 0 6.29 100.00 % 2 2 0
lib/git/lint/reporters/line.rb 100.00 % 30 14 14 0 1.57 100.00 % 2 2 0
lib/git/lint/reporters/lines/paragraph.rb 100.00 % 44 21 21 0 1.95 100.00 % 2 2 0
lib/git/lint/reporters/lines/sentence.rb 100.00 % 30 14 14 0 1.36 100.00 % 0 0 0
lib/git/lint/reporters/style.rb 100.00 % 52 27 27 0 8.59 100.00 % 6 6 0
lib/git/lint/validators/capitalization.rb 100.00 % 23 11 11 0 33.55 100.00 % 0 0 0
lib/git/lint/validators/email.rb 100.00 % 22 10 10 0 4.90 100.00 % 0 0 0
lib/git/lint/validators/name.rb 100.00 % 30 15 15 0 40.00 100.00 % 0 0 0
lib/git/lint/validators/repeated_word.rb 100.00 % 51 17 17 0 35.71 100.00 % 4 4 0

lib/git/lint.rb

100.0% lines covered

100.0% branches covered

11 relevant lines. 11 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "zeitwerk"
  3. 1 Zeitwerk::Loader.new.then do |loader|
  4. 1 loader.inflector.inflect "cli" => "CLI",
  5. "sha" => "SHA",
  6. "circle_ci" => "CircleCI",
  7. "netlify_ci" => "NetlifyCI",
  8. "travis_ci" => "TravisCI"
  9. 1 loader.tag = "git-lint"
  10. 1 loader.ignore "#{__dir__}/lint/rake"
  11. 1 loader.push_dir "#{__dir__}/.."
  12. 1 loader.setup
  13. end
  14. 1 module Git
  15. # Main namespace.
  16. 1 module Lint
  17. 1 def self.loader registry = Zeitwerk::Registry
  18. 6 @loader ||= registry.loaders.each.find { |loader| loader.tag == "git-lint" }
  19. end
  20. end
  21. end

lib/git/lint/../../../git-lint.gemspec

100.0% lines covered

100.0% branches covered

30 relevant lines. 30 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 Gem::Specification.new do |spec|
  3. 1 spec.name = "git-lint"
  4. 1 spec.version = "9.6.0"
  5. 1 spec.authors = ["Brooke Kuhlmann"]
  6. 1 spec.email = ["brooke@alchemists.io"]
  7. 1 spec.homepage = "https://alchemists.io/projects/git-lint"
  8. 1 spec.summary = "A command line interface for linting Git commits."
  9. 1 spec.license = "Hippocratic-2.1"
  10. 1 spec.metadata = {
  11. "bug_tracker_uri" => "https://github.com/bkuhlmann/git-lint/issues",
  12. "changelog_uri" => "https://alchemists.io/projects/git-lint/versions",
  13. "homepage_uri" => "https://alchemists.io/projects/git-lint",
  14. "funding_uri" => "https://github.com/sponsors/bkuhlmann",
  15. "label" => "Git Lint",
  16. "rubygems_mfa_required" => "true",
  17. "source_code_uri" => "https://github.com/bkuhlmann/git-lint"
  18. }
  19. 1 spec.signing_key = Gem.default_key_path
  20. 1 spec.cert_chain = [Gem.default_cert_path]
  21. 1 spec.required_ruby_version = "~> 3.4"
  22. 1 spec.add_dependency "cogger", "~> 1.0"
  23. 1 spec.add_dependency "containable", "~> 1.1"
  24. 1 spec.add_dependency "core", "~> 2.0"
  25. 1 spec.add_dependency "dry-monads", "~> 1.9"
  26. 1 spec.add_dependency "dry-schema", "~> 1.13"
  27. 1 spec.add_dependency "etcher", "~> 3.0"
  28. 1 spec.add_dependency "gitt", "~> 4.1"
  29. 1 spec.add_dependency "infusible", "~> 4.0"
  30. 1 spec.add_dependency "refinements", "~> 13.5"
  31. 1 spec.add_dependency "runcom", "~> 12.0"
  32. 1 spec.add_dependency "sod", "~> 1.0"
  33. 1 spec.add_dependency "spek", "~> 4.0"
  34. 1 spec.add_dependency "tone", "~> 2.0"
  35. 1 spec.add_dependency "zeitwerk", "~> 2.7"
  36. 1 spec.bindir = "exe"
  37. 1 spec.executables << "git-lint"
  38. 1 spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
  39. 1 spec.files = Dir["*.gemspec", "lib/**/*"]
  40. end

lib/git/lint/analyzer.rb

100.0% lines covered

100.0% branches covered

27 relevant lines. 27 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. # Runs all analyzers.
  5. 1 class Analyzer
  6. 1 include Dependencies[:settings]
  7. ANALYZERS = [
  8. 1 Analyzers::CommitAuthorCapitalization,
  9. Analyzers::CommitAuthorEmail,
  10. Analyzers::CommitAuthorName,
  11. Analyzers::CommitBodyBulletCapitalization,
  12. Analyzers::CommitBodyBulletDelimiter,
  13. Analyzers::CommitBodyBulletOnly,
  14. Analyzers::CommitBodyLeadingLine,
  15. Analyzers::CommitBodyLineLength,
  16. Analyzers::CommitBodyParagraphCapitalization,
  17. Analyzers::CommitBodyPhrase,
  18. Analyzers::CommitBodyPresence,
  19. Analyzers::CommitBodyTrackerShorthand,
  20. Analyzers::CommitBodyWordRepeat,
  21. Analyzers::CommitSignature,
  22. Analyzers::CommitSubjectLength,
  23. Analyzers::CommitSubjectPrefix,
  24. Analyzers::CommitSubjectSuffix,
  25. Analyzers::CommitSubjectWordRepeat,
  26. Analyzers::CommitTrailerCollaboratorCapitalization,
  27. Analyzers::CommitTrailerCollaboratorEmail,
  28. Analyzers::CommitTrailerCollaboratorKey,
  29. Analyzers::CommitTrailerCollaboratorName,
  30. Analyzers::CommitTrailerDuplicate,
  31. Analyzers::CommitTrailerFormatKey,
  32. Analyzers::CommitTrailerFormatValue,
  33. Analyzers::CommitTrailerIssueKey,
  34. Analyzers::CommitTrailerIssueValue,
  35. Analyzers::CommitTrailerMilestoneKey,
  36. Analyzers::CommitTrailerMilestoneValue,
  37. Analyzers::CommitTrailerOrder,
  38. Analyzers::CommitTrailerReviewerKey,
  39. Analyzers::CommitTrailerReviewerValue,
  40. Analyzers::CommitTrailerSignerCapitalization,
  41. Analyzers::CommitTrailerSignerEmail,
  42. Analyzers::CommitTrailerSignerKey,
  43. Analyzers::CommitTrailerSignerName,
  44. Analyzers::CommitTrailerTrackerKey,
  45. Analyzers::CommitTrailerTrackerValue
  46. ].freeze
  47. # rubocop:disable Metrics/ParameterLists
  48. 1 def initialize(
  49. analyzers: ANALYZERS,
  50. collector: Collector.new,
  51. reporter: Reporters::Branch,
  52. **
  53. )
  54. 43 super(**)
  55. 43 @analyzers = analyzers
  56. 43 @collector = collector
  57. 43 @reporter = reporter
  58. end
  59. # rubocop:enable Metrics/ParameterLists
  60. 1 def call commits: Commits::Loader.new.call
  61. 22 process commits
  62. 22 a_reporter = reporter.new(collector:)
  63. 22 then: 17 else: 5 block_given? ? yield(collector, a_reporter) : [collector, a_reporter]
  64. end
  65. 1 private
  66. 1 attr_reader :analyzers, :collector, :reporter
  67. 1 def process commits
  68. 22 collector.clear
  69. 43 commits.value_or([]).map { |commit| analyze commit }
  70. end
  71. 735 def analyze(commit) = enabled.map { |id| collector.add load_analyzer(commit, id) }
  72. # :reek:FeatureEnvy
  73. 1 def enabled
  74. 21 settings.to_h
  75. 2016 .select { |key, value| key.end_with?("enabled") && value == true }
  76. .keys
  77. 734 .map { |key| key.to_s.sub("commits_", "commit_").delete_suffix! "_enabled" }
  78. end
  79. 1 def load_analyzer commit, id
  80. 15586 analyzers.find { |analyzer| analyzer.id == id }
  81. 734 .then { |analyzer| analyzer.new commit }
  82. end
  83. end
  84. end
  85. end

lib/git/lint/analyzers/abstract.rb

100.0% lines covered

100.0% branches covered

36 relevant lines. 36 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 require "refinements/string"
  4. 1 module Git
  5. 1 module Lint
  6. 1 module Analyzers
  7. # An abstract class which provides basic functionality for all analyzers to inherit from.
  8. 1 class Abstract
  9. 1 include Dependencies[:settings, :environment]
  10. 1 using Refinements::String
  11. 1 LEVELS = %w[warn error].freeze
  12. 1 BODY_OFFSET = 3
  13. 1 def self.id = to_s.delete_prefix!("Git::Lint::Analyzers").snakecase
  14. 1 def self.label = to_s.delete_prefix("Git::Lint::Analyzers").titleize
  15. 1 def self.build_issue_line(index, line) = {number: index + BODY_OFFSET, content: line}
  16. 1 attr_reader :commit
  17. 1 def initialize(commit, **)
  18. 1059 super(**)
  19. 1059 @commit = commit
  20. 1059 @filter_list = load_filter_list
  21. end
  22. 1 def severity
  23. 92 settings.public_send("#{self.class.id}_severity".sub("commit_", "commits_"))
  24. 92 else: 90 then: 2 .tap { |level| fail Errors::Severity, level unless LEVELS.include? level }
  25. end
  26. 1 def valid? = fail NoMethodError, "The `##{__method__}` method must be implemented."
  27. 1 def invalid? = !valid?
  28. 1 def warning? = invalid? && severity == "warn"
  29. 1 def error? = invalid? && severity == "error"
  30. 1 def issue = fail NoMethodError, "The `##{__method__}` method must be implemented."
  31. 1 protected
  32. 1 attr_reader :filter_list
  33. 1 def load_filter_list = Core::EMPTY_ARRAY
  34. 1 def affected_commit_body_lines
  35. 138 commit.body_lines.each.with_object([]).with_index do |(line, lines), index|
  36. 94 then: 34 else: 60 lines << self.class.build_issue_line(index, line) if invalid_line? line
  37. end
  38. end
  39. 1 def affected_commit_trailers
  40. 1376 commit.trailers
  41. .each
  42. .with_object([])
  43. .with_index(commit.body_lines.size) do |(trailer, trailers), index|
  44. 381 else: 71 then: 310 next unless invalid_line? trailer
  45. 71 trailers << self.class.build_issue_line(index, trailer.to_s)
  46. end
  47. end
  48. end
  49. end
  50. end
  51. end

lib/git/lint/analyzers/commit_author_capitalization.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes author name for proper capitalization.
  6. 1 class CommitAuthorCapitalization < Abstract
  7. 1 include Dependencies[validator: "validators.capitalization"]
  8. 1 def valid? = validator.call commit.author_name
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. 1 {hint: %(Capitalize each part of name: "#{commit.author_name}".)}
  12. end
  13. end
  14. end
  15. end
  16. end

lib/git/lint/analyzers/commit_author_email.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes author email address for proper format.
  6. 1 class CommitAuthorEmail < Abstract
  7. 1 include Dependencies[validator: "validators.email"]
  8. 1 def valid? = validator.call commit.author_email
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. 1 {hint: %(Use "<name>@<server>.<domain>" instead of "#{commit.author_email}".)}
  12. end
  13. end
  14. end
  15. end
  16. end

lib/git/lint/analyzers/commit_author_name.rb

100.0% lines covered

100.0% branches covered

11 relevant lines. 11 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes author name for minimum parts of name.
  6. 1 class CommitAuthorName < Abstract
  7. 1 include Dependencies[validator: "validators.name"]
  8. 1 def valid? = validator.call(commit.author_name, minimum:)
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. 1 {hint: "Author name must consist of #{minimum} parts (minimum)."}
  12. end
  13. 1 private
  14. 1 def minimum = settings.commits_author_name_minimum
  15. end
  16. end
  17. end
  18. end

lib/git/lint/analyzers/commit_body_bullet_capitalization.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit body for proper capitalization of bullet sentences.
  6. 1 class CommitBodyBulletCapitalization < Abstract
  7. 1 def valid? = lowercased_bullets.empty?
  8. 1 def issue
  9. 3 then: 1 else: 2 return {} if valid?
  10. 2 {
  11. hint: "Capitalize first word.",
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 31 Kit::FilterList.new settings.commits_body_bullet_capitalization_includes
  18. end
  19. 1 def invalid_line? line
  20. 67 line.sub(/link:.+(?=\[)/, "").match?(/\A\s*#{Regexp.union filter_list}\s[[:lower:]]+/)
  21. end
  22. 1 private
  23. 60 def lowercased_bullets = commit.body_lines.select { |line| invalid_line? line }
  24. end
  25. end
  26. end
  27. end

lib/git/lint/analyzers/commit_body_bullet_delimiter.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit body delimiter usage.
  6. 1 class CommitBodyBulletDelimiter < Abstract
  7. 60 def valid? = commit.body_lines.none? { |line| invalid_line? line }
  8. 1 def issue
  9. 3 then: 1 else: 2 return {} if valid?
  10. 2 {
  11. hint: "Use space after bullet.",
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 31 Kit::FilterList.new settings.commits_body_bullet_delimiter_includes
  18. end
  19. 1 def invalid_line?(line) = line.match?(/\A\s*#{pattern}(?!(#{pattern}|\s)).+\Z/)
  20. 1 def pattern = Regexp.union filter_list
  21. end
  22. end
  23. end
  24. end

lib/git/lint/analyzers/commit_body_bullet_only.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit bodies with only a single bullet point.
  6. 1 class CommitBodyBulletOnly < Abstract
  7. 1 def valid? = !affected_commit_body_lines.one?
  8. 1 def issue
  9. 5 then: 1 else: 4 return {} if valid?
  10. 4 {
  11. hint: "Use paragraph instead of single bullet.",
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 32 Kit::FilterList.new settings.commits_body_bullet_only_includes
  18. end
  19. 1 def invalid_line?(line) = line.match?(/\A#{Regexp.union filter_list}\s+/)
  20. end
  21. end
  22. end
  23. end

lib/git/lint/analyzers/commit_body_leading_line.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes leading line between commit subject and start of body.
  6. 1 class CommitBodyLeadingLine < Abstract
  7. 1 def valid?
  8. 119 raw = commit.raw
  9. 119 subject, body = raw.split "\n", 2
  10. 119 then: 99 else: 20 return true if !String(subject).empty? && String(body).strip.empty?
  11. 20 raw.match?(/\A.+(\n\n|\#).+/m)
  12. end
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {hint: "Use blank line between subject and body."}
  16. end
  17. end
  18. end
  19. end
  20. end

lib/git/lint/analyzers/commit_body_line_length.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit body line length to prevent unnecessary horizontal scrolling.
  6. 1 class CommitBodyLineLength < Abstract
  7. 10 def valid? = commit.body_lines.all? { |line| !invalid_line? line }
  8. 1 def issue
  9. 3 then: 1 else: 2 return {} if valid?
  10. {
  11. 2 hint: "Use #{maximum} characters or less per line.",
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def invalid_line?(line) = line.length > maximum
  17. 1 private
  18. 1 def maximum = settings.commits_body_line_length_maximum
  19. end
  20. end
  21. end
  22. end

lib/git/lint/analyzers/commit_body_paragraph_capitalization.rb

100.0% lines covered

100.0% branches covered

19 relevant lines. 19 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes proper capitalization of commit body paragraphs.
  6. 1 class CommitBodyParagraphCapitalization < Abstract
  7. 1 PATTERN = /
  8. \A # Search start.
  9. (?! # Negative lookahead start.
  10. (?: # Non-capture group start.
  11. audio # Ignore audio.
  12. | # Or.
  13. image # Ignore image.
  14. | # Or.
  15. video # Ignore video.
  16. ) # Non-capture group end.
  17. :: # Suffix.
  18. | # Or.
  19. link: # Ignore link.
  20. | # Or.
  21. xref: # Ignore xref.
  22. ) # Negative lookahead end.
  23. [[:lower:]] # Match lowercase letters.
  24. .+ # Match one or more characters.
  25. \Z # Search end.
  26. /mx
  27. 1 def initialize(commit, pattern: PATTERN, **)
  28. 34 super(commit, **)
  29. 34 @pattern = pattern
  30. end
  31. 1 def valid? = invalids.empty?
  32. 1 def issue
  33. 3 then: 1 else: 2 return {} if valid?
  34. 2 {
  35. hint: "Capitalize first word.",
  36. lines: affected_lines
  37. }
  38. end
  39. 1 private
  40. 1 attr_reader :pattern
  41. 1 def affected_lines
  42. 2 invalids.each.with_object [] do |line, lines|
  43. 2 lines << self.class.build_issue_line(commit.body_lines.index(line[/.+/]), line)
  44. end
  45. end
  46. 1 def invalids
  47. 125 @invalids ||= commit.body_paragraphs.grep pattern
  48. end
  49. end
  50. end
  51. end
  52. end

lib/git/lint/analyzers/commit_body_phrase.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes use of commit body phrases that are not informative.
  6. 1 class CommitBodyPhrase < Abstract
  7. 85 def valid? = commit.body_lines.all? { |line| !invalid_line? line }
  8. 1 def issue
  9. 3 then: 1 else: 2 return {} if valid?
  10. {
  11. 2 hint: %(Avoid: #{filter_list.to_usage}.),
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 62 Kit::FilterList.new settings.commits_body_phrase_excludes
  18. end
  19. 1 def invalid_line? line
  20. 88 line.downcase.match? Regexp.new(Regexp.union(filter_list).source, Regexp::IGNORECASE)
  21. end
  22. end
  23. end
  24. end
  25. end

lib/git/lint/analyzers/commit_body_presence.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes presence of commit body.
  6. 1 class CommitBodyPresence < Abstract
  7. 1 using Refinements::String
  8. 1 def valid?
  9. 7 then: 1 else: 6 return true if commit.fixup?
  10. 6 valid_lines = commit.body_lines.grep_v(/^\s*$/)
  11. 6 valid_lines.size >= minimum
  12. end
  13. 1 def minimum = settings.commits_body_presence_minimum
  14. 1 def issue
  15. 2 then: 1 else: 1 return {} if valid?
  16. 1 {hint: %(Use minimum of #{"#{minimum} line".pluralize "s", minimum} (non-empty).)}
  17. end
  18. end
  19. end
  20. end
  21. end

lib/git/lint/analyzers/commit_body_tracker_shorthand.rb

100.0% lines covered

100.0% branches covered

12 relevant lines. 12 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes body tracker shorthand usage.
  6. 1 class CommitBodyTrackerShorthand < Abstract
  7. 68 def valid? = commit.body_lines.none? { |line| invalid_line? line }
  8. 1 def issue
  9. 3 then: 1 else: 2 return {} if valid?
  10. {
  11. 2 hint: "Explain issue instead of using shorthand. Avoid: #{filter_list.to_usage}.",
  12. lines: affected_commit_body_lines
  13. }
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 43 Kit::FilterList.new settings.commits_body_tracker_shorthand_excludes
  18. end
  19. 1 def invalid_line?(line) = line.match?(/.*#{Regexp.union filter_list}.*/)
  20. end
  21. end
  22. end
  23. end

lib/git/lint/analyzers/commit_body_word_repeat.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit body for repeated words.
  6. 1 class CommitBodyWordRepeat < Abstract
  7. 1 include Dependencies[validator: "validators.repeated_word"]
  8. 51 def valid? = commit.body_lines.all? { |line| !invalid_line? line }
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Avoid repeating these words: #{validator.call commit.body}.",
  13. lines: affected_commit_body_lines
  14. }
  15. end
  16. 1 protected
  17. 1 def invalid_line? line
  18. 52 then: 7 else: 45 return false if line.start_with? "#"
  19. 45 !validator.call(line).empty?
  20. end
  21. end
  22. end
  23. end
  24. end

lib/git/lint/analyzers/commit_signature.rb

100.0% lines covered

100.0% branches covered

11 relevant lines. 11 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit signature validity.
  6. 1 class CommitSignature < Abstract
  7. 1 include Dependencies[sanitizer: "sanitizers.signature"]
  8. 1 def valid?
  9. 4 sanitizer.call(commit.signature).match?(/\A#{Regexp.union filter_list}\Z/)
  10. end
  11. 3 then: 1 else: 1 def issue = valid? ? {} : {hint: %(Use: #{filter_list.to_usage "or"}.)}
  12. 1 protected
  13. 1 def load_filter_list
  14. 4 Kit::FilterList.new settings.commits_signature_includes
  15. end
  16. end
  17. end
  18. end
  19. end

lib/git/lint/analyzers/commit_subject_length.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit subject length is short and concise.
  6. 1 class CommitSubjectLength < Abstract
  7. 1 def valid? = commit.subject.sub(/(fixup!|squash!)\s{1}/, "").size <= maximum
  8. 1 def issue
  9. 2 then: 1 else: 1 return {} if valid?
  10. 1 {hint: "Use #{maximum} characters or less."}
  11. end
  12. 1 private
  13. 1 def maximum = settings.commits_subject_length_maximum
  14. end
  15. end
  16. end
  17. end

lib/git/lint/analyzers/commit_subject_prefix.rb

100.0% lines covered

100.0% branches covered

19 relevant lines. 19 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit subject uses standard prefix.
  6. 1 class CommitSubjectPrefix < Abstract
  7. 1 def valid?
  8. 133 then: 3 else: 130 return true if locally_prefixed?
  9. 130 then: 1 else: 129 return true if filter_list.empty?
  10. 129 commit.subject.match?(/\A#{Regexp.union filter_list}/)
  11. end
  12. 1 def issue
  13. 11 then: 1 else: 10 return {} if valid?
  14. 10 {hint: %(Use: #{filter_list.to_usage "or"}.)}
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 42 settings.commits_subject_prefix_includes
  19. 198 .map { |prefix| "#{prefix}#{delimiter}" }
  20. 42 .then { |list| Kit::FilterList.new list }
  21. end
  22. 1 def locally_prefixed? = !ci? && commit.directive?
  23. 1 def ci? = environment["CI"] == "true"
  24. 1 def delimiter = settings.commits_subject_prefix_delimiter
  25. end
  26. end
  27. end
  28. end

lib/git/lint/analyzers/commit_subject_suffix.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit subject suffix for punctuation.
  6. 1 class CommitSubjectSuffix < Abstract
  7. 1 def valid?
  8. 76 then: 1 else: 75 return true if filter_list.empty?
  9. 75 !commit.subject.match?(/#{Regexp.union filter_list}\Z/)
  10. end
  11. 1 def issue
  12. 5 then: 1 else: 4 return {} if valid?
  13. 4 {hint: %(Avoid: #{filter_list.to_usage}.)}
  14. end
  15. 1 protected
  16. 1 def load_filter_list
  17. 29 Kit::FilterList.new settings.commits_subject_suffix_excludes
  18. end
  19. end
  20. end
  21. end
  22. end

lib/git/lint/analyzers/commit_subject_word_repeat.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit subject for repeated words.
  6. 1 class CommitSubjectWordRepeat < Abstract
  7. 1 include Dependencies[validator: "validators.repeated_word"]
  8. 1 def valid? = validator.call(commit.subject).empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. 1 {hint: "Avoid repeating these words: #{validator.call commit.subject}."}
  12. end
  13. end
  14. end
  15. end
  16. end

lib/git/lint/analyzers/commit_trailer_collaborator_capitalization.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer collaborator name capitalization.
  6. 1 class CommitTrailerCollaboratorCapitalization < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.collaborator",
  9. parser: "parsers.person",
  10. validator: "validators.capitalization"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {
  16. hint: "Name must be capitalized.",
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 19 parser.call(trailer.value).then do |person|
  23. 19 trailer.key.match?(setting.pattern) && !validator.call(person.name)
  24. end
  25. end
  26. end
  27. end
  28. end
  29. end

lib/git/lint/analyzers/commit_trailer_collaborator_email.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer collaborator email address format.
  6. 1 class CommitTrailerCollaboratorEmail < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.collaborator",
  9. parser: "parsers.person",
  10. validator: "validators.email"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {
  16. hint: %(Email must follow name and use format: "<name@server.domain>".),
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 18 email = parser.call(trailer.value).email
  23. 18 trailer.key.match?(setting.pattern) && !validator.call(email)
  24. end
  25. 1 private
  26. 1 attr_reader :parser, :validator
  27. end
  28. end
  29. end
  30. end

lib/git/lint/analyzers/commit_trailer_collaborator_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer collaborator key usage.
  6. 1 class CommitTrailerCollaboratorKey < Abstract
  7. 1 include Dependencies[setting: "trailers.collaborator"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_collaborator_name.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer collaborator name construction.
  6. 1 class CommitTrailerCollaboratorName < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.collaborator",
  9. parser: "parsers.person",
  10. validator: "validators.name"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. {
  16. 1 hint: "Name must follow key and consist of #{minimum} parts (minimum).",
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 20 parser.call(trailer.value).then do |person|
  23. 20 trailer.key.match?(setting.pattern) && !validator.call(person.name, minimum:)
  24. end
  25. end
  26. 1 private
  27. 1 def minimum = settings.commits_trailer_collaborator_name_minimum
  28. end
  29. end
  30. end
  31. end

lib/git/lint/analyzers/commit_trailer_duplicate.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer duplicate.
  6. 1 class CommitTrailerDuplicate < Abstract
  7. 1 def valid? = affected_commit_trailers.empty?
  8. 1 def issue
  9. 2 then: 1 else: 1 return {} if valid?
  10. 1 {
  11. hint: "Avoid duplicates.",
  12. lines: affected_commit_trailers
  13. }
  14. end
  15. 1 protected
  16. 1 def invalid_line?(trailer) = commit.trailers.tally[trailer] != 1
  17. end
  18. end
  19. end
  20. end

lib/git/lint/analyzers/commit_trailer_format_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer format key usage.
  6. 1 class CommitTrailerFormatKey < Abstract
  7. 1 include Dependencies[setting: "trailers.format"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_format_value.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer format value.
  6. 1 class CommitTrailerFormatValue < Abstract
  7. 1 include Dependencies[setting: "trailers.format"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage "or"}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 27 Kit::FilterList.new settings.commits_trailer_format_value_includes
  19. end
  20. 1 def invalid_line? trailer
  21. 19 trailer.key.match?(setting.pattern) && !trailer.value.match?(value_pattern)
  22. end
  23. 1 def value_pattern = /\A#{Regexp.union filter_list}\Z/
  24. end
  25. end
  26. end
  27. end

lib/git/lint/analyzers/commit_trailer_issue_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer issue key usage.
  6. 1 class CommitTrailerIssueKey < Abstract
  7. 1 include Dependencies[setting: "trailers.issue"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_issue_value.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer issue value.
  6. 1 class CommitTrailerIssueValue < Abstract
  7. 1 include Dependencies[setting: "trailers.issue"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 27 Kit::FilterList.new settings.commits_trailer_issue_value_includes
  19. end
  20. 1 def invalid_line? trailer
  21. 19 trailer.key.match?(setting.pattern) && !trailer.value.match?(value_pattern)
  22. end
  23. 1 def value_pattern = /\A#{Regexp.union filter_list}\Z/
  24. end
  25. end
  26. end
  27. end

lib/git/lint/analyzers/commit_trailer_milestone_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer milestone key usage.
  6. 1 class CommitTrailerMilestoneKey < Abstract
  7. 1 include Dependencies[setting: "trailers.milestone"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_milestone_value.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer milestone value.
  6. 1 class CommitTrailerMilestoneValue < Abstract
  7. 1 include Dependencies[setting: "trailers.milestone"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use: #{filter_list.to_usage "or"}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 27 Kit::FilterList.new settings.commits_trailer_milestone_value_includes
  19. end
  20. 1 def invalid_line? trailer
  21. 19 trailer.key.match?(setting.pattern) && !trailer.value.match?(value_pattern)
  22. end
  23. 1 def value_pattern = /\A#{Regexp.union filter_list}\Z/
  24. end
  25. end
  26. end
  27. end

lib/git/lint/analyzers/commit_trailer_order.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer order value.
  6. 1 class CommitTrailerOrder < Abstract
  7. 1 def initialize(...)
  8. 25 super
  9. 25 @original_order = commit.trailers.map(&:key)
  10. 25 @sorted_order = original_order.sort
  11. end
  12. 1 def valid? = original_order == sorted_order
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {
  16. hint: "Ensure keys are alphabetically sorted.",
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 3 key = trailer.key
  23. 3 original_order.index(key) != sorted_order.index(key)
  24. end
  25. 1 private
  26. 1 attr_reader :original_order, :sorted_order
  27. end
  28. end
  29. end
  30. end

lib/git/lint/analyzers/commit_trailer_reviewer_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer reviwer key usage.
  6. 1 class CommitTrailerReviewerKey < Abstract
  7. 1 include Dependencies[setting: "trailers.reviewer"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_reviewer_value.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer reviewer value.
  6. 1 class CommitTrailerReviewerValue < Abstract
  7. 1 include Dependencies[setting: "trailers.reviewer"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use: #{filter_list.to_usage "or"}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 27 Kit::FilterList.new settings.commits_trailer_reviewer_value_includes
  19. end
  20. 1 def invalid_line? trailer
  21. 19 trailer.key.match?(setting.pattern) && !trailer.value.match?(value_pattern)
  22. end
  23. 1 def value_pattern = /\A#{Regexp.union filter_list}\Z/
  24. end
  25. end
  26. end
  27. end

lib/git/lint/analyzers/commit_trailer_signer_capitalization.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer signer name capitalization.
  6. 1 class CommitTrailerSignerCapitalization < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.signer",
  9. parser: "parsers.person",
  10. validator: "validators.capitalization"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {
  16. hint: "Name must be capitalized.",
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 19 parser.call(trailer.value).then do |person|
  23. 19 trailer.key.match?(setting.pattern) && !validator.call(person.name)
  24. end
  25. end
  26. end
  27. end
  28. end
  29. end

lib/git/lint/analyzers/commit_trailer_signer_email.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer signer email address format.
  6. 1 class CommitTrailerSignerEmail < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.signer",
  9. parser: "parsers.person",
  10. validator: "validators.email"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. 1 {
  16. hint: %(Email must follow name and use format: "<name@server.domain>".),
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 18 email = parser.call(trailer.value).email
  23. 18 trailer.key.match?(setting.pattern) && !validator.call(email)
  24. end
  25. 1 private
  26. 1 attr_reader :parser, :validator
  27. end
  28. end
  29. end
  30. end

lib/git/lint/analyzers/commit_trailer_signer_key.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer signer key usage.
  6. 1 class CommitTrailerSignerKey < Abstract
  7. 1 include Dependencies[setting: "trailers.signer"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 28 Kit::FilterList.new setting.name
  19. end
  20. 1 def invalid_line? trailer
  21. 20 trailer.key.then do |key|
  22. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  23. end
  24. end
  25. end
  26. end
  27. end
  28. end

lib/git/lint/analyzers/commit_trailer_signer_name.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer signer name construction.
  6. 1 class CommitTrailerSignerName < Abstract
  7. 1 include Dependencies[
  8. setting: "trailers.signer",
  9. parser: "parsers.person",
  10. validator: "validators.name"
  11. ]
  12. 1 def valid? = affected_commit_trailers.empty?
  13. 1 def issue
  14. 2 then: 1 else: 1 return {} if valid?
  15. {
  16. 1 hint: "Name must follow key and consist of #{minimum} parts (minimum).",
  17. lines: affected_commit_trailers
  18. }
  19. end
  20. 1 protected
  21. 1 def invalid_line? trailer
  22. 20 parser.call(trailer.value).then do |person|
  23. 20 trailer.key.match?(setting.pattern) && !validator.call(person.name, minimum:)
  24. end
  25. end
  26. 1 private
  27. 1 def minimum = settings.commits_trailer_signer_name_minimum
  28. end
  29. end
  30. end
  31. end

lib/git/lint/analyzers/commit_trailer_tracker_key.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer tracker key usage.
  6. 1 class CommitTrailerTrackerKey < Abstract
  7. 1 include Dependencies[setting: "trailers.tracker"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list = Kit::FilterList.new setting.name
  18. 1 def invalid_line? trailer
  19. 20 trailer.key.then do |key|
  20. 20 key.match?(setting.pattern) && !key.match?(/\A#{Regexp.union filter_list}\Z/)
  21. end
  22. end
  23. end
  24. end
  25. end
  26. end

lib/git/lint/analyzers/commit_trailer_tracker_value.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Analyzers
  5. # Analyzes commit trailer tracker value.
  6. 1 class CommitTrailerTrackerValue < Abstract
  7. 1 include Dependencies[setting: "trailers.tracker"]
  8. 1 def valid? = affected_commit_trailers.empty?
  9. 1 def issue
  10. 2 then: 1 else: 1 return {} if valid?
  11. {
  12. 1 hint: "Use format: #{filter_list.to_usage}.",
  13. lines: affected_commit_trailers
  14. }
  15. end
  16. 1 protected
  17. 1 def load_filter_list
  18. 27 Kit::FilterList.new settings.commits_trailer_tracker_value_includes
  19. end
  20. 1 def invalid_line? trailer
  21. 19 trailer.key.match?(setting.pattern) && !trailer.value.match?(value_pattern)
  22. end
  23. 1 def value_pattern = /\A#{Regexp.union filter_list}\Z/
  24. end
  25. end
  26. end
  27. end

lib/git/lint/cli/actions/analyze/branch.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sod"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module CLI
  6. 1 module Actions
  7. 1 module Analyze
  8. # Handles analyze action for branch.
  9. 1 class Branch < Sod::Action
  10. 1 include Dependencies[:logger, :kernel, :io]
  11. 1 description "Analyze current branch."
  12. 1 on %w[-b --branch]
  13. 1 def initialize(analyzer: Analyzer.new, **)
  14. 14 super(**)
  15. 14 @analyzer = analyzer
  16. end
  17. 1 def call(*)
  18. 7 parse
  19. rescue Errors::Base => error
  20. 4 logger.error { error.message }
  21. 2 kernel.abort
  22. end
  23. 1 private
  24. 1 attr_reader :analyzer
  25. 1 def parse
  26. 7 analyzer.call do |collector, reporter|
  27. 5 io.puts reporter
  28. 5 then: 3 else: 2 kernel.abort if collector.errors?
  29. end
  30. end
  31. end
  32. end
  33. end
  34. end
  35. end
  36. end

lib/git/lint/cli/actions/analyze/commit.rb

100.0% lines covered

100.0% branches covered

23 relevant lines. 23 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sod"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module CLI
  6. 1 module Actions
  7. 1 module Analyze
  8. # Handles analyze action for single commit SHA
  9. 1 class Commit < Sod::Action
  10. 1 include Dependencies[:git, :logger, :kernel, :io]
  11. 1 description "Analyze specific commits."
  12. 1 on %w[-c --commit], argument: "a,b,c"
  13. 1 def initialize(analyzer: Analyzer.new, **)
  14. 15 super(**)
  15. 15 @analyzer = analyzer
  16. end
  17. 1 def call *arguments
  18. 8 process arguments.unshift "-1"
  19. rescue Errors::Base => error
  20. 4 logger.error { error.message }
  21. 2 kernel.abort
  22. end
  23. 1 private
  24. 1 attr_reader :analyzer
  25. 1 def process arguments
  26. 8 analyzer.call commits: git.commits(*arguments) do |collector, reporter|
  27. 6 io.puts reporter
  28. 6 then: 3 else: 3 kernel.abort if collector.errors?
  29. end
  30. end
  31. end
  32. end
  33. end
  34. end
  35. end
  36. end

lib/git/lint/cli/actions/hook.rb

100.0% lines covered

100.0% branches covered

20 relevant lines. 20 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sod"
  3. 1 require "sod/types/pathname"
  4. 1 module Git
  5. 1 module Lint
  6. 1 module CLI
  7. 1 module Actions
  8. # Handles unsaved Git commit action.
  9. 1 class Hook < Sod::Action
  10. 1 include Dependencies[:git, :logger, :kernel, :io]
  11. 1 description "Hook for analyzing unsaved commits."
  12. 1 on "--hook", argument: "PATH", type: Pathname
  13. 1 def initialize(analyzer: Analyzer.new, **)
  14. 12 super(**)
  15. 12 @analyzer = analyzer
  16. end
  17. 1 def call path
  18. 5 analyzer.call commits: commits(path) do |collector, reporter|
  19. 5 io.puts reporter
  20. 5 then: 3 else: 2 kernel.abort if collector.errors?
  21. end
  22. end
  23. 1 private
  24. 1 attr_reader :analyzer
  25. 6 def commits(path) = git.uncommitted(path).fmap { |commit| [commit] }
  26. end
  27. end
  28. end
  29. end
  30. end

lib/git/lint/cli/shell.rb

100.0% lines covered

100.0% branches covered

25 relevant lines. 25 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "sod"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module CLI
  6. # The main Command Line Interface (CLI) object.
  7. 1 class Shell
  8. 1 include Dependencies[:defaults_path, :xdg_config, :specification]
  9. 1 def initialize(context: Sod::Context, dsl: Sod, **)
  10. 10 super(**)
  11. 10 @context = context
  12. 10 @dsl = dsl
  13. end
  14. 1 def call(...) = cli.call(...)
  15. 1 private
  16. 1 attr_reader :context, :dsl
  17. 1 def cli
  18. 9 context = build_context
  19. 9 dsl.new "git-lint", banner: specification.banner do
  20. 9 on(Sod::Prefabs::Commands::Config, context:)
  21. 9 on "analyze", "Analyze branch or commit(s)." do
  22. 9 on Actions::Analyze::Branch
  23. 9 on Actions::Analyze::Commit
  24. end
  25. 9 on Actions::Hook
  26. 9 on(Sod::Prefabs::Actions::Version, context:)
  27. 9 on Sod::Prefabs::Actions::Help, self
  28. end
  29. end
  30. 1 def build_context
  31. 9 context[defaults_path:, xdg_config:, version_label: specification.labeled_version]
  32. end
  33. end
  34. end
  35. end
  36. end

lib/git/lint/collector.rb

100.0% lines covered

100.0% branches covered

21 relevant lines. 21 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. # Collects and categorizes, by severity, all issues (if any).
  5. 1 class Collector
  6. 1 def initialize
  7. 114 @collection = Hash.new { |default, missing_id| default[missing_id] = [] }
  8. end
  9. 1 def add analyzer
  10. 770 collection[analyzer.commit] << analyzer
  11. 770 analyzer
  12. end
  13. 1 def retrieve(id) = collection[id]
  14. 1 def clear = collection.clear && self
  15. 1 def empty? = collection.empty?
  16. 1 def warnings? = collection.values.flatten.any?(&:warning?)
  17. 1 def errors? = collection.values.flatten.any?(&:error?)
  18. 1 def issues? = collection.values.flatten.any?(&:invalid?)
  19. 1 def total_warnings = collection.values.flatten.count(&:warning?)
  20. 1 def total_errors = collection.values.flatten.count(&:error?)
  21. 1 def total_issues = collection.values.flatten.count(&:invalid?)
  22. 1 def total_commits = collection.keys.size
  23. 1 def to_h = collection
  24. 1 private
  25. 1 attr_reader :collection
  26. end
  27. end
  28. end

lib/git/lint/commits/hosts/circle_ci.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Commits
  5. 1 module Hosts
  6. # Provides Circle CI feature branch information.
  7. 1 class CircleCI
  8. 1 include Dependencies[:git]
  9. 1 def call = git.commits "origin/#{branch_default}..#{branch_name}"
  10. 1 private
  11. 1 def branch_default = git.branch_default.value_or nil
  12. 1 def branch_name = "origin/#{git.branch_name.value_or nil}"
  13. end
  14. end
  15. end
  16. end
  17. end

lib/git/lint/commits/hosts/git_hub_action.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Commits
  5. 1 module Hosts
  6. # Provides GitHub Action feature branch information.
  7. 1 class GitHubAction
  8. 1 include Dependencies[:git]
  9. 1 def call = git.commits "origin/#{branch_default}..#{branch_name}"
  10. 1 private
  11. 1 def branch_default = git.branch_default.value_or nil
  12. 1 def branch_name = "origin/#{git.branch_name.value_or nil}"
  13. end
  14. end
  15. end
  16. end
  17. end

lib/git/lint/commits/hosts/local.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Commits
  5. 1 module Hosts
  6. # Provides local feature branch information.
  7. 1 class Local
  8. 1 include Dependencies[:git]
  9. 1 def call = git.commits "#{branch_default}..#{branch_name}"
  10. 1 private
  11. 1 def branch_default = git.branch_default.value_or nil
  12. 1 def branch_name = git.branch_name.value_or nil
  13. end
  14. end
  15. end
  16. end
  17. end

lib/git/lint/commits/hosts/netlify_ci.rb

100.0% lines covered

100.0% branches covered

13 relevant lines. 13 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Commits
  5. 1 module Hosts
  6. # Provides Netlify CI feature branch information.
  7. 1 class NetlifyCI
  8. 1 include Dependencies[:git, :environment]
  9. 1 def call
  10. 3 git.call("remote", "add", "-f", "origin", environment["REPOSITORY_URL"])
  11. 3 .bind { git.call "fetch", "origin", "#{branch_name}:#{branch_name}" }
  12. 3 .bind { git.commits "origin/#{branch_default}..origin/#{branch_name}" }
  13. end
  14. 1 private
  15. 1 def branch_default = git.branch_default.value_or nil
  16. 1 def branch_name = environment["HEAD"]
  17. end
  18. end
  19. end
  20. end
  21. end

lib/git/lint/commits/loader.rb

100.0% lines covered

100.0% branches covered

18 relevant lines. 18 lines covered and 0 lines missed.
8 total branches, 8 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "refinements/string"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Commits
  6. # Automatically detects and loads host.
  7. 1 class Loader
  8. 1 include Dependencies[
  9. :git,
  10. :environment,
  11. circle_ci: "hosts.circle_ci",
  12. git_hub_action: "hosts.git_hub_action",
  13. netlify_ci: "hosts.git_hub_action",
  14. local: "hosts.local"
  15. ]
  16. 1 using Refinements::String
  17. 1 def call
  18. 15 message = "Invalid repository. Are you within a Git repository?"
  19. 15 else: 14 then: 1 fail Errors::Base, message unless git.exist?
  20. 14 host.call
  21. end
  22. 1 private
  23. 1 def host
  24. 14 then: 1 else: 13 if key? "CIRCLECI" then circle_ci
  25. 13 then: 1 else: 12 elsif key? "GITHUB_ACTIONS" then git_hub_action
  26. 12 then: 1 else: 11 elsif key? "NETLIFY" then netlify_ci
  27. 11 else local
  28. end
  29. end
  30. 1 def key?(key) = environment.fetch(key, "false").truthy?
  31. end
  32. end
  33. end
  34. end

lib/git/lint/configuration/contract.rb

100.0% lines covered

100.0% branches covered

103 relevant lines. 103 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "dry/schema"
  3. 1 require "etcher"
  4. 1 Dry::Schema.load_extensions :monads
  5. 1 module Git
  6. 1 module Lint
  7. 1 module Configuration
  8. 1 Contract = Dry::Schema.Params do
  9. 1 required(:commits_author_capitalization_enabled).filled :bool
  10. 1 required(:commits_author_capitalization_severity).filled :string
  11. 1 required(:commits_author_email_enabled).filled :bool
  12. 1 required(:commits_author_email_severity).filled :string
  13. 1 required(:commits_author_name_enabled).filled :bool
  14. 1 required(:commits_author_name_severity).filled :string
  15. 1 required(:commits_author_name_minimum).filled :integer
  16. 1 required(:commits_body_bullet_capitalization_enabled).filled :bool
  17. 1 required(:commits_body_bullet_capitalization_severity).filled :string
  18. 1 required(:commits_body_bullet_capitalization_includes).array :string
  19. 1 required(:commits_body_bullet_delimiter_enabled).filled :bool
  20. 1 required(:commits_body_bullet_delimiter_severity).filled :string
  21. 1 required(:commits_body_bullet_delimiter_includes).array :string
  22. 1 required(:commits_body_bullet_only_enabled).filled :bool
  23. 1 required(:commits_body_bullet_only_severity).filled :string
  24. 1 required(:commits_body_bullet_only_includes).array :string
  25. 1 required(:commits_body_leading_line_enabled).filled :bool
  26. 1 required(:commits_body_leading_line_severity).filled :string
  27. 1 required(:commits_body_line_length_enabled).filled :bool
  28. 1 required(:commits_body_line_length_severity).filled :string
  29. 1 required(:commits_body_line_length_maximum).filled :integer
  30. 1 required(:commits_body_paragraph_capitalization_enabled).filled :bool
  31. 1 required(:commits_body_paragraph_capitalization_severity).filled :string
  32. 1 required(:commits_body_phrase_enabled).filled :bool
  33. 1 required(:commits_body_phrase_severity).filled :string
  34. 1 required(:commits_body_phrase_excludes).array :string
  35. 1 required(:commits_body_presence_enabled).filled :bool
  36. 1 required(:commits_body_presence_severity).filled :string
  37. 1 required(:commits_body_presence_minimum).filled :integer
  38. 1 required(:commits_body_word_repeat_enabled).filled :bool
  39. 1 required(:commits_body_word_repeat_severity).filled :string
  40. 1 required(:commits_body_tracker_shorthand_enabled).filled :bool
  41. 1 required(:commits_body_tracker_shorthand_severity).filled :string
  42. 1 required(:commits_body_tracker_shorthand_excludes).array :string
  43. 1 required(:commits_signature_enabled).filled :bool
  44. 1 required(:commits_signature_severity).filled :string
  45. 1 required(:commits_signature_includes).array :string
  46. 1 required(:commits_subject_length_enabled).filled :bool
  47. 1 required(:commits_subject_length_severity).filled :string
  48. 1 required(:commits_subject_length_maximum).filled :integer
  49. 1 required(:commits_subject_prefix_enabled).filled :bool
  50. 1 required(:commits_subject_prefix_severity).filled :string
  51. 1 required(:commits_subject_prefix_delimiter).filled :string
  52. 1 required(:commits_subject_prefix_includes).array :string
  53. 1 required(:commits_subject_suffix_enabled).filled :bool
  54. 1 required(:commits_subject_suffix_severity).filled :string
  55. 1 required(:commits_subject_suffix_excludes).array :string
  56. 1 required(:commits_subject_word_repeat_enabled).filled :bool
  57. 1 required(:commits_subject_word_repeat_severity).filled :string
  58. 1 required(:commits_trailer_collaborator_capitalization_enabled).filled :bool
  59. 1 required(:commits_trailer_collaborator_capitalization_severity).filled :string
  60. 1 required(:commits_trailer_collaborator_email_enabled).filled :bool
  61. 1 required(:commits_trailer_collaborator_email_severity).filled :string
  62. 1 required(:commits_trailer_collaborator_key_enabled).filled :bool
  63. 1 required(:commits_trailer_collaborator_key_severity).filled :string
  64. 1 required(:commits_trailer_collaborator_name_enabled).filled :bool
  65. 1 required(:commits_trailer_collaborator_name_severity).filled :string
  66. 1 required(:commits_trailer_collaborator_name_minimum).filled :integer
  67. 1 required(:commits_trailer_duplicate_enabled).filled :bool
  68. 1 required(:commits_trailer_duplicate_severity).filled :string
  69. 1 required(:commits_trailer_format_key_enabled).filled :bool
  70. 1 required(:commits_trailer_format_key_severity).filled :string
  71. 1 required(:commits_trailer_format_value_enabled).filled :bool
  72. 1 required(:commits_trailer_format_value_severity).filled :string
  73. 1 required(:commits_trailer_format_value_includes).array :string
  74. 1 required(:commits_trailer_issue_key_enabled).filled :bool
  75. 1 required(:commits_trailer_issue_key_severity).filled :string
  76. 1 required(:commits_trailer_issue_value_enabled).filled :bool
  77. 1 required(:commits_trailer_issue_value_severity).filled :string
  78. 1 required(:commits_trailer_issue_value_includes).array :string
  79. 1 required(:commits_trailer_milestone_key_enabled).filled :bool
  80. 1 required(:commits_trailer_milestone_key_severity).filled :string
  81. 1 required(:commits_trailer_milestone_value_enabled).filled :bool
  82. 1 required(:commits_trailer_milestone_value_severity).filled :string
  83. 1 required(:commits_trailer_milestone_value_includes).array :string
  84. 1 required(:commits_trailer_order_enabled).filled :bool
  85. 1 required(:commits_trailer_order_severity).filled :string
  86. 1 required(:commits_trailer_reviewer_key_enabled).filled :bool
  87. 1 required(:commits_trailer_reviewer_key_severity).filled :string
  88. 1 required(:commits_trailer_reviewer_value_enabled).filled :bool
  89. 1 required(:commits_trailer_reviewer_value_severity).filled :string
  90. 1 required(:commits_trailer_reviewer_value_includes).array :string
  91. 1 required(:commits_trailer_signer_capitalization_enabled).filled :bool
  92. 1 required(:commits_trailer_signer_capitalization_severity).filled :string
  93. 1 required(:commits_trailer_signer_email_enabled).filled :bool
  94. 1 required(:commits_trailer_signer_email_severity).filled :string
  95. 1 required(:commits_trailer_signer_key_enabled).filled :bool
  96. 1 required(:commits_trailer_signer_key_severity).filled :string
  97. 1 required(:commits_trailer_signer_name_enabled).filled :bool
  98. 1 required(:commits_trailer_signer_name_severity).filled :string
  99. 1 required(:commits_trailer_signer_name_minimum).filled :integer
  100. 1 required(:commits_trailer_tracker_key_enabled).filled :bool
  101. 1 required(:commits_trailer_tracker_key_severity).filled :string
  102. 1 required(:commits_trailer_tracker_value_enabled).filled :bool
  103. 1 required(:commits_trailer_tracker_value_severity).filled :string
  104. 1 required(:commits_trailer_tracker_value_includes).array :string
  105. end
  106. end
  107. end
  108. end

lib/git/lint/configuration/model.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Configuration
  5. # Defines configuration content as the primary source of truth for use throughout the gem.
  6. 1 Model = Struct.new :commits_author_capitalization_enabled,
  7. :commits_author_capitalization_severity,
  8. :commits_author_email_enabled,
  9. :commits_author_email_severity,
  10. :commits_author_name_enabled,
  11. :commits_author_name_severity,
  12. :commits_author_name_minimum,
  13. :commits_body_bullet_capitalization_enabled,
  14. :commits_body_bullet_capitalization_severity,
  15. :commits_body_bullet_capitalization_includes,
  16. :commits_body_bullet_delimiter_enabled,
  17. :commits_body_bullet_delimiter_severity,
  18. :commits_body_bullet_delimiter_includes,
  19. :commits_body_bullet_only_enabled,
  20. :commits_body_bullet_only_severity,
  21. :commits_body_bullet_only_includes,
  22. :commits_body_leading_line_enabled,
  23. :commits_body_leading_line_severity,
  24. :commits_body_line_length_enabled,
  25. :commits_body_line_length_severity,
  26. :commits_body_line_length_maximum,
  27. :commits_body_paragraph_capitalization_enabled,
  28. :commits_body_paragraph_capitalization_severity,
  29. :commits_body_phrase_enabled,
  30. :commits_body_phrase_severity,
  31. :commits_body_phrase_excludes,
  32. :commits_body_presence_enabled,
  33. :commits_body_presence_severity,
  34. :commits_body_presence_minimum,
  35. :commits_body_word_repeat_enabled,
  36. :commits_body_word_repeat_severity,
  37. :commits_body_tracker_shorthand_enabled,
  38. :commits_body_tracker_shorthand_severity,
  39. :commits_body_tracker_shorthand_excludes,
  40. :commits_signature_enabled,
  41. :commits_signature_severity,
  42. :commits_signature_includes,
  43. :commits_subject_length_enabled,
  44. :commits_subject_length_severity,
  45. :commits_subject_length_maximum,
  46. :commits_subject_prefix_enabled,
  47. :commits_subject_prefix_severity,
  48. :commits_subject_prefix_delimiter,
  49. :commits_subject_prefix_includes,
  50. :commits_subject_suffix_enabled,
  51. :commits_subject_suffix_severity,
  52. :commits_subject_suffix_excludes,
  53. :commits_subject_word_repeat_enabled,
  54. :commits_subject_word_repeat_severity,
  55. :commits_trailer_collaborator_capitalization_enabled,
  56. :commits_trailer_collaborator_capitalization_severity,
  57. :commits_trailer_collaborator_email_enabled,
  58. :commits_trailer_collaborator_email_severity,
  59. :commits_trailer_collaborator_key_enabled,
  60. :commits_trailer_collaborator_key_severity,
  61. :commits_trailer_collaborator_name_enabled,
  62. :commits_trailer_collaborator_name_severity,
  63. :commits_trailer_collaborator_name_minimum,
  64. :commits_trailer_duplicate_enabled,
  65. :commits_trailer_duplicate_severity,
  66. :commits_trailer_format_key_enabled,
  67. :commits_trailer_format_key_severity,
  68. :commits_trailer_format_value_enabled,
  69. :commits_trailer_format_value_severity,
  70. :commits_trailer_format_value_includes,
  71. :commits_trailer_issue_key_enabled,
  72. :commits_trailer_issue_key_severity,
  73. :commits_trailer_issue_value_enabled,
  74. :commits_trailer_issue_value_severity,
  75. :commits_trailer_issue_value_includes,
  76. :commits_trailer_milestone_key_enabled,
  77. :commits_trailer_milestone_key_severity,
  78. :commits_trailer_milestone_value_enabled,
  79. :commits_trailer_milestone_value_severity,
  80. :commits_trailer_milestone_value_includes,
  81. :commits_trailer_order_enabled,
  82. :commits_trailer_order_severity,
  83. :commits_trailer_reviewer_key_enabled,
  84. :commits_trailer_reviewer_key_severity,
  85. :commits_trailer_reviewer_value_enabled,
  86. :commits_trailer_reviewer_value_severity,
  87. :commits_trailer_reviewer_value_includes,
  88. :commits_trailer_signer_capitalization_enabled,
  89. :commits_trailer_signer_capitalization_severity,
  90. :commits_trailer_signer_email_enabled,
  91. :commits_trailer_signer_email_severity,
  92. :commits_trailer_signer_key_enabled,
  93. :commits_trailer_signer_key_severity,
  94. :commits_trailer_signer_name_enabled,
  95. :commits_trailer_signer_name_severity,
  96. :commits_trailer_signer_name_minimum,
  97. :commits_trailer_tracker_key_enabled,
  98. :commits_trailer_tracker_key_severity,
  99. :commits_trailer_tracker_value_enabled,
  100. :commits_trailer_tracker_value_severity,
  101. :commits_trailer_tracker_value_includes
  102. end
  103. end
  104. end

lib/git/lint/configuration/trailer.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Configuration
  5. # Defines trailer configuration as a subset of the primary settings.
  6. 1 Trailer = Data.define :name, :pattern
  7. end
  8. end
  9. end

lib/git/lint/dependencies.rb

100.0% lines covered

100.0% branches covered

4 relevant lines. 4 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "infusible"
  3. 1 module Git
  4. 1 module Lint
  5. 1 Dependencies = Infusible[Container]
  6. end
  7. end

lib/git/lint/errors/base.rb

100.0% lines covered

100.0% branches covered

6 relevant lines. 6 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Errors
  5. # The root class of gem related errors.
  6. 1 class Base < StandardError
  7. 1 def initialize message = "Invalid Git Lint action."
  8. 10 super
  9. end
  10. end
  11. end
  12. end
  13. end

lib/git/lint/errors/severity.rb

100.0% lines covered

100.0% branches covered

9 relevant lines. 9 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "refinements/array"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Errors
  6. # Categorizes severity errors.
  7. 1 class Severity < Base
  8. 1 using Refinements::Array
  9. 1 def initialize level
  10. 3 usage = Analyzers::Abstract::LEVELS.to_usage "or"
  11. 3 super %(Invalid severity level: #{level}. Use: #{usage}.)
  12. end
  13. end
  14. end
  15. end
  16. end

lib/git/lint/errors/sha.rb

100.0% lines covered

100.0% branches covered

6 relevant lines. 6 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Errors
  5. # Categorizes commit SHA errors.
  6. 1 class SHA < Base
  7. 1 def initialize sha
  8. 1 super %(Invalid commit SHA: "#{sha}". Unable to obtain commit details.)
  9. end
  10. end
  11. end
  12. end
  13. end

lib/git/lint/kit/filter_list.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 require "refinements/array"
  4. 1 module Git
  5. 1 module Lint
  6. 1 module Kit
  7. # Represents an regular expression list which may be used as an analyzer setting.
  8. 1 class FilterList
  9. 1 using Refinements::Array
  10. 1 def initialize list = Core::EMPTY_ARRAY
  11. 3382 @list = Array(list).map { |item| Regexp.new item }
  12. end
  13. 1 def empty? = list.empty?
  14. 1 def to_a = list
  15. 1 alias to_ary to_a
  16. 1 def to_usage(...) = list.to_usage(...)
  17. 1 private
  18. 1 attr_reader :list
  19. end
  20. end
  21. end
  22. end

lib/git/lint/rake/register.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "git/lint"
  3. 1 require "rake"
  4. 1 module Git
  5. 1 module Lint
  6. 1 module Rake
  7. # Registers Rake tasks for use.
  8. 1 class Register
  9. 1 include ::Rake::DSL
  10. 1 def self.call = new.call
  11. 1 def initialize shell: CLI::Shell.new
  12. 2 @shell = shell
  13. end
  14. 1 def call
  15. 2 desc "Run Git Lint"
  16. 3 task(:git_lint) { shell.call %w[analyze --branch] }
  17. end
  18. 1 private
  19. 1 attr_reader :shell
  20. end
  21. end
  22. end
  23. end

lib/git/lint/reporters/branch.rb

100.0% lines covered

100.0% branches covered

39 relevant lines. 39 lines covered and 0 lines missed.
10 total branches, 10 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Reporters
  5. # Reports issues related to a single branch.
  6. 1 class Branch
  7. 1 include Dependencies[:color]
  8. 1 using Refinements::String
  9. 1 def initialize(collector: Collector.new, **)
  10. 26 super(**)
  11. 26 @collector = collector
  12. end
  13. 1 def to_s
  14. 20 "Running Git Lint...#{branch_report}\n" \
  15. "#{commit_total}. #{issue_totals}.\n"
  16. end
  17. 1 alias to_str to_s
  18. 1 private
  19. 1 attr_reader :collector
  20. 1 def branch_report
  21. 20 else: 11 then: 9 return "" unless collector.issues?
  22. 11 "\n\n#{commit_report}".chomp
  23. end
  24. 1 def commit_report
  25. 11 collector.to_h.reduce "" do |details, (commit, analyzers)|
  26. 11 details + Commit.new(commit:, analyzers:)
  27. end
  28. end
  29. 1 def commit_total
  30. 20 total = collector.total_commits
  31. 20 %(#{total} #{"commit".pluralize "s", total} inspected)
  32. end
  33. 1 def issue_totals
  34. 20 then: 11 if collector.issues?
  35. 11 "#{issue_total} detected (#{warning_total}, #{error_total})"
  36. else: 9 else
  37. 9 color["0 issues", :green] + " detected"
  38. end
  39. end
  40. 1 def issue_total
  41. 11 then: 10 else: 1 style = collector.errors? ? :red : :yellow
  42. 11 total = collector.total_issues
  43. 11 color["#{total} issue".pluralize("s", total), style]
  44. end
  45. 1 def warning_total
  46. 11 then: 1 else: 10 style = collector.warnings? ? :yellow : :green
  47. 11 total = collector.total_warnings
  48. 11 color["#{total} warning".pluralize("s", total), style]
  49. end
  50. 1 def error_total
  51. 11 then: 10 else: 1 style = collector.errors? ? :red : :green
  52. 11 total = collector.total_errors
  53. 11 color["#{total} error".pluralize("s", total), style]
  54. end
  55. end
  56. end
  57. end
  58. end

lib/git/lint/reporters/commit.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Reporters
  5. # Reports issues related to a single commit.
  6. 1 class Commit
  7. 1 def initialize commit:, analyzers: []
  8. 15 @commit = commit
  9. 15 @analyzers = analyzers.select(&:invalid?)
  10. end
  11. 1 def to_s
  12. 15 then: 1 else: 14 return "" if analyzers.empty?
  13. 14 "#{commit.sha} (#{commit.author_name}, #{commit.authored_relative_at}): " \
  14. "#{commit.subject}\n#{report}\n"
  15. end
  16. 1 alias to_str to_s
  17. 1 private
  18. 1 attr_reader :commit, :analyzers
  19. 20 def report = analyzers.reduce("") { |report, analyzer| report + Style.new(analyzer) }
  20. end
  21. end
  22. end
  23. end

lib/git/lint/reporters/line.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Reporters
  6. # Reports issues related to an invalid line within the commit body.
  7. 1 class Line
  8. 1 DEFAULT_INDENT = " "
  9. 1 def initialize data = Core::EMPTY_HASH
  10. 5 @data = data
  11. end
  12. 1 def to_s
  13. 5 then: 1 else: 4 content.include?("\n") ? Lines::Paragraph.new(data).to_s : Lines::Sentence.new(data).to_s
  14. end
  15. 1 alias to_str to_s
  16. 1 private
  17. 1 attr_reader :data
  18. 1 def content = data.fetch(__method__)
  19. end
  20. end
  21. end
  22. end

lib/git/lint/reporters/lines/paragraph.rb

100.0% lines covered

100.0% branches covered

21 relevant lines. 21 lines covered and 0 lines missed.
2 total branches, 2 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Reporters
  6. 1 module Lines
  7. # Reports paragraph details.
  8. 1 class Paragraph
  9. 1 def initialize data = Core::EMPTY_HASH
  10. 3 @data = data
  11. end
  12. 1 def to_s
  13. 3 %(#{label}"#{paragraph}"\n)
  14. end
  15. 1 alias to_str to_s
  16. 1 private
  17. 1 attr_reader :data
  18. 1 def label = "#{Line::DEFAULT_INDENT}Line #{number}: "
  19. 1 def paragraph = formatted_lines.join("\n")
  20. 1 def formatted_lines
  21. 3 content.split("\n").map.with_index do |line, index|
  22. 9 then: 3 else: 6 index.zero? ? line : "#{indent}#{line}"
  23. end
  24. end
  25. 7 def indent = " " * (label.length + 1)
  26. 1 def number = data.fetch(:number)
  27. 1 def content = data.fetch(:content)
  28. end
  29. end
  30. end
  31. end
  32. end

lib/git/lint/reporters/lines/sentence.rb

100.0% lines covered

100.0% branches covered

14 relevant lines. 14 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Reporters
  6. 1 module Lines
  7. # Reports sentence details.
  8. 1 class Sentence
  9. 1 def initialize data = Core::EMPTY_HASH
  10. 6 @data = data
  11. end
  12. 1 def to_s = %(#{Line::DEFAULT_INDENT}Line #{number}: "#{content}"\n)
  13. 1 alias to_str to_s
  14. 1 private
  15. 1 attr_reader :data
  16. 1 def number = data.fetch(:number)
  17. 1 def content = data.fetch(:content)
  18. end
  19. end
  20. end
  21. end
  22. end

lib/git/lint/reporters/style.rb

100.0% lines covered

100.0% branches covered

27 relevant lines. 27 lines covered and 0 lines missed.
6 total branches, 6 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Reporters
  5. # Reports issues related to a single style.
  6. 1 class Style
  7. 1 include Dependencies[:color]
  8. 1 def initialize(analyzer, **)
  9. 24 super(**)
  10. 24 @analyzer = analyzer
  11. 24 @issue = analyzer.issue
  12. end
  13. 1 def to_s = color[message, style]
  14. 1 alias to_str to_s
  15. 1 private
  16. 1 attr_reader :analyzer, :issue
  17. 1 def message
  18. 24 " #{analyzer.class.label}#{severity_suffix}. " \
  19. "#{issue.fetch :hint}\n" \
  20. "#{affected_lines}"
  21. end
  22. 1 def severity_suffix
  23. 24 when: 6 case analyzer.severity
  24. 6 when: 17 when "warn" then " Warning"
  25. 17 else: 1 when "error" then " Error"
  26. 1 else ""
  27. end
  28. end
  29. 1 def style
  30. 24 when: 6 case analyzer.severity
  31. 6 when: 17 when "warn" then :yellow
  32. 17 else: 1 when "error" then :red
  33. 1 else :white
  34. end
  35. end
  36. 1 def affected_lines
  37. 26 issue.fetch(:lines, []).reduce("") { |lines, line| lines + Line.new(line) }
  38. end
  39. end
  40. end
  41. end
  42. end

lib/git/lint/validators/capitalization.rb

100.0% lines covered

100.0% branches covered

11 relevant lines. 11 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 module Git
  3. 1 module Lint
  4. 1 module Validators
  5. # Validates the capitalizationn of text.
  6. 1 class Capitalization
  7. 1 PATTERN = /\A[[:upper:]].*\Z/
  8. 1 def initialize delimiter: Name::DELIMITER, pattern: PATTERN
  9. 50 @delimiter = delimiter
  10. 50 @pattern = pattern
  11. end
  12. 261 def call(content) = String(content).split(delimiter).all? { |name| name.match? pattern }
  13. 1 private
  14. 1 attr_reader :delimiter, :pattern
  15. end
  16. end
  17. end
  18. end

lib/git/lint/validators/email.rb

100.0% lines covered

100.0% branches covered

10 relevant lines. 10 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "uri"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Validators
  6. # Validates the format of email addresses.
  7. 1 class Email
  8. 1 def initialize pattern: URI::MailTo::EMAIL_REGEXP
  9. 40 @pattern = pattern
  10. end
  11. 1 def call(content) = String(content).match? pattern
  12. 1 private
  13. 1 attr_reader :pattern
  14. end
  15. end
  16. end
  17. end

lib/git/lint/validators/name.rb

100.0% lines covered

100.0% branches covered

15 relevant lines. 15 lines covered and 0 lines missed.
0 total branches, 0 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "refinements/string"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Validators
  6. # Validates the format of names.
  7. 1 class Name
  8. 1 using Refinements::String
  9. 1 DELIMITER = /\s{1}/
  10. 1 MINIMUM = 2
  11. 1 def initialize delimiter: DELIMITER
  12. 50 @delimiter = delimiter
  13. end
  14. 1 def call content, minimum: MINIMUM
  15. 141 parts = String(content).split delimiter
  16. 397 parts.size >= minimum && parts.all? { |name| !name.blank? }
  17. end
  18. 1 private
  19. 1 attr_reader :delimiter
  20. end
  21. end
  22. end
  23. end

lib/git/lint/validators/repeated_word.rb

100.0% lines covered

100.0% branches covered

17 relevant lines. 17 lines covered and 0 lines missed.
4 total branches, 4 branches covered and 0 branches missed.
    
  1. # frozen_string_literal: true
  2. 1 require "core"
  3. 1 module Git
  4. 1 module Lint
  5. 1 module Validators
  6. # Validates content has no repeated words.
  7. 1 class RepeatedWord
  8. 1 PATTERNS = {
  9. word: /
  10. \w+(?=\s) # Match word with trailing space.
  11. | # Or.
  12. (?<=\s)\w+(?=\s) # Match word between two spaces.
  13. | # Or.
  14. (?<=\s)\w+ # Match word with leading space.
  15. /x,
  16. exclude: /
  17. ( # Conditional start.
  18. `.+` # Code blocks.
  19. | # Or.
  20. \d+\. # Digits followed by periods.
  21. ) # Conditional end.
  22. /x
  23. }.freeze
  24. 1 def initialize patterns: PATTERNS
  25. 46 @patterns = patterns
  26. end
  27. 131 then: 128 else: 2 def call(content) = content ? scan(content) : Core::EMPTY_ARRAY
  28. 1 private
  29. 1 attr_reader :patterns
  30. 1 def scan content
  31. 128 parse(content).each_cons(2).with_object [] do |(current, future), repeats|
  32. 289 then: 17 else: 272 repeats.append future if current.casecmp(future).zero?
  33. end
  34. end
  35. 1 def parse(content) = content.gsub(exclude_pattern, "").scan word_pattern
  36. 1 def word_pattern = patterns.fetch :word
  37. 1 def exclude_pattern = patterns.fetch :exclude
  38. end
  39. end
  40. end
  41. end