officer_neetzsche 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 271ee4879a7e1972dfd96edbf5ed8db9ce1edcd6c642631f52d6aa77bba9f7c6
4
- data.tar.gz: 4d6ce8fb83453d12b2bdbe4824974e776247fbbdb2a3f304d8f4fcf641fb9a5a
3
+ metadata.gz: 7a43403b5afe1dda6db1ada7ebcbed1edfcedf20b74310ae26418ad2476caaa4
4
+ data.tar.gz: 2852001bdaba6f54726cba1ff1381a001a78827c201ec3a0fb91da9b181da443
5
5
  SHA512:
6
- metadata.gz: f2380fae302b365699c193d7b8721ca3ed35a56e18b039b9c335ac4bb79815db3c35afaad341512e737c06332dc35ac752d9233ed518dc92c77bb1f777610314
7
- data.tar.gz: 702240bef2cf52c4fab6882271c737f0b5381d48917e3b3a10bdc43d4d36433dc1139eb6dc798db07495d9f6ca74608bc21283de9d690b8a8260a3e0490c2d62
6
+ metadata.gz: b6306713c14f1fc76bbb93341985a1de4e98a6ef49d5a168bb14ab9373cfd381ca0c0d0efab2ce866fc8f36df15090fdb95534561e9e9bbacf4fefe337b832b3
7
+ data.tar.gz: cedd62f5afe1fc618b2da479f3b93fc39f4d585008fd41d1bc5dda9a73601c1df160d159407b7cd85ce8fccaf6f4cbf56329e1f368b0e2177a1645d213436efb
data/CHANGELOG.md CHANGED
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 (unreleased)
3
+ ## 0.3.0 (unreleased)
4
+
5
+ ### Added
6
+
7
+ - `NEETzsche/StatementModifier`: rewrites an `if`, `unless`, `while`, `until`, or `rescue` whose body is one statement on one line as a statement modifier, whatever the resulting line length. Method-level and block-level `rescue` clauses count. Bodies that are themselves conditionals or rescues, comments inside the block, heredocs, and conditions or bodies whose meaning would change when the body is parsed first are left alone. Autocorrects, adding parentheses where the value is used.
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** the plugin now disables `Style/IfUnlessModifier` and `Style/WhileUntilModifier`, which `NEETzsche/StatementModifier` subsumes, and `Style/RescueModifier`, which forbids what it demands. Re-enable them in your own `.rubocop.yml` if you want them back; `Style/IfUnlessModifier` will then fight over modifier lines that exceed `Layout/LineLength`.
12
+
13
+ ## 0.2.0 (2026-09-09)
14
+
15
+ ### Added
16
+
17
+ - `NEETzsche/GeneratedMigrationTimestamp`: flags a migration file whose timestamp prefix is missing, impossible, in the future, or a round or patterned clock time that a hand or an LLM typed instead of `bin/rails generate migration`. Runs on `db/migrate/**/*.rb` and `db/*_migrate/**/*.rb`.
18
+
19
+ ## 0.1.0 (2026-09-09)
4
20
 
5
21
  - Add `NEETzsche/NoComments`, ported from hyperborea-news.
6
22
  - Disable `Style/Documentation` whenever the plugin is loaded, since it demands the comments `NEETzsche/NoComments` forbids.
