annotaterb 4.22.0 → 4.23.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 +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +40 -4
- data/VERSION +1 -1
- data/lib/annotate_rb/helper.rb +24 -1
- data/lib/annotate_rb/model_annotator/annotated_file/generator.rb +34 -12
- data/lib/annotate_rb/model_annotator/annotated_file/updater.rb +14 -1
- data/lib/annotate_rb/model_annotator/annotation/annotation_builder.rb +1 -0
- data/lib/annotate_rb/model_annotator/annotation_decider.rb +3 -0
- data/lib/annotate_rb/model_annotator/annotation_diff_generator.rb +9 -4
- data/lib/annotate_rb/model_annotator/column_annotation/attributes_builder.rb +7 -0
- data/lib/annotate_rb/model_annotator/column_annotation/column_component.rb +2 -2
- data/lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb +4 -0
- data/lib/annotate_rb/model_annotator/enum_annotation/annotation.rb +40 -0
- data/lib/annotate_rb/model_annotator/enum_annotation/annotation_builder.rb +38 -0
- data/lib/annotate_rb/model_annotator/enum_annotation/enum_component.rb +27 -0
- data/lib/annotate_rb/model_annotator/enum_annotation.rb +11 -0
- data/lib/annotate_rb/model_annotator/file_parser/annotation_finder.rb +39 -4
- data/lib/annotate_rb/model_annotator/file_parser/annotation_target.rb +31 -0
- data/lib/annotate_rb/model_annotator/file_parser/custom_parser.rb +11 -4
- data/lib/annotate_rb/model_annotator/file_parser/magic_comment.rb +32 -0
- data/lib/annotate_rb/model_annotator/file_parser/parsed_file.rb +5 -6
- data/lib/annotate_rb/model_annotator/file_parser.rb +2 -0
- data/lib/annotate_rb/model_annotator/index_annotation/index_component.rb +26 -4
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +25 -1
- data/lib/annotate_rb/model_annotator/project_annotator.rb +2 -1
- data/lib/annotate_rb/model_annotator/single_file_annotator.rb +5 -5
- data/lib/annotate_rb/model_annotator/single_file_annotator_instruction.rb +3 -2
- data/lib/annotate_rb/model_annotator/zeitwerk_class_getter.rb +1 -1
- data/lib/annotate_rb/model_annotator.rb +1 -0
- data/lib/annotate_rb/options.rb +6 -2
- data/lib/annotate_rb/parser.rb +7 -5
- data/lib/annotate_rb/route_annotator/base_processor.rb +1 -1
- data/lib/annotate_rb/route_annotator/helper.rb +1 -1
- data/lib/annotate_rb/runner.rb +12 -4
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +8 -3
- data/lib/generators/annotate_rb/update_config/update_config_generator.rb +4 -1
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 249172f3c37299ac55df233909754c48456b629d11517ce03c84fae62232677d
|
|
4
|
+
data.tar.gz: 9fa726c0abbb3bc7508257d24e7e0f0e4f9a79c94e255909b8bf7162a2f25d9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f695ccb014c329b7c897f1832c4bb053df9fbec718198d68466884585b17f17fbfb6805a053024a38133196f7d9934aaff56d89c83598160966b1fc4dcaaaa3c
|
|
7
|
+
data.tar.gz: 73994c40f74a0c793cd974cf1369b59403e3b3a49ce6c643b661f9dfdffb7425dcfbb96c98ba2a23c3347861fa4cd759be380613d92627cb4046956af249dea3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v4.22.0](https://github.com/drwl/annotaterb/tree/v4.22.0) (2026-02-12)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/drwl/annotaterb/compare/v4.21.0...v4.22.0)
|
|
6
|
+
|
|
7
|
+
**Fixed bugs:**
|
|
8
|
+
|
|
9
|
+
- Yardoc formatting for comments on database attributes [\#162](https://github.com/drwl/annotaterb/issues/162)
|
|
10
|
+
|
|
11
|
+
**Closed issues:**
|
|
12
|
+
|
|
13
|
+
- New `ignore_multi_database_name` option seems to be non-functional [\#303](https://github.com/drwl/annotaterb/issues/303)
|
|
14
|
+
- Changing sort options does not change annotations [\#294](https://github.com/drwl/annotaterb/issues/294)
|
|
15
|
+
- CLI script for annotaterb not installed or runnable [\#290](https://github.com/drwl/annotaterb/issues/290)
|
|
16
|
+
- Feature: ruby-lsp addon [\#175](https://github.com/drwl/annotaterb/issues/175)
|
|
17
|
+
- Mounting ActionCable leads to weird annotation [\#161](https://github.com/drwl/annotaterb/issues/161)
|
|
18
|
+
|
|
19
|
+
**Merged pull requests:**
|
|
20
|
+
|
|
21
|
+
- Bump version to v4.22.0 [\#310](https://github.com/drwl/annotaterb/pull/310) ([drwl](https://github.com/drwl))
|
|
22
|
+
- Run CI on CRuby 4.0 [\#308](https://github.com/drwl/annotaterb/pull/308) ([viralpraxis](https://github.com/viralpraxis))
|
|
23
|
+
- Generate changelog for v4.21.0 [\#307](https://github.com/drwl/annotaterb/pull/307) ([drwl](https://github.com/drwl))
|
|
24
|
+
- fix NoMethodError when using nested\_position with fixture files [\#298](https://github.com/drwl/annotaterb/pull/298) ([OdenTakashi](https://github.com/OdenTakashi))
|
|
25
|
+
- fix: Respect configured sort [\#295](https://github.com/drwl/annotaterb/pull/295) ([patrickarnett](https://github.com/patrickarnett)) **(Maintainer note: this could result in annotations shifting depending on configuration, please create an issue if it is a breaking change)**
|
|
26
|
+
- Use `#lease_connection` if available [\#292](https://github.com/drwl/annotaterb/pull/292) ([viralpraxis](https://github.com/viralpraxis))
|
|
27
|
+
- refactor: simplify primary key check logic \(no functional changes\) [\#285](https://github.com/drwl/annotaterb/pull/285) ([OdenTakashi](https://github.com/OdenTakashi))
|
|
28
|
+
- Honor skip\_on\_db\_migrate config option when runnig migrate tasks [\#274](https://github.com/drwl/annotaterb/pull/274) ([martinechtner](https://github.com/martinechtner))
|
|
29
|
+
|
|
3
30
|
## [v4.21.0](https://github.com/drwl/annotaterb/tree/v4.21.0) (2026-01-30)
|
|
4
31
|
|
|
5
32
|
[Full Changelog](https://github.com/drwl/annotaterb/compare/v4.20.0...v4.21.0)
|
data/README.md
CHANGED
|
@@ -64,6 +64,13 @@ $ bin/rails g annotate_rb:install
|
|
|
64
64
|
|
|
65
65
|
This will copy a rake task into your Rails project's `lib/tasks` directory that will hook into the Rails project rake tasks, automatically running AnnotateRb after database migration rake tasks.
|
|
66
66
|
|
|
67
|
+
```sh
|
|
68
|
+
$ bin/rails db:migrate
|
|
69
|
+
# ...
|
|
70
|
+
# Annotating models
|
|
71
|
+
# Annotated (1): app/models/task.rb
|
|
72
|
+
```
|
|
73
|
+
|
|
67
74
|
To skip the automatic annotation that happens after a db task, pass the environment variable `ANNOTATERB_SKIP_ON_DB_TASKS=1` before your command.
|
|
68
75
|
|
|
69
76
|
```sh
|
|
@@ -184,10 +191,10 @@ Additional options that work for annotating models and routes
|
|
|
184
191
|
-f [bare|rdoc|yard|markdown], Render Schema Information as plain/RDoc/Yard/Markdown
|
|
185
192
|
--format
|
|
186
193
|
--config_path [path] Path to configuration file (by default, .annotaterb.yml in the root of the project)
|
|
187
|
-
-p [before|top|after|bottom],
|
|
188
|
-
--position
|
|
189
|
-
--pc, --position-in-class [before|top|after|bottom]
|
|
190
|
-
Place the annotations at the top (before) or the
|
|
194
|
+
-p [before|top|after|bottom|before_doc],
|
|
195
|
+
--position Place the annotations at the top (before), bottom (after), or above the class documentation (before_doc) of the model/test/fixture/factory/route/serializer file(s)
|
|
196
|
+
--pc, --position-in-class [before|top|after|bottom|before_doc]
|
|
197
|
+
Place the annotations at the top (before), bottom (after), or above the class documentation (before_doc) of the model file
|
|
191
198
|
--pf, --position-in-factory [before|top|after|bottom]
|
|
192
199
|
Place the annotations at the top (before) or the bottom (after) of any factory files
|
|
193
200
|
--px, --position-in-fixture [before|top|after|bottom]
|
|
@@ -230,6 +237,35 @@ Annotaterb reads first the configuration file, if it exists, passes its content
|
|
|
230
237
|
|
|
231
238
|
For further details visit the [section in the migration guide](MIGRATION_GUIDE.md#automatic-annotations-after-running-database-migration-commands).
|
|
232
239
|
|
|
240
|
+
### Preserving class documentation comments
|
|
241
|
+
|
|
242
|
+
By default, when `position_in_class` is `before` (or `top`), AnnotateRb places the schema annotation immediately before the class declaration line. Any human-written documentation comment that was directly above the class is therefore pushed above the annotation.
|
|
243
|
+
|
|
244
|
+
If you prefer to keep the documentation comment adjacent to the class, set `position_in_class` to `before_doc`. The schema annotation is then inserted above the documentation comment block, leaving the comment directly before the class.
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
# Source file:
|
|
248
|
+
# Doc about User
|
|
249
|
+
class User < ApplicationRecord
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# With position_in_class: before (default)
|
|
253
|
+
# Doc about User
|
|
254
|
+
# == Schema Information
|
|
255
|
+
# ...
|
|
256
|
+
class User < ApplicationRecord
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# With position_in_class: before_doc
|
|
260
|
+
# == Schema Information
|
|
261
|
+
# ...
|
|
262
|
+
# Doc about User
|
|
263
|
+
class User < ApplicationRecord
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
A "documentation comment" is the contiguous comment block immediately above the class declaration, with no blank line between the comments and the class. Recognized magic comments (`encoding`, `frozen_string_literal`, `shareable_constant_value`, `warn_indent`, `typed`, `rbs_inline`, plus Emacs/Vim style modelines) are excluded so the annotation can still be inserted between magic comments and the class doc.
|
|
268
|
+
|
|
233
269
|
### How to skip annotating a particular model
|
|
234
270
|
|
|
235
271
|
If you want to always skip annotations on a particular model, add this string
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.23.0
|
data/lib/annotate_rb/helper.rb
CHANGED
|
@@ -2,15 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
module AnnotateRb
|
|
4
4
|
module Helper
|
|
5
|
+
# Unicode ranges that typically occupy 2 columns in monospace terminals.
|
|
6
|
+
# This is a simplified wcwidth implementation.
|
|
7
|
+
DOUBLE_WIDTH_RANGES = [
|
|
8
|
+
0x1100..0x115F, # Hangul Jamo
|
|
9
|
+
0x2E80..0x303E, # CJK Radicals, Kangxi, CJK Symbols
|
|
10
|
+
0x3040..0x4DBF, # Hiragana, Katakana, Bopomofo, CJK Ext A
|
|
11
|
+
0x4E00..0x9FFF, # CJK Unified Ideographs
|
|
12
|
+
0xAC00..0xD7AF, # Hangul Syllables
|
|
13
|
+
0xF900..0xFAFF, # CJK Compatibility Ideographs
|
|
14
|
+
0xFE30..0xFE6F, # CJK Compatibility Forms
|
|
15
|
+
0xFF01..0xFF60, # Fullwidth Forms
|
|
16
|
+
0xFFE0..0xFFE6, # Fullwidth Signs
|
|
17
|
+
0x20000..0x2FA1F, # CJK Extensions B-F
|
|
18
|
+
0x30000..0x3134F # CJK Extensions G-I
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
5
21
|
class << self
|
|
6
22
|
def width(string)
|
|
7
|
-
string.
|
|
23
|
+
string.each_char.sum { |char| double_width?(char) ? 2 : 1 }
|
|
8
24
|
end
|
|
9
25
|
|
|
10
26
|
# TODO: Find another implementation that doesn't depend on ActiveSupport
|
|
11
27
|
def fallback(*args)
|
|
12
28
|
args.compact.detect(&:present?)
|
|
13
29
|
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def double_width?(char)
|
|
34
|
+
cp = char.ord
|
|
35
|
+
DOUBLE_WIDTH_RANGES.any? { |range| range.cover?(cp) }
|
|
36
|
+
end
|
|
14
37
|
end
|
|
15
38
|
end
|
|
16
39
|
end
|
|
@@ -5,7 +5,7 @@ module AnnotateRb
|
|
|
5
5
|
module AnnotatedFile
|
|
6
6
|
# Generates the file with fresh annotations
|
|
7
7
|
class Generator
|
|
8
|
-
def initialize(file_content, new_annotations, annotation_position, parser_klass, parsed_file, options)
|
|
8
|
+
def initialize(file_content, new_annotations, annotation_position, parser_klass, parsed_file, options, model_class_name: nil)
|
|
9
9
|
@annotation_position = annotation_position
|
|
10
10
|
@options = options
|
|
11
11
|
|
|
@@ -16,6 +16,7 @@ module AnnotateRb
|
|
|
16
16
|
|
|
17
17
|
@parser = parser_klass
|
|
18
18
|
@parsed_file = parsed_file
|
|
19
|
+
@model_class_name = model_class_name
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
# @return [String] Returns the annotated file content to be written back to a file
|
|
@@ -35,7 +36,7 @@ module AnnotateRb
|
|
|
35
36
|
# We need to get class start and class end depending on the position
|
|
36
37
|
parsed = @parser.parse(content_without_annotations)
|
|
37
38
|
|
|
38
|
-
_content = if
|
|
39
|
+
_content = if Parser::AFTER_POSITIONS.include?(annotation_write_position)
|
|
39
40
|
content_annotated_after(parsed, content_without_annotations)
|
|
40
41
|
else
|
|
41
42
|
content_annotated_before(parsed, content_without_annotations, annotation_write_position)
|
|
@@ -45,10 +46,14 @@ module AnnotateRb
|
|
|
45
46
|
private
|
|
46
47
|
|
|
47
48
|
def content_annotated_before(parsed, content_without_annotations, write_position)
|
|
48
|
-
same_write_position =
|
|
49
|
+
same_write_position = same_side?(write_position)
|
|
49
50
|
|
|
50
51
|
_constant_name, line_number_before = determine_annotation_position(parsed)
|
|
51
52
|
|
|
53
|
+
if Parser::DOC_AWARE_POSITIONS.include?(write_position)
|
|
54
|
+
line_number_before = class_doc_start_line(content_without_annotations.lines, line_number_before)
|
|
55
|
+
end
|
|
56
|
+
|
|
52
57
|
content_with_annotations_written_before = []
|
|
53
58
|
content_with_annotations_written_before << content_without_annotations.lines[0...line_number_before]
|
|
54
59
|
content_with_annotations_written_before << $/ if @parsed_file.has_leading_whitespace? && same_write_position
|
|
@@ -59,17 +64,34 @@ module AnnotateRb
|
|
|
59
64
|
content_with_annotations_written_before.join
|
|
60
65
|
end
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
def same_side?(write_position)
|
|
68
|
+
return false unless @parsed_file.has_annotations?
|
|
69
|
+
|
|
70
|
+
new_side = Parser::AFTER_POSITIONS.include?(write_position) ? :after : :before
|
|
71
|
+
@parsed_file.annotation_position == new_side
|
|
72
|
+
end
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
# Magic comments are excluded so annotations slot between them and
|
|
75
|
+
# the class doc rather than being treated as part of the doc.
|
|
76
|
+
def class_doc_start_line(content_lines, line_number_before)
|
|
77
|
+
doc_start = line_number_before
|
|
78
|
+
cursor = line_number_before - 1
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
while cursor >= 0
|
|
81
|
+
line = content_lines[cursor]
|
|
82
|
+
break if line.match?(/\A\s*\z/)
|
|
83
|
+
break unless line.match?(/\A\s*#/)
|
|
84
|
+
break if FileParser::MagicComment.match?(line)
|
|
85
|
+
|
|
86
|
+
doc_start = cursor
|
|
87
|
+
cursor -= 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
doc_start
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def determine_annotation_position(parsed)
|
|
94
|
+
FileParser::AnnotationTarget.find(parsed, @options, model_class_name: @model_class_name) || [nil, 0]
|
|
73
95
|
end
|
|
74
96
|
|
|
75
97
|
# Formats annotations with appropriate indentation for consistent code structure.
|
|
@@ -18,13 +18,26 @@ module AnnotateRb
|
|
|
18
18
|
def update
|
|
19
19
|
return "" if !@parsed_file.has_annotations?
|
|
20
20
|
|
|
21
|
-
new_annotation = wrapped_content(@new_annotations)
|
|
21
|
+
new_annotation = indent(wrapped_content(@new_annotations), existing_indentation)
|
|
22
22
|
|
|
23
23
|
_content = @file_content.sub(@parsed_file.annotations) { new_annotation }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
+
# Returns the leading whitespace of the existing annotation block so
|
|
29
|
+
# nested-position annotations keep their indentation across re-runs.
|
|
30
|
+
def existing_indentation
|
|
31
|
+
first_line = @parsed_file.annotations.lines.first
|
|
32
|
+
first_line&.match(/\A([ \t]*)/)&.[](1) || ""
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def indent(content, indentation)
|
|
36
|
+
return content if indentation.empty?
|
|
37
|
+
|
|
38
|
+
content.lines.map { |line| (line == "\n") ? line : "#{indentation}#{line}" }.join
|
|
39
|
+
end
|
|
40
|
+
|
|
28
41
|
def wrapped_content(content)
|
|
29
42
|
wrapper_open = if @options[:wrapper_open]
|
|
30
43
|
"# #{@options[:wrapper_open]}\n"
|
|
@@ -27,6 +27,7 @@ module AnnotateRb
|
|
|
27
27
|
IndexAnnotation::AnnotationBuilder.new(@model, @options).build,
|
|
28
28
|
ForeignKeyAnnotation::AnnotationBuilder.new(@model, @options).build,
|
|
29
29
|
CheckConstraintAnnotation::AnnotationBuilder.new(@model, @options).build,
|
|
30
|
+
EnumAnnotation::AnnotationBuilder.new(@model, @options).build,
|
|
30
31
|
SchemaFooter.new
|
|
31
32
|
]
|
|
32
33
|
end
|
|
@@ -33,6 +33,9 @@ module AnnotateRb
|
|
|
33
33
|
warn "Unable to process #{@file}: #{e.message}"
|
|
34
34
|
warn "\t#{e.backtrace.join("\n\t")}" if @options[:trace]
|
|
35
35
|
end
|
|
36
|
+
rescue ActiveRecord::ConnectionNotEstablished,
|
|
37
|
+
ActiveRecord::NoDatabaseError => e
|
|
38
|
+
abort "AnnotateRb: Database connection error - #{e.message}"
|
|
36
39
|
rescue => e
|
|
37
40
|
warn "Unable to process #{@file}: #{e.message}"
|
|
38
41
|
warn "\t#{e.backtrace.join("\n\t")}" if @options[:trace]
|
|
@@ -4,14 +4,17 @@ module AnnotateRb
|
|
|
4
4
|
module ModelAnnotator
|
|
5
5
|
# Compares the current file content and new annotation block and generates the column annotation differences
|
|
6
6
|
class AnnotationDiffGenerator
|
|
7
|
-
|
|
7
|
+
# Leading whitespace is tolerated so that annotations placed inside a
|
|
8
|
+
# nested module/class (i.e. with --nested-position, or hand-aligned)
|
|
9
|
+
# still match.
|
|
10
|
+
HEADER_PATTERN = /(^[ \t]*# Table name:.*?\n([ \t]*#.*\r?\n)*\r?)/
|
|
8
11
|
# Example matches:
|
|
9
12
|
# - "# id :uuid not null, primary key"
|
|
10
13
|
# - "# title(length 255) :string not null"
|
|
11
14
|
# - "# status(a/b/c) :string not null"
|
|
12
15
|
# - "# created_at :datetime not null"
|
|
13
16
|
# - "# updated_at :datetime not null"
|
|
14
|
-
COLUMN_PATTERN =
|
|
17
|
+
COLUMN_PATTERN = /^[ \t]*#[\t ]+[[\p{L}\p{N}_]*.`\[\]():]+(?:\(.*?\))?[\t ]+.+$/
|
|
15
18
|
class << self
|
|
16
19
|
def call(file_content, annotation_block)
|
|
17
20
|
new(file_content, annotation_block).generate
|
|
@@ -30,14 +33,16 @@ module AnnotateRb
|
|
|
30
33
|
current_annotations = @file_content.match(HEADER_PATTERN).to_s
|
|
31
34
|
new_annotations = @annotation_block.match(HEADER_PATTERN).to_s
|
|
32
35
|
|
|
36
|
+
# Strip leading whitespace from each scanned column so that an
|
|
37
|
+
# indented existing block compares equal to a flush-left new block.
|
|
33
38
|
current_annotation_columns = if current_annotations.present?
|
|
34
|
-
current_annotations.scan(COLUMN_PATTERN)
|
|
39
|
+
current_annotations.scan(COLUMN_PATTERN).map(&:lstrip)
|
|
35
40
|
else
|
|
36
41
|
[]
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
new_annotation_columns = if new_annotations.present?
|
|
40
|
-
new_annotations.scan(COLUMN_PATTERN)
|
|
45
|
+
new_annotations.scan(COLUMN_PATTERN).map(&:lstrip)
|
|
41
46
|
else
|
|
42
47
|
[]
|
|
43
48
|
end
|
|
@@ -84,6 +84,13 @@ module AnnotateRb
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
+
if column_type == "enum" && @options[:show_enums]
|
|
88
|
+
enum_type_name = @column.sql_type
|
|
89
|
+
if enum_type_name.present? && enum_type_name != "enum"
|
|
90
|
+
attrs << "enum_type: #{enum_type_name}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
# Check if the column is a virtual column and print the function
|
|
88
95
|
if @options[:show_virtual_columns] && @column.virtual?
|
|
89
96
|
# Any whitespace in the function gets reduced to a single space
|
|
@@ -42,7 +42,7 @@ module AnnotateRb
|
|
|
42
42
|
|
|
43
43
|
def to_yard
|
|
44
44
|
res = ""
|
|
45
|
-
res +=
|
|
45
|
+
res += "# @!attribute #{name}\n"
|
|
46
46
|
|
|
47
47
|
ruby_class = if @column.respond_to?(:array) && @column.array
|
|
48
48
|
"Array<#{map_col_type_to_ruby_classes(type)}>"
|
|
@@ -50,7 +50,7 @@ module AnnotateRb
|
|
|
50
50
|
map_col_type_to_ruby_classes(type)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
res +=
|
|
53
|
+
res += "# @return [#{ruby_class}]"
|
|
54
54
|
|
|
55
55
|
res
|
|
56
56
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module EnumAnnotation
|
|
6
|
+
class Annotation
|
|
7
|
+
HEADER_TEXT = "Enums"
|
|
8
|
+
|
|
9
|
+
def initialize(enums)
|
|
10
|
+
@enums = enums
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def body
|
|
14
|
+
[
|
|
15
|
+
Components::BlankCommentLine.new,
|
|
16
|
+
Components::Header.new(HEADER_TEXT),
|
|
17
|
+
Components::BlankCommentLine.new,
|
|
18
|
+
*@enums
|
|
19
|
+
]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def to_markdown
|
|
23
|
+
body.map(&:to_markdown).join("\n")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_rdoc
|
|
27
|
+
body.map(&:to_rdoc).join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_yard
|
|
31
|
+
body.map(&:to_yard).join("\n")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_default
|
|
35
|
+
body.map(&:to_default).join("\n")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module EnumAnnotation
|
|
6
|
+
class AnnotationBuilder
|
|
7
|
+
def initialize(model, options)
|
|
8
|
+
@model = model
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def build
|
|
13
|
+
return Components::NilComponent.new unless @options[:show_enums]
|
|
14
|
+
|
|
15
|
+
enum_types = @model.enum_types
|
|
16
|
+
return Components::NilComponent.new if enum_types.empty?
|
|
17
|
+
|
|
18
|
+
# Filter to only enum types used by this table's columns
|
|
19
|
+
table_enum_types = @model.columns.select { |col| col.type == :enum }
|
|
20
|
+
.map { |col| col.sql_type.to_s }
|
|
21
|
+
.uniq
|
|
22
|
+
|
|
23
|
+
relevant_enums = enum_types
|
|
24
|
+
.filter_map { |name, values| [name.to_s, values] if table_enum_types.include?(name.to_s) }
|
|
25
|
+
return Components::NilComponent.new if relevant_enums.empty?
|
|
26
|
+
|
|
27
|
+
max_size = relevant_enums.map { |name, _values| name.size }.max + 1
|
|
28
|
+
|
|
29
|
+
components = relevant_enums.sort_by { |name, _values| name }.map do |name, values|
|
|
30
|
+
EnumComponent.new(name, values, max_size)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Annotation.new(components)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module EnumAnnotation
|
|
6
|
+
class EnumComponent < Components::Base
|
|
7
|
+
attr_reader :name, :values, :max_size
|
|
8
|
+
|
|
9
|
+
def initialize(name, values, max_size)
|
|
10
|
+
@name = name
|
|
11
|
+
@values = values
|
|
12
|
+
@max_size = max_size
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_default
|
|
16
|
+
# standard:disable Lint/FormatParameterMismatch
|
|
17
|
+
sprintf("# %-#{max_size}.#{max_size}s %s", name, values.join(", ")).rstrip
|
|
18
|
+
# standard:enable Lint/FormatParameterMismatch
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_markdown
|
|
22
|
+
sprintf("# * `%s`: `%s`", name, values.join(", "))
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module EnumAnnotation
|
|
6
|
+
autoload :AnnotationBuilder, "annotate_rb/model_annotator/enum_annotation/annotation_builder"
|
|
7
|
+
autoload :Annotation, "annotate_rb/model_annotator/enum_annotation/annotation"
|
|
8
|
+
autoload :EnumComponent, "annotate_rb/model_annotator/enum_annotation/enum_component"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -8,6 +8,20 @@ module AnnotateRb
|
|
|
8
8
|
COMPAT_PREFIX_MD = "## Schema Info"
|
|
9
9
|
DEFAULT_ANNOTATION_ENDING = "#"
|
|
10
10
|
|
|
11
|
+
SCHEMA_HEADER_PREFIXES = [
|
|
12
|
+
COMPAT_PREFIX,
|
|
13
|
+
COMPAT_PREFIX_MD,
|
|
14
|
+
"Table name:",
|
|
15
|
+
"Database name:",
|
|
16
|
+
"Schema version:"
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
SCHEMA_HEADER_EXACT = [
|
|
20
|
+
IndexAnnotation::Annotation::HEADER_TEXT,
|
|
21
|
+
ForeignKeyAnnotation::Annotation::HEADER_TEXT,
|
|
22
|
+
CheckConstraintAnnotation::Annotation::HEADER_TEXT
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
11
25
|
class MalformedAnnotation < StandardError; end
|
|
12
26
|
|
|
13
27
|
class NoAnnotationFound < StandardError; end
|
|
@@ -70,10 +84,7 @@ module AnnotateRb
|
|
|
70
84
|
end
|
|
71
85
|
end
|
|
72
86
|
else
|
|
73
|
-
|
|
74
|
-
while ending > start && comments[ending].first != DEFAULT_ANNOTATION_ENDING
|
|
75
|
-
ending -= 1
|
|
76
|
-
end
|
|
87
|
+
ending = walk_forward_through_schema(comments, start, ending)
|
|
77
88
|
end
|
|
78
89
|
|
|
79
90
|
# We want .last because we want the line indexes
|
|
@@ -95,6 +106,30 @@ module AnnotateRb
|
|
|
95
106
|
def annotated?
|
|
96
107
|
@annotation_start && @annotation_end
|
|
97
108
|
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def walk_forward_through_schema(comments, start, block_end)
|
|
113
|
+
ending = start
|
|
114
|
+
while ending < block_end
|
|
115
|
+
break unless schema_like?(comments[ending + 1].first)
|
|
116
|
+
ending += 1
|
|
117
|
+
end
|
|
118
|
+
ending
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Tabular rows have ≥2 leading spaces after `#`; prose has at most one.
|
|
122
|
+
def schema_like?(comment)
|
|
123
|
+
return true if comment == DEFAULT_ANNOTATION_ENDING
|
|
124
|
+
|
|
125
|
+
text = comment.sub(/\A#\s?/, "")
|
|
126
|
+
return false if text.empty?
|
|
127
|
+
|
|
128
|
+
return true if SCHEMA_HEADER_PREFIXES.any? { |p| text.start_with?(p) }
|
|
129
|
+
return true if SCHEMA_HEADER_EXACT.include?(text)
|
|
130
|
+
|
|
131
|
+
comment.match?(/\A#\s{2,}\S/)
|
|
132
|
+
end
|
|
98
133
|
end
|
|
99
134
|
end
|
|
100
135
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module FileParser
|
|
6
|
+
# When `model_class_name` is given, matches that class declaration directly
|
|
7
|
+
# so inner classes inside the model body cannot be mistaken for the target.
|
|
8
|
+
module AnnotationTarget
|
|
9
|
+
SKIP_NAMES = %w[require require_relative load].freeze
|
|
10
|
+
|
|
11
|
+
def self.find(parser, options, model_class_name: nil)
|
|
12
|
+
starts = parser.starts.reject { |entry| SKIP_NAMES.include?(entry.first) }
|
|
13
|
+
return nil if starts.empty?
|
|
14
|
+
|
|
15
|
+
return starts.first unless options[:nested_position] && parser.respond_to?(:type_map)
|
|
16
|
+
|
|
17
|
+
if model_class_name && parser.type_map[model_class_name] == :class
|
|
18
|
+
match = starts.find { |name, _line| name == model_class_name }
|
|
19
|
+
return match if match
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class_entries = starts
|
|
23
|
+
.select { |name, _line| parser.type_map[name] == :class }
|
|
24
|
+
.uniq { |name, _line| name }
|
|
25
|
+
|
|
26
|
+
class_entries.last || starts.first
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -50,16 +50,23 @@ module AnnotateRb
|
|
|
50
50
|
}
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def on_const(const)
|
|
54
|
+
@_last_const_lineno = lineno
|
|
55
|
+
super
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
def on_const_ref(const)
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
declaration_lineno = @_last_const_lineno || lineno
|
|
60
|
+
add_event(__method__, const, declaration_lineno)
|
|
61
|
+
@block_starts << [const, declaration_lineno]
|
|
56
62
|
super
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
# Used for `class Foo::User`
|
|
60
66
|
def on_const_path_ref(_left, const)
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
declaration_lineno = @_last_const_lineno || lineno
|
|
68
|
+
add_event(__method__, const, declaration_lineno)
|
|
69
|
+
@block_starts << [const, declaration_lineno]
|
|
63
70
|
super
|
|
64
71
|
end
|
|
65
72
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AnnotateRb
|
|
4
|
+
module ModelAnnotator
|
|
5
|
+
module FileParser
|
|
6
|
+
module MagicComment
|
|
7
|
+
DIRECTIVE_KEYS = %w[
|
|
8
|
+
encoding
|
|
9
|
+
coding
|
|
10
|
+
frozen_string_literal
|
|
11
|
+
warn_indent
|
|
12
|
+
shareable_constant_value
|
|
13
|
+
typed
|
|
14
|
+
rbs_inline
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
SIMPLE = /\A\s*#\s*(?<key>[A-Za-z][A-Za-z0-9_-]*)\s*:\s*\S/
|
|
18
|
+
EMACS = /\A\s*#\s*-\*-.*-\*-\s*\z/
|
|
19
|
+
VIM = /\A\s*#\s*vim:\s/
|
|
20
|
+
|
|
21
|
+
def self.match?(line)
|
|
22
|
+
if (m = SIMPLE.match(line))
|
|
23
|
+
key = m[:key].downcase.tr("-", "_")
|
|
24
|
+
return true if DIRECTIVE_KEYS.include?(key)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
EMACS.match?(line) || VIM.match?(line)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -6,12 +6,13 @@ module AnnotateRb
|
|
|
6
6
|
class ParsedFile
|
|
7
7
|
SKIP_ANNOTATION_STRING = "# -*- SkipSchemaAnnotations"
|
|
8
8
|
|
|
9
|
-
def initialize(file_content, new_annotations, parser_klass, options)
|
|
9
|
+
def initialize(file_content, new_annotations, parser_klass, options, model_class_name: nil)
|
|
10
10
|
@file_content = file_content
|
|
11
11
|
@file_lines = @file_content.lines
|
|
12
12
|
@new_annotations = new_annotations
|
|
13
13
|
@parser_klass = parser_klass
|
|
14
14
|
@options = options
|
|
15
|
+
@model_class_name = model_class_name
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def parse
|
|
@@ -44,7 +45,7 @@ module AnnotateRb
|
|
|
44
45
|
annotation_start = @finder.annotation_start
|
|
45
46
|
annotation_end = @finder.annotation_end
|
|
46
47
|
|
|
47
|
-
if @file_lines[annotation_start - 1]&.strip&.empty?
|
|
48
|
+
if annotation_start > 0 && @file_lines[annotation_start - 1]&.strip&.empty?
|
|
48
49
|
annotation_start -= 1
|
|
49
50
|
has_leading_whitespace = true
|
|
50
51
|
end
|
|
@@ -64,10 +65,8 @@ module AnnotateRb
|
|
|
64
65
|
annotation_position = nil
|
|
65
66
|
|
|
66
67
|
if has_annotations
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# If the file does not have any class or module declaration then const_declaration can be nil
|
|
70
|
-
_const, line_number = const_declaration
|
|
68
|
+
target = AnnotationTarget.find(@file_parser, @options, model_class_name: @model_class_name)
|
|
69
|
+
_const, line_number = target if target
|
|
71
70
|
|
|
72
71
|
if line_number
|
|
73
72
|
annotation_position = if @finder.annotation_start < line_number
|
|
@@ -4,7 +4,9 @@ module AnnotateRb
|
|
|
4
4
|
module ModelAnnotator
|
|
5
5
|
module FileParser
|
|
6
6
|
autoload :AnnotationFinder, "annotate_rb/model_annotator/file_parser/annotation_finder"
|
|
7
|
+
autoload :AnnotationTarget, "annotate_rb/model_annotator/file_parser/annotation_target"
|
|
7
8
|
autoload :CustomParser, "annotate_rb/model_annotator/file_parser/custom_parser"
|
|
9
|
+
autoload :MagicComment, "annotate_rb/model_annotator/file_parser/magic_comment"
|
|
8
10
|
autoload :ParsedFile, "annotate_rb/model_annotator/file_parser/parsed_file"
|
|
9
11
|
autoload :ParsedFileResult, "annotate_rb/model_annotator/file_parser/parsed_file_result"
|
|
10
12
|
autoload :YmlParser, "annotate_rb/model_annotator/file_parser/yml_parser"
|
|
@@ -45,16 +45,27 @@ module AnnotateRb
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
comment_info = ""
|
|
49
|
+
if options[:show_indexes_comments]
|
|
50
|
+
value = index.try(:comment).try(:to_s)
|
|
51
|
+
comment_info = if value.present?
|
|
52
|
+
" COMMENT #{value}"
|
|
53
|
+
else
|
|
54
|
+
""
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
48
58
|
# standard:disable Lint/FormatParameterMismatch
|
|
49
59
|
sprintf(
|
|
50
|
-
"# %-#{max_size}.#{max_size}s %s%s%s%s%s%s",
|
|
60
|
+
"# %-#{max_size}.#{max_size}s %s%s%s%s%s%s%s",
|
|
51
61
|
index.name,
|
|
52
62
|
"(#{columns_info.join(",")})",
|
|
53
63
|
include_info,
|
|
54
64
|
unique_info,
|
|
55
65
|
nulls_not_distinct_info,
|
|
56
66
|
where_info,
|
|
57
|
-
using_info
|
|
67
|
+
using_info,
|
|
68
|
+
comment_info
|
|
58
69
|
).rstrip
|
|
59
70
|
# standard:enable Lint/FormatParameterMismatch
|
|
60
71
|
end
|
|
@@ -92,13 +103,24 @@ module AnnotateRb
|
|
|
92
103
|
end
|
|
93
104
|
end
|
|
94
105
|
|
|
106
|
+
comment_info = ""
|
|
107
|
+
if options[:show_indexes_comments]
|
|
108
|
+
value = index.try(:comment).try(:to_s)
|
|
109
|
+
comment_info = if value.present?
|
|
110
|
+
" _comment_ #{value}"
|
|
111
|
+
else
|
|
112
|
+
""
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
95
116
|
details = sprintf(
|
|
96
|
-
"%s%s%s%s%s",
|
|
117
|
+
"%s%s%s%s%s%s",
|
|
97
118
|
include_info,
|
|
98
119
|
unique_info,
|
|
99
120
|
nulls_not_distinct_info,
|
|
100
121
|
where_info,
|
|
101
|
-
using_info
|
|
122
|
+
using_info,
|
|
123
|
+
comment_info
|
|
102
124
|
).strip
|
|
103
125
|
details = " (#{details})" unless details.blank?
|
|
104
126
|
|
|
@@ -219,6 +219,23 @@ module AnnotateRb
|
|
|
219
219
|
]
|
|
220
220
|
end
|
|
221
221
|
|
|
222
|
+
def enum_types
|
|
223
|
+
@enum_types ||=
|
|
224
|
+
if connection.respond_to?(:enum_types)
|
|
225
|
+
begin
|
|
226
|
+
# enum values may be a String or an Array depending on the Rails version.
|
|
227
|
+
# See: https://github.com/rails/rails/pull/54141
|
|
228
|
+
connection.enum_types.map do |name, values|
|
|
229
|
+
[name, values.is_a?(Array) ? values : values.to_s.split(",")]
|
|
230
|
+
end
|
|
231
|
+
rescue ActiveRecord::StatementInvalid
|
|
232
|
+
[]
|
|
233
|
+
end
|
|
234
|
+
else
|
|
235
|
+
[]
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
222
239
|
def migration_version
|
|
223
240
|
return 0 unless @options[:include_version]
|
|
224
241
|
|
|
@@ -229,7 +246,14 @@ module AnnotateRb
|
|
|
229
246
|
|
|
230
247
|
if @options.get_state(cache_key).nil?
|
|
231
248
|
migration_version = begin
|
|
232
|
-
|
|
249
|
+
# Rails 7.1+ moved migration_context from ConnectionAdapter to ConnectionPool.
|
|
250
|
+
# ConnectionAdapter#migration_context was removed in Rails 7.2.
|
|
251
|
+
# See: https://github.com/rails/rails/pull/51162
|
|
252
|
+
if connection.pool.respond_to?(:migration_context)
|
|
253
|
+
connection.pool.migration_context.current_version
|
|
254
|
+
else
|
|
255
|
+
connection.migration_context.current_version
|
|
256
|
+
end
|
|
233
257
|
rescue
|
|
234
258
|
0
|
|
235
259
|
end
|
|
@@ -42,13 +42,14 @@ module AnnotateRb
|
|
|
42
42
|
klass.reset_column_information
|
|
43
43
|
annotation = Annotation::AnnotationBuilder.new(klass, @options).build
|
|
44
44
|
model_name = klass.name.underscore
|
|
45
|
+
model_class_name = klass.name.demodulize
|
|
45
46
|
|
|
46
47
|
# In multi-database configurations, it is possible for different models to have the same table name but live
|
|
47
48
|
# in different databases. Here we are opting to use the table name to find related files only when the model
|
|
48
49
|
# is using the primary connection.
|
|
49
50
|
table_name = klass.table_name if klass.connection_specification_name == ActiveRecord::Base.name
|
|
50
51
|
|
|
51
|
-
model_instruction = SingleFileAnnotatorInstruction.new(file, annotation, :position_in_class, @options)
|
|
52
|
+
model_instruction = SingleFileAnnotatorInstruction.new(file, annotation, :position_in_class, @options, model_class_name: model_class_name)
|
|
52
53
|
instructions << model_instruction
|
|
53
54
|
|
|
54
55
|
related_files = RelatedFilesListBuilder.new(file, model_name, table_name, @options).build
|
|
@@ -5,7 +5,7 @@ module AnnotateRb
|
|
|
5
5
|
class SingleFileAnnotator
|
|
6
6
|
class << self
|
|
7
7
|
def call_with_instructions(instruction)
|
|
8
|
-
call(instruction.file, instruction.annotation, instruction.position, instruction.options)
|
|
8
|
+
call(instruction.file, instruction.annotation, instruction.position, instruction.options, model_class_name: instruction.model_class_name)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# Add a schema block to a file. If the file already contains
|
|
@@ -21,14 +21,14 @@ module AnnotateRb
|
|
|
21
21
|
# :position_in_*<Symbol>:: where to place the annotated section in fixture or model file,
|
|
22
22
|
# :before, :top, :after or :bottom. Default is :before.
|
|
23
23
|
#
|
|
24
|
-
def call(file_name, annotation, annotation_position, options)
|
|
24
|
+
def call(file_name, annotation, annotation_position, options, model_class_name: nil)
|
|
25
25
|
return false unless File.exist?(file_name)
|
|
26
26
|
old_content = File.read(file_name)
|
|
27
27
|
|
|
28
28
|
parser_klass = FileToParserMapper.map(file_name)
|
|
29
29
|
|
|
30
30
|
begin
|
|
31
|
-
parsed_file = FileParser::ParsedFile.new(old_content, annotation, parser_klass, options).parse
|
|
31
|
+
parsed_file = FileParser::ParsedFile.new(old_content, annotation, parser_klass, options, model_class_name: model_class_name).parse
|
|
32
32
|
rescue FileParser::AnnotationFinder::MalformedAnnotation => e
|
|
33
33
|
warn "Unable to process #{file_name}: #{e.message}"
|
|
34
34
|
warn "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
|
@@ -41,9 +41,9 @@ module AnnotateRb
|
|
|
41
41
|
abort "AnnotateRb error. #{file_name} needs to be updated, but annotaterb was run with `--frozen`." if options[:frozen]
|
|
42
42
|
|
|
43
43
|
updated_file_content = if !parsed_file.has_annotations?
|
|
44
|
-
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, parser_klass, parsed_file, options).generate
|
|
44
|
+
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, parser_klass, parsed_file, options, model_class_name: model_class_name).generate
|
|
45
45
|
elsif options[:force]
|
|
46
|
-
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, parser_klass, parsed_file, options).generate
|
|
46
|
+
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, parser_klass, parsed_file, options, model_class_name: model_class_name).generate
|
|
47
47
|
else
|
|
48
48
|
AnnotatedFile::Updater.new(old_content, annotation, annotation_position, parsed_file, options).update
|
|
49
49
|
end
|
|
@@ -4,14 +4,15 @@ module AnnotateRb
|
|
|
4
4
|
module ModelAnnotator
|
|
5
5
|
# A plain old Ruby object (PORO) that contains all necessary information for SingleFileAnnotator
|
|
6
6
|
class SingleFileAnnotatorInstruction
|
|
7
|
-
def initialize(file, annotation, position, options)
|
|
7
|
+
def initialize(file, annotation, position, options, model_class_name: nil)
|
|
8
8
|
@file = file # Path to file
|
|
9
9
|
@annotation = annotation # Annotation string
|
|
10
10
|
@position = position # Position in the file where to write the annotation to
|
|
11
11
|
@options = options
|
|
12
|
+
@model_class_name = model_class_name # Short class name; set for the model file itself, nil for related files
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
attr_reader :file, :annotation, :position, :options
|
|
15
|
+
attr_reader :file, :annotation, :position, :options, :model_class_name
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
18
|
end
|
|
@@ -49,7 +49,7 @@ module AnnotateRb
|
|
|
49
49
|
|
|
50
50
|
# once we have the filepath_relative_to_root_dir, we need to see if it
|
|
51
51
|
# falls within one of our Zeitwerk "collapsed" paths.
|
|
52
|
-
if loader.collapse.any? { |path| path.include?(root_dir) && file.include?(path.split(root_dir)[1]) }
|
|
52
|
+
if loader.collapse.any? { |path| path.include?(root_dir) && @file.include?(path.split(root_dir)[1]) }
|
|
53
53
|
# if the file is within a collapsed path, we then need to, for each
|
|
54
54
|
# collapsed path, remove the root dir
|
|
55
55
|
collapsed = loader.collapse.map { |path| path.split(root_dir)[1].sub(/^\//, "") }.to_set
|
|
@@ -27,6 +27,7 @@ module AnnotateRb
|
|
|
27
27
|
autoload :FileParser, "annotate_rb/model_annotator/file_parser"
|
|
28
28
|
autoload :ZeitwerkClassGetter, "annotate_rb/model_annotator/zeitwerk_class_getter"
|
|
29
29
|
autoload :CheckConstraintAnnotation, "annotate_rb/model_annotator/check_constraint_annotation"
|
|
30
|
+
autoload :EnumAnnotation, "annotate_rb/model_annotator/enum_annotation"
|
|
30
31
|
autoload :FileToParserMapper, "annotate_rb/model_annotator/file_to_parser_mapper"
|
|
31
32
|
autoload :Components, "annotate_rb/model_annotator/components"
|
|
32
33
|
autoload :Annotation, "annotate_rb/model_annotator/annotation"
|
data/lib/annotate_rb/options.rb
CHANGED
|
@@ -47,8 +47,10 @@ module AnnotateRb
|
|
|
47
47
|
include_version: false, # ModelAnnotator
|
|
48
48
|
show_complete_foreign_keys: false, # ModelAnnotator
|
|
49
49
|
show_check_constraints: false, # ModelAnnotator
|
|
50
|
+
show_enums: false, # ModelAnnotator
|
|
50
51
|
show_foreign_keys: true, # ModelAnnotator
|
|
51
52
|
show_indexes: true, # ModelAnnotator
|
|
53
|
+
show_indexes_comments: false, # ModelAnnotator
|
|
52
54
|
show_indexes_include: false, # ModelAnnotator
|
|
53
55
|
show_virtual_columns: false, # ModelAnnotator
|
|
54
56
|
simple_indexes: false, # ModelAnnotator
|
|
@@ -78,10 +80,10 @@ module AnnotateRb
|
|
|
78
80
|
ignore_columns: nil, # ModelAnnotator
|
|
79
81
|
ignore_multi_database_name: false, # ModelAnnotator
|
|
80
82
|
ignore_routes: nil, # RouteAnnotator
|
|
81
|
-
ignore_unknown_models: false, # ModelAnnotator
|
|
82
83
|
models: true, # Core
|
|
83
84
|
routes: false, # Core
|
|
84
85
|
skip_on_db_migrate: false, # Core
|
|
86
|
+
auto_annotate_routes_after_migrate: false, # Core
|
|
85
87
|
target_action: :do_annotations, # Core; Possible values: :do_annotations, :remove_annotations
|
|
86
88
|
wrapper: nil, # ModelAnnotator, RouteAnnotator
|
|
87
89
|
wrapper_close: nil, # ModelAnnotator, RouteAnnotator
|
|
@@ -120,9 +122,11 @@ module AnnotateRb
|
|
|
120
122
|
:ignore_unknown_models,
|
|
121
123
|
:include_version,
|
|
122
124
|
:show_check_constraints,
|
|
125
|
+
:show_enums,
|
|
123
126
|
:show_complete_foreign_keys,
|
|
124
127
|
:show_foreign_keys,
|
|
125
128
|
:show_indexes,
|
|
129
|
+
:show_indexes_comments,
|
|
126
130
|
:show_indexes_include,
|
|
127
131
|
:simple_indexes,
|
|
128
132
|
:sort,
|
|
@@ -143,11 +147,11 @@ module AnnotateRb
|
|
|
143
147
|
:timestamp_columns,
|
|
144
148
|
:ignore_columns,
|
|
145
149
|
:ignore_routes,
|
|
146
|
-
:ignore_unknown_models,
|
|
147
150
|
:ignore_multi_database_name,
|
|
148
151
|
:models,
|
|
149
152
|
:routes,
|
|
150
153
|
:skip_on_db_migrate,
|
|
154
|
+
:auto_annotate_routes_after_migrate,
|
|
151
155
|
:target_action,
|
|
152
156
|
:wrapper,
|
|
153
157
|
:wrapper_close,
|
data/lib/annotate_rb/parser.rb
CHANGED
|
@@ -22,7 +22,9 @@ module AnnotateRb
|
|
|
22
22
|
exit: false
|
|
23
23
|
}.freeze
|
|
24
24
|
|
|
25
|
-
ANNOTATION_POSITIONS = %w[before top after bottom].freeze
|
|
25
|
+
ANNOTATION_POSITIONS = %w[before top after bottom before_doc].freeze
|
|
26
|
+
AFTER_POSITIONS = %w[after bottom].freeze
|
|
27
|
+
DOC_AWARE_POSITIONS = %w[before_doc].freeze
|
|
26
28
|
FILE_TYPE_POSITIONS = %w[position_in_class position_in_factory position_in_fixture position_in_test position_in_routes position_in_serializer].freeze
|
|
27
29
|
EXCLUSION_LIST = %w[tests fixtures factories serializers].freeze
|
|
28
30
|
FORMAT_TYPES = %w[bare rdoc yard markdown].freeze
|
|
@@ -297,9 +299,9 @@ module AnnotateRb
|
|
|
297
299
|
has_set_position = {}
|
|
298
300
|
|
|
299
301
|
option_parser.on("-p",
|
|
300
|
-
"--position [before|top|after|bottom]",
|
|
302
|
+
"--position [before|top|after|bottom|before_doc]",
|
|
301
303
|
ANNOTATION_POSITIONS,
|
|
302
|
-
"Place the annotations at the top (before) or the
|
|
304
|
+
"Place the annotations at the top (before), bottom (after), or above the class documentation (before_doc) of the model/test/fixture/factory/route/serializer file(s)") do |position|
|
|
303
305
|
@options[:position] = position
|
|
304
306
|
|
|
305
307
|
FILE_TYPE_POSITIONS.each do |key|
|
|
@@ -308,9 +310,9 @@ module AnnotateRb
|
|
|
308
310
|
end
|
|
309
311
|
|
|
310
312
|
option_parser.on("--pc",
|
|
311
|
-
"--position-in-class [before|top|after|bottom]",
|
|
313
|
+
"--position-in-class [before|top|after|bottom|before_doc]",
|
|
312
314
|
ANNOTATION_POSITIONS,
|
|
313
|
-
"Place the annotations at the top (before) or the
|
|
315
|
+
"Place the annotations at the top (before), bottom (after), or above the class documentation (before_doc) of the model file") do |position_in_class|
|
|
314
316
|
@options[:position_in_class] = position_in_class
|
|
315
317
|
has_set_position["position_in_class"] = true
|
|
316
318
|
end
|
|
@@ -75,7 +75,7 @@ module AnnotateRb
|
|
|
75
75
|
header_position = 0
|
|
76
76
|
|
|
77
77
|
content.split(/\n/, -1).each_with_index do |line, line_number|
|
|
78
|
-
if mode == :header && line !~ /\s*#/
|
|
78
|
+
if mode == :header && line !~ /\A\s*#/
|
|
79
79
|
mode = :content
|
|
80
80
|
real_content << line unless line.blank?
|
|
81
81
|
elsif mode == :content
|
|
@@ -70,7 +70,7 @@ module AnnotateRb
|
|
|
70
70
|
header_position = 0
|
|
71
71
|
|
|
72
72
|
content.split(/\n/, -1).each_with_index do |line, line_number|
|
|
73
|
-
if mode == :header && line !~ /\s*#/
|
|
73
|
+
if mode == :header && line !~ /\A\s*#/
|
|
74
74
|
mode = :content
|
|
75
75
|
real_content << line unless line.blank?
|
|
76
76
|
elsif mode == :content
|
data/lib/annotate_rb/runner.rb
CHANGED
|
@@ -5,10 +5,18 @@ module AnnotateRb
|
|
|
5
5
|
class << self
|
|
6
6
|
attr_reader :runner
|
|
7
7
|
|
|
8
|
-
def
|
|
8
|
+
def run_after_migration
|
|
9
|
+
config_file_options = ConfigLoader.load_config
|
|
10
|
+
options = Options.from(config_file_options)
|
|
11
|
+
|
|
12
|
+
commands = ["models", *(options[:auto_annotate_routes_after_migrate] ? ["routes"] : [])]
|
|
13
|
+
commands.each { |cmd| run([cmd], config_file_options: config_file_options) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run(args, config_file_options: nil)
|
|
9
17
|
self.runner = new
|
|
10
18
|
|
|
11
|
-
runner.run(args)
|
|
19
|
+
runner.run(args, config_file_options: config_file_options)
|
|
12
20
|
|
|
13
21
|
self.runner = nil
|
|
14
22
|
end
|
|
@@ -22,14 +30,14 @@ module AnnotateRb
|
|
|
22
30
|
attr_writer :runner
|
|
23
31
|
end
|
|
24
32
|
|
|
25
|
-
def run(args)
|
|
33
|
+
def run(args, config_file_options: nil)
|
|
26
34
|
parser = Parser.new(args, {})
|
|
27
35
|
|
|
28
36
|
parsed_options = parser.parse
|
|
29
37
|
remaining_args = parser.remaining_args
|
|
30
38
|
|
|
31
39
|
AnnotateRb::ConfigFinder.config_path = parsed_options[:config_path] if parsed_options[:config_path]
|
|
32
|
-
config_file_options = ConfigLoader.load_config
|
|
40
|
+
config_file_options = ConfigLoader.load_config if config_file_options.nil?
|
|
33
41
|
options = config_file_options.merge(parsed_options)
|
|
34
42
|
|
|
35
43
|
@options = Options.from(options, {working_args: remaining_args})
|
|
@@ -28,11 +28,16 @@ migration_tasks.each do |task|
|
|
|
28
28
|
next unless Rake::Task.task_defined?(task)
|
|
29
29
|
next if config[:skip_on_db_migrate]
|
|
30
30
|
|
|
31
|
-
Rake::Task[task].enhance do # This block is ran after `task` completes
|
|
32
|
-
|
|
31
|
+
Rake::Task[task].enhance do |current_task| # This block is ran after `task` completes
|
|
32
|
+
# Prefer the top-level task (the one invoked from the CLI, e.g. "db:migrate") so that
|
|
33
|
+
# when sub-tasks chain (e.g. db:migrate:redo invokes db:rollback then db:migrate), we
|
|
34
|
+
# defer the annotation run to after everything completes. Fall back to the currently
|
|
35
|
+
# enhanced task when there is no top-level task (e.g. when the task is invoked
|
|
36
|
+
# programmatically from application code rather than from the Rake CLI).
|
|
37
|
+
task_name = Rake.application.top_level_tasks.last || current_task.name
|
|
33
38
|
|
|
34
39
|
Rake::Task[task_name].enhance do
|
|
35
|
-
::AnnotateRb::Runner.
|
|
40
|
+
::AnnotateRb::Runner.run_after_migration
|
|
36
41
|
end
|
|
37
42
|
end
|
|
38
43
|
end
|
|
@@ -6,7 +6,10 @@ module AnnotateRb
|
|
|
6
6
|
module Generators
|
|
7
7
|
class UpdateConfigGenerator < ::Rails::Generators::Base
|
|
8
8
|
def generate_config
|
|
9
|
-
|
|
9
|
+
parsed_options = AnnotateRb::Parser.parse(ARGV, {})
|
|
10
|
+
AnnotateRb::ConfigFinder.config_path = parsed_options[:config_path] if parsed_options[:config_path]
|
|
11
|
+
|
|
12
|
+
insert_into_file ::AnnotateRb::ConfigFinder.find_project_dotfile do
|
|
10
13
|
::AnnotateRb::ConfigGenerator.unset_config_defaults
|
|
11
14
|
end
|
|
12
15
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: annotaterb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew W. Lee
|
|
@@ -90,10 +90,16 @@ files:
|
|
|
90
90
|
- lib/annotate_rb/model_annotator/column_annotation/default_value_builder.rb
|
|
91
91
|
- lib/annotate_rb/model_annotator/column_annotation/type_builder.rb
|
|
92
92
|
- lib/annotate_rb/model_annotator/components.rb
|
|
93
|
+
- lib/annotate_rb/model_annotator/enum_annotation.rb
|
|
94
|
+
- lib/annotate_rb/model_annotator/enum_annotation/annotation.rb
|
|
95
|
+
- lib/annotate_rb/model_annotator/enum_annotation/annotation_builder.rb
|
|
96
|
+
- lib/annotate_rb/model_annotator/enum_annotation/enum_component.rb
|
|
93
97
|
- lib/annotate_rb/model_annotator/file_name_resolver.rb
|
|
94
98
|
- lib/annotate_rb/model_annotator/file_parser.rb
|
|
95
99
|
- lib/annotate_rb/model_annotator/file_parser/annotation_finder.rb
|
|
100
|
+
- lib/annotate_rb/model_annotator/file_parser/annotation_target.rb
|
|
96
101
|
- lib/annotate_rb/model_annotator/file_parser/custom_parser.rb
|
|
102
|
+
- lib/annotate_rb/model_annotator/file_parser/magic_comment.rb
|
|
97
103
|
- lib/annotate_rb/model_annotator/file_parser/parsed_file.rb
|
|
98
104
|
- lib/annotate_rb/model_annotator/file_parser/parsed_file_result.rb
|
|
99
105
|
- lib/annotate_rb/model_annotator/file_parser/yml_parser.rb
|
|
@@ -164,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
164
170
|
- !ruby/object:Gem::Version
|
|
165
171
|
version: '0'
|
|
166
172
|
requirements: []
|
|
167
|
-
rubygems_version:
|
|
173
|
+
rubygems_version: 3.6.9
|
|
168
174
|
specification_version: 4
|
|
169
175
|
summary: A gem for generating annotations for Rails projects.
|
|
170
176
|
test_files: []
|