pronto-rails_migrations_annotated 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2b901020822988af989df4a5c7380a1d0888436b99a1b2ccd835e9b08a88875
4
- data.tar.gz: 51392445670ec4052e0ad6790a6499cf71d62b4d5354a48e27df8ed036ab5f34
3
+ metadata.gz: 88430fc4dc1457f81ee58ddbb791363370cb6e85fcd6ffe17b504717a637d5de
4
+ data.tar.gz: e7f1c20a1f124f27506cb1494d756d1ab314cd9217ce075e68e7a42842380030
5
5
  SHA512:
6
- metadata.gz: 9b50a79d424571f695fe1faeb208ab10d0b474ab161d26c9128cfe355cbaf4de58000cb373daa64b234ac0bd0e2a7b8744ee2dec000f80785c673bdd067db353
7
- data.tar.gz: 4b2b7867aced9e74a06c855337437b802407479d67bb805da19ec502079bdebe748e8f185f383c99338c74d9e9d8194461100c55374b0f1c93bd35a840610143
6
+ metadata.gz: 0f56e324b56c92ef3e2957bd39a6dba969e6fa4748bf8acadc1e077288cf0abb882b6e30b9c7c1f9a6d4d0a73b2be713a344e66c8c2d5aee667e7e6dd379f21d
7
+ data.tar.gz: a9f3ebffd0dd50bc1549d669069cc27a0f3cd67dc0064d428c03f1adffcd0d58df885485e4b9458dbb0c95a8a244e4dc479fbffbb7b2ea169742a73e7b7db5ad
data/.rubocop.yml CHANGED
@@ -1,5 +1,13 @@
1
+ require:
2
+ - rubocop-rake
3
+ - rubocop-rspec
4
+ - rubocop-performance
5
+
1
6
  AllCops:
2
- TargetRubyVersion: 2.6
7
+ TargetRubyVersion: 2.5
8
+ NewCops: enable
9
+ Exclude:
10
+ - spec/fixtures/somerepo/**/*.rb
3
11
 
4
12
  Style/StringLiterals:
5
13
  Enabled: true
@@ -11,3 +19,13 @@ Style/StringLiteralsInInterpolation:
11
19
 
12
20
  Layout/LineLength:
13
21
  Max: 120
22
+
23
+ Metrics/BlockLength:
24
+ Exclude:
25
+ - spec/**/*.rb
26
+ Metrics/ClassLength:
27
+ Max: 180
28
+ Metrics/AbcSize:
29
+ Max: 25
30
+ Metrics/CyclomaticComplexity:
31
+ Max: 8
data/Gemfile CHANGED
@@ -10,3 +10,6 @@ gem "rake", "~> 13.0"
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
12
  gem "rubocop", "~> 1.21"
13
+ gem "rubocop-performance"
14
+ gem "rubocop-rake"
15
+ gem "rubocop-rspec"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pronto-rails_migrations_annotated (0.0.2)
4
+ pronto-rails_migrations_annotated (0.0.3)
5
5
  pronto (~> 0.11)
6
6
 
7
7
  GEM
@@ -84,6 +84,13 @@ GEM
84
84
  unicode-display_width (>= 1.4.0, < 3.0)
85
85
  rubocop-ast (1.13.0)
86
86
  parser (>= 3.0.1.1)
87
+ rubocop-performance (1.12.0)
88
+ rubocop (>= 1.7.0, < 2.0)
89
+ rubocop-ast (>= 0.4.0)
90
+ rubocop-rake (0.6.0)
91
+ rubocop (~> 1.0)
92
+ rubocop-rspec (2.6.0)
93
+ rubocop (~> 1.19)
87
94
  ruby-progressbar (1.11.0)
88
95
  ruby2_keywords (0.0.5)
89
96
  rugged (1.0.1)
@@ -103,6 +110,9 @@ DEPENDENCIES
103
110
  rake (~> 13.0)
104
111
  rspec (~> 3.0)
105
112
  rubocop (~> 1.21)
113
+ rubocop-performance
114
+ rubocop-rake
115
+ rubocop-rspec
106
116
 
