officer_neetzsche 0.1.0 → 0.2.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: 100c19f093b3f8972a0c73262ea4d1020057aea94a4835ac2a3e573d6b4d95c6
4
+ data.tar.gz: 7d267e9d2cd3c091223b63c65243aa9f369d3cc8356de4346772188edcfdd681
5
5
  SHA512:
6
- metadata.gz: f2380fae302b365699c193d7b8721ca3ed35a56e18b039b9c335ac4bb79815db3c35afaad341512e737c06332dc35ac752d9233ed518dc92c77bb1f777610314
7
- data.tar.gz: 702240bef2cf52c4fab6882271c737f0b5381d48917e3b3a10bdc43d4d36433dc1139eb6dc798db07495d9f6ca74608bc21283de9d690b8a8260a3e0490c2d62
6
+ metadata.gz: 8cf5f9105e71d0044dbbe8267db688767ea642f73ef6a00bc4272930e2bd8cecd3e42a6529b195e5b9ba905917345021447a701164372616e8c2fa214ae78b6c
7
+ data.tar.gz: b4fafbac860b255990358dfc15e95a4463358b3106a72fdb54627908f5484516722c0a616299e862c28241fef26e4a438d2cd1d3026d82c3d0e6b81bdb7c285d
data/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.0 (unreleased)
3
+ ## 0.2.0 (unreleased)
4
+
5
+ ### Added
6
+
7
+ - `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`.
8
+
9
+ ## 0.1.0 (2026-09-09)
4
10
 
5
11
  - Add `NEETzsche/NoComments`, ported from hyperborea-news.
6
12
  - Disable `Style/Documentation` whenever the plugin is loaded, since it demands the comments `NEETzsche/NoComments` forbids.
data/README.md CHANGED
@@ -1,11 +1,14 @@
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 |
10
13
 
11
14
  ## Requirements
@@ -23,15 +26,13 @@ group :development, :test do
23
26
  end
24
27
  ```
25
28
 
26
- Until the gem is published to RubyGems, point Bundler at the repository instead:
29
+ Then run `bundle install`. Or install it directly:
27
30
 
28
- ```ruby
29
- group :development, :test do
30
- gem "officer_neetzsche", github: "TheStranjer/OfficerNEETzsche", require: false
31
- end
31
+ ```sh
32
+ gem install officer_neetzsche
32
33
  ```
33
34
 
34
- Then run `bundle install`.
35
+ The gem is published at [rubygems.org/gems/officer_neetzsche](https://rubygems.org/gems/officer_neetzsche).
35
36
 
36
37
  ## Usage
37
38
 
@@ -58,6 +59,54 @@ Autocorrect what they flag:
58
59
  bundle exec rubocop --autocorrect --only NEETzsche
59
60
  ```
60
61
 
62
+ ## NEETzsche/GeneratedMigrationTimestamp
63
+
64
+ 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.
65
+
66
+ ```
67
+ # bad
68
+ db/migrate/20240101000000_create_users.rb
69
+ db/migrate/20240101000001_create_posts.rb
70
+ db/migrate/20240115120000_add_email_to_users.rb
71
+ db/migrate/20240315143000_create_comments.rb
72
+ db/migrate/20240601123456_add_index_to_comments.rb
73
+
74
+ # good
75
+ db/migrate/20260409074855_create_users.rb
76
+ ```
77
+
78
+ The offense sits on the first line of the file and names the tell:
79
+
80
+ - **No 14-digit timestamp.** `001_create_users.rb`, `20240101_create_users.rb`, `create_users.rb`.
81
+ - **Not a real date or time.** `2023-02-29`, month `13`, hour `24`, minute or second `60`.
82
+ - **In the future.** A date later than today in UTC.
83
+ - **A round clock time.** Seconds `00` on a five-minute mark: `00:00:00`, `12:00:00`, `14:30:00`, `09:15:00`.
84
+ - **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.
85
+ - **One number repeated.** `11:11:11`, `12:12:12`.
86
+ - **Consecutive numbers.** `01:02:03`, `10:11:12`.
87
+ - **A placeholder time.** `12:34:56`, `01:23:45`, `10:20:30`, `11:22:33`, `15:30:45`, `23:59:59`.
88
+
89
+ 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.
90
+
91
+ 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:
92
+
93
+ ```yaml
94
+ NEETzsche/GeneratedMigrationTimestamp:
95
+ Exclude:
96
+ - "db/migrate/20230614140700_add_index_to_users.rb"
97
+ ```
98
+
99
+ Or silence it from the first line with a directive, which `NEETzsche/NoComments` allows:
100
+
101
+ ```ruby
102
+ # rubocop:disable NEETzsche/GeneratedMigrationTimestamp
103
+ class AddIndexToUsers < ActiveRecord::Migration[8.0]
104
+ ```
105
+
106
+ 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.
107
+
108
+ There is no autocorrect. The fix is to delete the file and run `bin/rails generate migration` again.
109
+
61
110
  ## NEETzsche/NoComments
62
111
 
63
112
  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.
data/config/default.yml CHANGED
@@ -1,6 +1,14 @@
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OfficerNEETzsche
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.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
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "neetzsche/generated_migration_timestamp"
3
4
  require_relative "neetzsche/no_comments"
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TheStranjer
@@ -51,6 +51,7 @@ 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
55
56
  - lib/rubocop/cop/neetzsche_cops.rb
56
57
  homepage: https://github.com/TheStranjer/OfficerNEETzsche