data/README.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # OfficerNEETzsche
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/officer_neetzsche.svg)](https://rubygems.org/gems/officer_neetzsche)
4
+
3
5
  RuboCop cops that enforce NEETzsche-isms. Every cop ships enabled: register the plugin and the rules apply.
4
6
 
5
7
  ## Cops
6
8
 
7
9
  | Cop | Enforces | Autocorrect |
8
10
  | --- | --- | --- |
11
+ | `NEETzsche/GeneratedMigrationTimestamp` | Migration timestamps come from `bin/rails generate migration`, not a keyboard. | No |
9
12
  | `NEETzsche/NoComments` | No comments. The code explains itself. | Yes |
13
+ | `NEETzsche/StatementModifier` | An `if`, `unless`, `while`, `until`, or `rescue` around one statement is a modifier. | Yes |
10
14
 
11
15
  ## Requirements
12
16
 
@@ -23,15 +27,13 @@ group :development, :test do
23
27
  end
24
28
  ```
25
29
 
26
- Until the gem is published to RubyGems, point Bundler at the repository instead:
30
+ Then run `bundle install`. Or install it directly:
27
31
 
28
- ```ruby
29
- group :development, :test do
30
- gem "officer_neetzsche", github: "TheStranjer/OfficerNEETzsche", require: false
31
- end
32
+ ```sh
33
+ gem install officer_neetzsche
32
34
  ```
33
35
 
34
- Then run `bundle install`.
36
+ The gem is published at [rubygems.org/gems/officer_neetzsche](https://rubygems.org/gems/officer_neetzsche).
35
37
 
36
38
  ## Usage
37
39
 
@@ -58,6 +60,54 @@ Autocorrect what they flag:
58
60
  bundle exec rubocop --autocorrect --only NEETzsche
59
61
  ```
60
62
 
63
+ ## NEETzsche/GeneratedMigrationTimestamp
64
+
65
+ Flags a migration file whose 14-digit timestamp prefix looks typed rather than generated. `bin/rails generate migration` stamps the file with the current UTC second, and that second is almost never round. A person, or an LLM writing the file without running the generator, reaches for round numbers and patterns instead.
66
+
67
+ ```
68
+ # bad
69
+ db/migrate/20240101000000_create_users.rb
70
+ db/migrate/20240101000001_create_posts.rb
71
+ db/migrate/20240115120000_add_email_to_users.rb
72
+ db/migrate/20240315143000_create_comments.rb
73
+ db/migrate/20240601123456_add_index_to_comments.rb
74
+
75
+ # good
76
+ db/migrate/20260409074855_create_users.rb
77
+ ```
78
+
79
+ The offense sits on the first line of the file and names the tell:
80
+
81
+ - **No 14-digit timestamp.** `001_create_users.rb`, `20240101_create_users.rb`, `create_users.rb`.
82
+ - **Not a real date or time.** `2023-02-29`, month `13`, hour `24`, minute or second `60`.
83
+ - **In the future.** A date later than today in UTC.
84
+ - **A round clock time.** Seconds `00` on a five-minute mark: `00:00:00`, `12:00:00`, `14:30:00`, `09:15:00`.
85
+ - **A round hour plus a counter.** Up to nine seconds or nine minutes past any hour (`12:00:01`, `12:01:00`), or any whole minute or second past midnight (`00:00:23`, `00:37:00`). Incrementing the last digits of a copied timestamp produces these.
86
+ - **One number repeated.** `11:11:11`, `12:12:12`.
87
+ - **Consecutive numbers.** `01:02:03`, `10:11:12`.
88
+ - **A placeholder time.** `12:34:56`, `01:23:45`, `10:20:30`, `11:22:33`, `15:30:45`, `23:59:59`.
89
+
90
+ A time that only ends in `:00`, such as `14:17:00`, passes. The date is not judged beyond existing and having happened, so a migration generated on New Year's Day passes too.
91
+
92
+ The cop runs on `db/migrate/**/*.rb` and on the `db/*_migrate/**/*.rb` directories that multiple-database setups use. About one generated timestamp in a hundred lands on a clock-time tell, so an established project may see a few legitimate migrations flagged. Never rename a migration that has run anywhere, since its version is recorded in `schema_migrations`. Exclude the file instead:
93
+
94
+ ```yaml
95
+ NEETzsche/GeneratedMigrationTimestamp:
96
+ Exclude:
97
+ - "db/migrate/20230614140700_add_index_to_users.rb"
98
+ ```
99
+
100
+ Or silence it from the first line with a directive, which `NEETzsche/NoComments` allows:
101
+
102
+ ```ruby
103
+ # rubocop:disable NEETzsche/GeneratedMigrationTimestamp
104
+ class AddIndexToUsers < ActiveRecord::Migration[8.0]
105
+ ```
106
+
107
+ Projects that set `config.active_record.timestamped_migrations = false` number their migrations `001`, `002`, and so on. Every one of those trips the cop, so disable it there.
108
+
109
+ There is no autocorrect. The fix is to delete the file and run `bin/rails generate migration` again.
110
+
61
111
  ## NEETzsche/NoComments
62
112
 
63
113
  Flags every comment. On autocorrect, a comment on its own line is deleted along with the line, and a trailing comment is deleted along with the whitespace before it.
@@ -83,6 +133,62 @@ Comments that change how Ruby or RuboCop behaves are allowed:
83
133
 
84
134
  `Style/Documentation` demands the class and module comments this cop forbids, so the plugin disables it. Re-enable it in your own `.rubocop.yml` if you want the two to fight.
85
135
 
136
+ ## NEETzsche/StatementModifier
137
+
138
+ Flags an `if`, `unless`, `while`, `until`, or `rescue` whose body is one statement on one line, and rewrites it as a statement modifier. How long the modifier line ends up does not matter.
139
+
140
+ ```ruby
141
+ # bad
142
+ if condition?
143
+ do_thing
144
+ end
145
+
146
+ until queue.empty?
147
+ queue.pop.call
148
+ end
149
+
150
+ begin
151
+ fetch
152
+ rescue
153
+ fallback
154
+ end
155
+
156
+ def call
157
+ fetch
158
+ rescue StandardError
159
+ fallback
160
+ end
161
+
162
+ # good
163
+ do_thing if condition?
164
+ queue.pop.call until queue.empty?
165
+ fetch rescue fallback
166
+
167
+ def call
168
+ fetch rescue fallback
169
+ end
170
+
171
+ # good, because the body is more than one statement
172
+ if condition?
173
+ do_this
174
+ do_that
175
+ end
176
+ ```
177
+
178
+ The offense sits on the keyword. A `rescue` counts when it is the only clause, rescues `StandardError` (bare, or named, since that is what the modifier rescues), binds no variable, and has no `else`; the `begin`/`end` around it goes away, while a `def`, a block, or an `ensure` around it stays. Parentheses are added where the value is used, so `value = if condition? then compute end` becomes `value = (compute if condition?)` and `call(begin; fetch; rescue; fallback; end)` becomes `call((fetch rescue fallback))`. An assigned `rescue` needs none: `value = fetch rescue fallback` already binds that way.
179
+
180
+ Left alone, because the block form is the only correct one:
181
+
182
+ - A body of more than one statement, a statement that spans lines, or a condition that spans lines.
183
+ - `else`, `elsif`, ternaries, and `begin ... end while` loops.
184
+ - A body that is itself an `if`, `unless`, `while`, `until`, `case`, or `rescue`, in either form. No chained modifiers.
185
+ - A `rescue` that names another class, lists several, binds a variable, has several clauses, an `else`, or an empty body or handler.
186
+ - A comment on any line of the block other than the first. A comment on the keyword line moves to the end of the modifier line.
187
+ - A heredoc anywhere inside.
188
+ - Anything whose meaning changes when the body is parsed before the condition, which is what the modifier form does: a condition that assigns a local variable, matches a pattern, or captures a named group; a body that assigns a name the condition calls as a method, as in `if name.nil?` followed by `name = default`; and an endless method definition, which would swallow the modifier.
189
+
190
+ The plugin disables `Style/IfUnlessModifier` and `Style/WhileUntilModifier`, which this cop subsumes, and `Style/RescueModifier`, which forbids what this cop demands. Re-enable them in your own `.rubocop.yml` if you want them back; `Style/IfUnlessModifier` will then fight over any modifier line longer than `Layout/LineLength` allows. `Layout/LineLength` itself is not consulted. If a modifier line is too long for it, shorten the statement or the condition, or exclude the file. Under `rubocop --autocorrect`, `Layout/LineLength` may instead wrap the arguments of a call on that line, after which `Style/MultilineIfModifier` restores the block form around the now multi-line body, and this cop leaves it there. `Style/GuardClause` and `Style/SoleNestedConditional` may flag the same block with a different fix; whichever corrects first wins, and the result satisfies both.
191
+
86
192
  ## Configuration
87
193
 
88
194
  Disable one cop:
data/config/default.yml CHANGED
@@ -1,10 +1,32 @@
1
1
  NEETzsche:
2
2
  Enabled: true
3
3
 
4
+ NEETzsche/GeneratedMigrationTimestamp:
5
+ Description: "Requires migration filenames to carry the timestamp `bin/rails generate migration` stamped, not a round or patterned one typed by hand."
6
+ Enabled: true
7
+ VersionAdded: "0.2.0"
8
+ Include:
9
+ - "**/db/migrate/**/*.rb"
10
+ - "**/db/*_migrate/**/*.rb"
11
+
4
12
  NEETzsche/NoComments:
5
13
  Description: "Forbids comments. Magic comments, shebangs, and rubocop directives are allowed."
6
14
  Enabled: true
7
15
  VersionAdded: "0.1.0"
8
16
 
17
+ NEETzsche/StatementModifier:
18
+ Description: "Writes an `if`, `unless`, `while`, `until`, or `rescue` whose body is one statement as a modifier, whatever the line length."
19
+ Enabled: true
20
+ VersionAdded: "0.3.0"
21
+
9
22
  Style/Documentation:
10
23
  Enabled: false
24
+
25
+ Style/IfUnlessModifier:
26
+ Enabled: false
27
+
28
+ Style/RescueModifier:
29
+ Enabled: false
30
+
31
+ Style/WhileUntilModifier:
32
+ Enabled: false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OfficerNEETzsche
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module NEETzsche
8
+ class GeneratedMigrationTimestamp < Base
9
+ include RangeHelp
10
+
11
+ MSG = "Migration timestamp `%<timestamp>s` looks made up; %<tell>s. " \
12
+ "Let `bin/rails generate migration` name the file."
13
+ MSG_NO_TIMESTAMP = "Migration filename `%<basename>s` does not start with a 14-digit " \
14
+ "`YYYYMMDDHHMMSS_` timestamp. Let `bin/rails generate migration` name the file."
15
+
16
+ VERSION_PREFIX = /\A\d+(?=_)/.freeze
17
+
18
+ def on_new_investigation
19
+ basename = File.basename(processed_source.file_path)
20
+ digits = basename[VERSION_PREFIX]
21
+ message = if digits&.length == 14
22
+ made_up_message(digits)
23
+ else
24
+ format(MSG_NO_TIMESTAMP, basename: basename)
25
+ end
26
+
27
+ add_offense(first_character, message: message) if message
28
+ end
29
+
30
+ private
31
+
32
+ def made_up_message(digits)
33
+ tell = Timestamp.new(digits).tell
34
+ format(MSG, timestamp: digits, tell: tell) if tell
35
+ end
36
+
37
+ def first_character
38
+ source_range(processed_source.buffer, 1, 0)
39
+ end
40
+
41
+ class Timestamp
42
+ PLACEHOLDERS = %w[123456 012345 102030 112233 153045 235959].freeze
43
+
44
+ def initialize(digits)
45
+ @digits = digits
46
+ @year, @month, @day, @hour, @minute, @second = digits.unpack("a4a2a2a2a2a2").map(&:to_i)
47
+ end
48
+
49
+ def tell
50
+ return "`#{date}` is not a real date" unless Date.valid_date?(@year, @month, @day)
51
+ return "`#{clock}` is not a real time" unless real_time?
52
+ return "`#{date}` is in the future" if Date.new(@year, @month, @day) > Time.now.utc.to_date
53
+
54
+ clock_tell
55
+ end
56
+
57
+ private
58
+
59
+ def clock_tell
60
+ if round? then "`#{clock}` is a round clock time"
61
+ elsif counter? then "`#{clock}` is a round hour plus a counter"
62
+ elsif repeated? then "`#{clock}` repeats one number"
63
+ elsif consecutive? then "`#{clock}` is three consecutive numbers"
64
+ elsif placeholder? then "`#{clock}` is a placeholder time"
65
+ end
66
+ end
67
+
68
+ def date
69
+ "#{@digits[0, 4]}-#{@digits[4, 2]}-#{@digits[6, 2]}"
70
+ end
71
+
72
+ def clock
73
+ "#{@digits[8, 2]}:#{@digits[10, 2]}:#{@digits[12, 2]}"
74
+ end
75
+
76
+ def real_time?
77
+ @hour < 24 && @minute < 60 && @second < 60
78
+ end
79
+
80
+ def round?
81
+ @second.zero? && (@minute % 5).zero?
82
+ end
83
+
84
+ def counter?
85
+ return @minute.zero? || @second.zero? if @hour.zero?
86
+
87
+ (@minute.zero? && @second < 10) || (@second.zero? && @minute < 10)
88
+ end
89
+
90
+ def repeated?
91
+ @hour == @minute && @minute == @second
92
+ end
93
+
94
+ def consecutive?
95
+ @minute == @hour + 1 && @second == @minute + 1
96
+ end
97
+
98
+ def placeholder?
99
+ PLACEHOLDERS.include?(@digits[8, 6])
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module NEETzsche
6
+ class StatementModifier < Base
7
+ include RangeHelp
8
+ extend AutoCorrector
9
+
10
+ MSG = "Write a one-statement `%<keyword>s` as a modifier."
11
+
12
+ CHAIN_TYPES = %i[if while until while_post until_post for case case_match rescue kwbegin].freeze
13
+ CALLS = %i[send csend super yield].freeze
14
+
15
+ def self.autocorrect_incompatible_with
16
+ [Style::Next, Style::SoleNestedConditional]
17
+ end
18
+
19
+ def on_if(node)
20
+ return if node.modifier_form? || node.ternary? || node.elsif? || node.else?
21
+
22
+ check_conditional(node)
23
+ end
24
+
25
+ def on_while(node)
26
+ return if node.modifier_form?
27
+
28
+ check_conditional(node)
29
+ end
30
+ alias on_until on_while
31
+
32
+ def on_rescue(node)
33
+ resbody = node.resbody_branches.first
34
+ return unless bare_rescue?(node, resbody)
35
+
36
+ target = node.parent&.kwbegin_type? ? node.parent : node
37
+ return unless one_liner?(target, [node.body, resbody.body])
38
+
39
+ add_offense(resbody.loc.keyword, message: format(MSG, keyword: "rescue")) do |corrector|
40
+ rewrite(corrector, target, rescue_statement(node, resbody), Precedence.parenthesize_rescue?(target))
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def check_conditional(node)
47
+ return unless eligible_conditional?(node)
48
+
49
+ add_offense(node.loc.keyword, message: format(MSG, keyword: node.keyword)) do |corrector|
50
+ rewrite(corrector, node, conditional_statement(node), Precedence.parenthesize_conditional?(node))
51
+ end
52
+ end
53
+
54
+ def eligible_conditional?(node)
55
+ node.condition.single_line? && one_liner?(node, [node.body]) &&
56
+ !Precedence.endless_def?(node.body) && !ParseOrder.hazard?(node)
57
+ end
58
+
59
+ def conditional_statement(node)
60
+ "#{statement_source(node.body)} #{node.keyword} #{node.condition.source}"
61
+ end
62
+
63
+ def rescue_statement(node, resbody)
64
+ "#{statement_source(node.body)} rescue #{statement_source(resbody.body)}"
65
+ end
66
+
67
+ def rewrite(corrector, node, statement, parenthesize)
68
+ statement = "(#{statement})" if parenthesize
69
+ corrector.replace(node, [statement, first_line_comment(node)&.source].compact.join(" "))
70
+ end
71
+
72
+ def bare_rescue?(node, resbody)
73
+ !rescue_modifier?(resbody) && node.resbody_branches.one? && !node.else? &&
74
+ resbody.exception_variable.nil? && standard_error_only?(resbody.exceptions)
75
+ end
76
+
77
+ def rescue_modifier?(resbody)
78
+ processed_source.tokens.any? { |token| token.rescue_modifier? && token.pos == resbody.loc.keyword }
79
+ end
80
+
81
+ def standard_error_only?(exceptions)
82
+ return true if exceptions.empty?
83
+ return false unless exceptions.one? && exceptions.first.const_type?
84
+
85
+ namespace, name = *exceptions.first
86
+ name == :StandardError && (namespace.nil? || namespace.cbase_type?)
87
+ end
88
+
89
+ def one_liner?(node, statements)
90
+ statements.all? { |statement| single_statement?(statement) } &&
91
+ !modifier_body?(node) && !heredoc?(node) && comments_movable?(node)
92
+ end
93
+
94
+ def single_statement?(statement)
95
+ return false if statement.nil? || statement.begin_type? || CHAIN_TYPES.include?(statement.type)
96
+ return false if statement.source.end_with?(":") && !CALLS.include?(statement.type)
97
+
98
+ statement.single_line?
99
+ end
100
+
101
+ def modifier_body?(node)
102
+ parent = node.parent
103
+ return false unless parent && (parent.if_type? || parent.while_type? || parent.until_type?)
104
+
105
+ parent.modifier_form? && parent.body.equal?(node)
106
+ end
107
+
108
+ def heredoc?(node)
109
+ node.each_descendant(:str, :dstr, :xstr).any?(&:heredoc?)
110
+ end
111
+
112
+ def comments_movable?(node)
113
+ return false if processed_source.each_comment_in_lines((node.first_line + 1)..node.last_line).any?
114
+
115
+ first_line_comment(node).nil? || code_after(node).nil?
116
+ end
117
+
118
+ def first_line_comment(node)
119
+ processed_source.comment_at_line(node.first_line)
120
+ end
121
+
122
+ def code_after(node)
123
+ line_end = first_line_comment(node)&.source_range&.begin_pos if node.single_line?
124
+ line_end ||= processed_source.buffer.line_range(node.last_line).end_pos
125
+ code = range_between(node.source_range.end_pos, line_end).source.strip
126
+ code unless code.empty?
127
+ end
128
+
129
+ def statement_source(statement)
130
+ return statement.source unless statement.source.end_with?(":")
131
+
132
+ head = range_between(statement.source_range.begin_pos, statement.first_argument.source_range.begin_pos)
133
+ "#{head.source.rstrip}(#{statement.arguments.map(&:source).join(", ")})"
134
+ end
135
+
136
+ module ParseOrder
137
+ CAPTURING_CONDITIONS = %i[lvasgn match_with_lvasgn match_pattern match_pattern_p].freeze
138
+
139
+ module_function
140
+
141
+ def hazard?(node)
142
+ condition = node.condition
143
+ return true if condition.each_node(*CAPTURING_CONDITIONS).any?
144
+
145
+ assigned = node.body.each_node(:lvasgn).map { |assignment| assignment.children.first }
146
+ return false if assigned.empty?
147
+
148
+ condition.each_node(:send).any? { |call| bare_identifier?(call) && assigned.include?(call.method_name) }
149
+ end
150
+
151
+ def bare_identifier?(call)
152
+ call.receiver.nil? && call.arguments.empty? && !call.parenthesized? && call.block_node.nil?
153
+ end
154
+ end
155
+
156
+ module Precedence
157
+ STATEMENT_PARENTS = %i[begin kwbegin def defs block numblock itblock class module sclass
158
+ ensure rescue resbody preexe postexe].freeze
159
+ ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn casgn masgn op_asgn or_asgn and_asgn indexasgn].freeze
160
+
161
+ module_function
162
+
163
+ def parenthesize_conditional?(node)
164
+ !statement_position?(node)
165
+ end
166
+
167
+ def parenthesize_rescue?(node)
168
+ !statement_position?(node) && !assignment_value?(node)
169
+ end
170
+
171
+ def endless_def?(body)
172
+ %i[def defs].include?(body.type) && body.endless?
173
+ end
174
+
175
+ def statement_position?(node)
176
+ parent = node.parent
177
+ return true if parent.nil? || STATEMENT_PARENTS.include?(parent.type)
178
+
179
+ case parent.type
180
+ when :if, :while, :until, :case, :case_match then !parent.children.first.equal?(node)
181
+ when :for, :when, :in_pattern then parent.body.equal?(node)
182
+ else false
183
+ end
184
+ end
185
+
186
+ def assignment_value?(node)
187
+ parent = node.parent
188
+ return false unless parent.children.last.equal?(node)
189
+
190
+ ASSIGNMENTS.include?(parent.type) || (parent.send_type? && parent.assignment_method?)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -1,3 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "neetzsche/generated_migration_timestamp"
3
4
  require_relative "neetzsche/no_comments"
5
+ require_relative "neetzsche/statement_modifier"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: officer_neetzsche
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheStranjer
@@ -51,7 +51,9 @@ files:
51
51
  - lib/officer_neetzsche.rb
52
52
  - lib/officer_neetzsche/plugin.rb
53
53
  - lib/officer_neetzsche/version.rb
54
+ - lib/rubocop/cop/neetzsche/generated_migration_timestamp.rb
54
55
  - lib/rubocop/cop/neetzsche/no_comments.rb
56
+ - lib/rubocop/cop/neetzsche/statement_modifier.rb
55
57
  - lib/rubocop/cop/neetzsche_cops.rb
56
58
  homepage: https://github.com/TheStranjer/OfficerNEETzsche
57
59
  licenses: []