107
117
  BUNDLED WITH
108
118
  2.2.31
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Pronto::RailsMigrationsAnnotated
2
+ [![Gem Version](https://badge.fury.io/rb/pronto-rails_migrations_annotated.svg)](https://badge.fury.io/rb/pronto-rails_migrations_annotated)
2
3
 
3
4
  Pronto runner to enforce migrations to be in separate PRs, but allow schema annotations.
4
5
  Also check for obvious things like adding migration number that is not present in PR to structure.sql.
@@ -8,17 +9,13 @@ Also check for obvious things like adding migration number that is not present i
8
9
  Add this line to your application's Gemfile:
9
10
 
10
11
  ```ruby
11
- gem 'pronto-rails_migrations_annotated'
12
+ gem 'pronto-rails_migrations_annotated', require: false
12
13
  ```
13
14
 
14
15
  And then execute:
15
16
 
16
17
  $ bundle install
17
18
 
18
- Or install it yourself as:
19
-
20
- $ gem install pronto-rails_migrations_annotated
21
-
22
19
  ## Usage
23
20
 
24
21
  When in gemfile - pronto should pick up this runner automatically.
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Pronto
4
4
  module RailsMigrationsAnnotatedVersion
5
- VERSION = "0.0.2"
5
+ VERSION = "0.0.3"
6
6
  end
7
7
  end
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pronto'
3
+ require "pronto"
4
4
  require_relative "rails_migrations_annotated/version"
5
5
 
6
6
  module Pronto
7
+ # Runner that detects migration smells
7
8
  class RailsMigrationsAnnotated < Runner
8
9
  VERSION = Pronto::RailsMigrationsAnnotatedVersion::VERSION
9
10
 
@@ -11,13 +12,15 @@ module Pronto
11
12
  @messages = []
12
13
 
13
14
  check_migrations_mixed_with_code
14
- check_migration_version_numbers
15
+ check_migration_schema_version
16
+ check_structure_migration_version_numbers
15
17
  check_large_schema_diff
18
+ check_large_structure_diff
16
19
 
17
20
  @messages
18
21
  end
19
22
 
20
- # todo: override def self.title ?
23
+ # TODO: override def self.title ?
21
24
 
22
25
  private
23
26
 
@@ -25,19 +28,11 @@ module Pronto
25
28
  patches = [patches] unless patches.is_a?(Array)
26
29
  patches.each do |patch|
27
30
  target_line = case line
28
- when :first then patch.added_lines.first || patch.lines.first
29
- when :last then patch.added_lines.last || patch.lines.last
30
- else line
31
- end
32
-
33
- @messages << Message.new(
34
- patch.delta.new_file[:path],
35
- target_line,
36
- level,
37
- message,
38
- nil,
39
- self.class
40
- )
31
+ when :first then patch.added_lines.first || patch.lines.first
32
+ when :last then patch.added_lines.last || patch.lines.last
33
+ else line
34
+ end
35
+ @messages << Message.new(patch.delta.new_file[:path], target_line, level, message, nil, self.class)
41
36
  end
42
37
  end
43
38
 
@@ -48,14 +43,18 @@ module Pronto
48
43
 
49
44
  def check_large_schema_diff
50
45
  return unless diff_threshold
46
+ return unless schema_patches.sum(&:additions) >= diff_threshold ||
47
+ schema_patches.sum(&:deletions) >= diff_threshold
51
48
 
52
- if schema_patches.sum(&:additions) >= diff_threshold || schema_patches.sum(&:deletions) >= diff_threshold
53
- add_message_at_patch(schema_patches.first, "Large schema diff, pay attention")
54
- end
49
+ add_message_at_patch(schema_patches.first, "Large schema diff, pay attention")
50
+ end
55
51
 
56
- if structure_patches.sum(&:additions) >= diff_threshold || structure_patches.sum(&:deletions) >= diff_threshold
57
- add_message_at_patch(structure_patches.first, "Large structure diff, pay attention")
58
- end
52
+ def check_large_structure_diff
53
+ return unless diff_threshold
54
+ return unless structure_patches.sum(&:additions) >= diff_threshold ||
55
+ structure_patches.sum(&:deletions) >= diff_threshold
56
+
57
+ add_message_at_patch(structure_patches.first, "Large structure diff, pay attention")
59
58
  end
60
59
 
61
60
  def check_migrations_mixed_with_code
@@ -64,82 +63,109 @@ module Pronto
64
63
  add_message_at_patch(migration_patches, "Do not mix migrations with other stuff", :fatal)
65
64
  end
66
65
 
67
- def check_migration_version_numbers
68
- return unless migration_patches.any?
66
+ def check_migration_schema_version
67
+ return if migration_patches.none? || !File.exist?(schema_file_name) || gitignored?(schema_file_name)
69
68
 
70
- version_numbers = migration_patches.map { |patch| patch.delta.new_file[:path].sub(/^[^0-9]*([0-9]+).+/, '\1') }
71
-
72
- schema_file_name = 'db/schema.rb'
73
- if File.exist?(schema_file_name) && !gitignored?(schema_file_name)
74
- if schema_patches.none?
75
- add_message_at_patch(migration_patches, "Migration file detected, but no changes in schema.rb", :error)
76
- else
77
- match = File.read(schema_file_name).match(%r{ActiveRecord::Schema.define\(version:\s*(?<version>[0-9_]+)\s*\)})
78
- if match
79
- schema_migration_version = match[:version]
80
- schema_migration_version_clean = schema_migration_version.gsub(/[^0-9]/, '')
81
- version_numbers.select { |version| version > schema_migration_version_clean }.each do |wrong_version|
82
- add_message_at_patch(
83
- migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
84
- "Migration version #{wrong_version} is above schema.rb version #{schema_migration_version}"
85
- )
86
- end
87
- else
88
- add_message_at_patch(migration_patches.first, "Cannot detect schema migration version", :warning)
89
- end
90
- end
69
+ if schema_patches.none?
70
+ return add_message_at_patch(migration_patches, "Migration file detected, but no changes in schema.rb", :error)
91
71
  end
92
72
 
93
- structure_file_name = 'db/structure.sql'
94
- if File.exist?(structure_file_name) && !gitignored?(structure_file_name)
95
- migration_line_regex = /\A\s*\('(?<version>[0-9]{14})'\)/
96
- structure_file_lines = File.readlines(structure_file_name)
97
- versions_from_schema = structure_file_lines.select{|line| line =~ migration_line_regex }
98
-
99
- missing_from_structure = false
100
- version_numbers.select { |version| versions_from_schema.none? { |strct_ver| strct_ver.include?(version) } }
101
- .each do |wrong_version|
102
- missing_from_structure = true
103
- add_message_at_patch(
104
- migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
105
- "Migration #{wrong_version} is missing from structure.sql", :error
106
- )
107
- end
73
+ migration_version_numbers.select { |version| schema_migration_version&.<(version) }.each do |wrong_version|
74
+ add_message_at_patch(
75
+ migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
76
+ "Migration version #{wrong_version} is above schema.rb version #{schema_migration_version}"
77
+ )
78
+ end
79
+ end
108
80
 
109
- if structure_patches.none? && !missing_from_structure
110
- add_message_at_patch(migration_patches, "Migration file detected, but no changes in structure.sql")
111
- end
81
+ def schema_migration_version
82
+ return @schema_migration_version if defined? @schema_migration_version
112
83
 
113
- structure_patches.each do |patch|
114
- patch.added_lines.each do |line|
115
- next unless line.content.match?(migration_line_regex)
116
- match = line.content.match(migration_line_regex)
117
- version = match[:version]
118
- next if version_numbers.include?(version) ||
119
- line.content.end_with?(',') && patch.deleted_lines.any? { |deleted| deleted.content.include?(version) }
84
+ @schema_migration_version ||= File.read(schema_file_name)
85
+ .match(/ActiveRecord::Schema.define\(version:\s*(?<version>[0-9_]+)\s*\)/)
86
+ &.[](:version)&.gsub(/[^0-9]/, "") ||
87
+ (add_message_at_patch(migration_patches.first,
88
+ "Cannot detect migration version in schema.rb. "\
89
+ "Migration versions are not checked.",
90
+ :warning) && nil)
91
+ end
120
92
 
121
- add_message_at_patch(patch, "Migration #{version} is not present in this changeset", :error, line: line)
122
- end
123
- end
93
+ def migration_version_numbers
94
+ @migration_version_numbers ||= migration_patches.map do |patch|
95
+ patch.delta.new_file[:path].sub(/^[^0-9]*([0-9]+).+/, '\1')
96
+ end
97
+ end
124
98
 
125
- bad_semicolon = !versions_from_schema.last.end_with?("');\n")
126
- unsorted_migrations = versions_from_schema != versions_from_schema.sort
99
+ def check_structure_migration_version_numbers
100
+ return unless migration_patches.any?
127
101
 
128
- if bad_semicolon || unsorted_migrations
129
- add_message_at_patch(
130
- structure_patches.first{ |patch| patch.lines.any?{ |line| line.content.match?(migration_line_regex) } },
131
- "Migration versions must be sorted and have correct syntax"
132
- )
133
- end
102
+ structure_file_name = "db/structure.sql"
103
+ return if !File.exist?(structure_file_name) || gitignored?(structure_file_name)
104
+
105
+ structure_file_lines = File.readlines(structure_file_name)
106
+ versions_from_schema = structure_file_lines.grep(migration_line_regex)
107
+
108
+ check_structure_versions_present(versions_from_schema)
109
+ check_structure_migration_missing
110
+
111
+ check_structure_versions_syntax(versions_from_schema)
112
+ check_structure_versions_sorted(versions_from_schema)
113
+ check_structure_ending(structure_file_lines)
114
+ end
115
+
116
+ def check_structure_versions_present(versions_from_schema)
117
+ found_missing_migration = false
118
+ migration_version_numbers.select { |version| versions_from_schema.none? { |ver| ver.include?(version) } }
119
+ .each do |wrong_version|
120
+ found_missing_migration = true
121
+ add_message_at_patch(migration_patches.first { |patch| patch.delta.new_file[:path].include?(wrong_version) },
122
+ "Migration #{wrong_version} is missing from structure.sql", :error)
123
+ end
124
+
125
+ return if structure_patches.any? || found_missing_migration
126
+
127
+ add_message_at_patch(migration_patches, "Migration file detected, but no changes in structure.sql")
128
+ end
134
129
 
135
- if structure_patches.any? &&
136
- !(structure_file_lines.last(2) == ["\n", "\n"] && structure_file_lines[-3] != "\n") &&
137
- !structure_file_lines.last.match?(/\A\s*[^\s]+\s*\n/)
138
- add_message_at_patch(structure_patches.last,
139
- "structure.sql must end with a newline or 2 empty lines", line: :last)
130
+ def check_structure_migration_missing
131
+ structure_patches.each do |patch|
132
+ patch.added_lines.select { |line| line.content.match?(migration_line_regex) }.each do |line|
133
+ version = line.content.match(migration_line_regex)[:version]
134
+ next if migration_version_numbers.include?(version)
135
+ next if line.content.end_with?(",") &&
136
+ patch.deleted_lines.any? { |del| del.content.include?("#{version}');") }
137
+
138
+ add_message_at_patch(patch, "Migration #{version} is not present in this changeset", :error, line: line)
140
139
  end
141
140
  end
141
+ end
142
+
143
+ def check_structure_versions_sorted(versions_from_schema)
144
+ return if versions_from_schema == versions_from_schema.sort
145
+
146
+ add_message_at_patch(
147
+ structure_patches.first { |patch| patch.lines.any? { |line| line.content.match?(migration_line_regex) } },
148
+ "Migration versions must be sorted and have correct syntax"
149
+ )
150
+ end
151
+
152
+ def check_structure_versions_syntax(versions_from_schema)
153
+ return if versions_from_schema.last.end_with?("');\n") &&
154
+ versions_from_schema.all? { |line| line.end_with?("'),\n", "');\n") }
142
155
 
156
+ add_message_at_patch(
157
+ structure_patches.first { |patch| patch.lines.any? { |line| line.content.match?(migration_line_regex) } },
158
+ "Migration version lines must be separated by comma and end with semicolon"
159
+ )
160
+ end
161
+
162
+ def check_structure_ending(structure_file_lines)
163
+ return if structure_patches.none?
164
+ return if structure_file_lines.last(2) == %W[\n \n] && structure_file_lines[-3] != "\n"
165
+ return if structure_file_lines.last.match?(/\A\s*[^\s]+\s*\n/)
166
+
167
+ add_message_at_patch(structure_patches.last,
168
+ "structure.sql must end with a newline or 2 empty lines", line: :last)
143
169
  end
144
170
 
145
171
  def gitignored?(path)
@@ -149,6 +175,22 @@ module Pronto
149
175
  nil
150
176
  end
151
177
 
178
+ def schema_file_name
179
+ "db/schema.rb"
180
+ end
181
+
182
+ def migration_line_regex
183
+ /\A\s*\('(?<version>[0-9]{14})'\)/
184
+ end
185
+
186
+ def migration_related_files
187
+ Regexp.union(
188
+ %r{db/migrate/.*[0-9]+_\w+.rb},
189
+ %r{db/schema.rb},
190
+ %r{db/structure.sql}
191
+ )
192
+ end
193
+
152
194
  def migration_patches
153
195
  # nb: there may be engines added to migrations_paths in config or database.yml
154
196
  # but cannot check for this without more knowledge into the particular app
@@ -159,14 +201,14 @@ module Pronto
159
201
  end
160
202
 
161
203
  def non_migration_related_patches
162
- @patches.reject do |patch|
204
+ @patches.select do |patch|
163
205
  path = patch.delta.new_file[:path]
164
- # lines = added_lines + deleted_lines, allow edits in comments
165
- path =~ %r{db/migrate/.*[0-9]+_\w+.rb} || path =~ %r{db/schema.rb} || path =~ %r{db/structure.sql} ||
166
- path.end_with?('.rb') && patch.lines.all? do |line|
167
- !(line.addition? || line.deletion?) ||
168
- (line.content =~ /\A\s*#/ || line.content =~ /\A\s*\z/)
169
- end
206
+ next false if path.match?(migration_related_files)
207
+
208
+ # allow edits in comments and blank lines (model annotations usually come there)
209
+ !path.end_with?(".rb") || patch.lines.any? do |line|
210
+ (line.addition? || line.deletion?) && !line.content.match?(/\A\s*(#|\z)/)
211
+ end
170
212
  end
171
213
  end
172
214
 
@@ -177,6 +219,5 @@ module Pronto
177
219
  def structure_patches
178
220
  @structure_patches = @patches.select { |patch| patch.delta.new_file[:path] =~ %r{db/structure.sql} }
179
221
  end
180
-
181
222
  end
182
223
  end
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  TEXT
16
16
  spec.homepage = "https://github.com/Vasfed/pronto-rails_migrations_annotated"
17
17
  spec.license = "MIT"
18
- spec.required_ruby_version = ">= 2.3.0"
18
+ spec.required_ruby_version = ">= 2.5.0"
19
19
 
20
20
  spec.metadata["homepage_uri"] = spec.homepage
21
21
  spec.metadata["source_code_uri"] = "https://github.com/Vasfed/pronto-rails_migrations_annotated"
@@ -30,5 +30,5 @@ Gem::Specification.new do |spec|
30
30
  end
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency "pronto", '~>0.11'
33
+ spec.add_dependency "pronto", "~>0.11"
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pronto-rails_migrations_annotated
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vasily Fedoseyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2021-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pronto
@@ -57,7 +57,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: 2.3.0
60
+ version: 2.5.0
61
61
  required_rubygems_version: !ruby/object:Gem::Requirement
62
62
  requirements:
63
63
  - - ">="