annotaterb 4.5.0 → 4.6.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 +28 -0
- data/VERSION +1 -1
- data/lib/annotate_rb/eager_loader.rb +6 -2
- data/lib/annotate_rb/model_annotator/annotated_file/generator.rb +50 -7
- data/lib/annotate_rb/model_annotator/annotated_file/updater.rb +9 -9
- data/lib/annotate_rb/model_annotator/file_name_resolver.rb +5 -0
- data/lib/annotate_rb/model_annotator/file_parser/annotation_finder.rb +103 -0
- data/lib/annotate_rb/model_annotator/file_parser/custom_parser.rb +217 -0
- data/lib/annotate_rb/model_annotator/file_parser/parsed_file.rb +94 -0
- data/lib/annotate_rb/model_annotator/file_parser/parsed_file_result.rb +54 -0
- data/lib/annotate_rb/model_annotator/file_parser.rb +4 -1
- data/lib/annotate_rb/model_annotator/model_class_getter.rb +7 -0
- data/lib/annotate_rb/model_annotator/model_wrapper.rb +10 -1
- data/lib/annotate_rb/model_annotator/related_files_list_builder.rb +3 -3
- data/lib/annotate_rb/model_annotator/single_file_annotation_remover.rb +10 -12
- data/lib/annotate_rb/model_annotator/single_file_annotator.rb +14 -6
- data/lib/annotate_rb/model_annotator/zeitwerk_class_getter.rb +113 -0
- data/lib/annotate_rb/model_annotator.rb +1 -2
- data/lib/annotate_rb/tasks/annotate_models_migrate.rake +5 -0
- metadata +8 -6
- data/lib/annotate_rb/model_annotator/annotation_pattern_generator.rb +0 -19
- data/lib/annotate_rb/model_annotator/file_components.rb +0 -81
- data/lib/annotate_rb/model_annotator/file_parser/magic_comment_parser.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c9e326fa73164a6a2e43edc402e6990cfbb5d953ac5fd1ccf8c71fa6d358ee1
|
4
|
+
data.tar.gz: dabf1afd54a8dec7a96ed8c259ca84e0a6085618713be78f43f075a51a22db7f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12e277c2d09e37e1001e64b0f08542ff03981bf5bbe37e86ed3dca01b1ec52c5b1805ce6f5cb1a8e5b25c3b9d655cb2345d0c9d878ecf4ae6bbfd265870445ac
|
7
|
+
data.tar.gz: 88d7c9ba17c3abbf38e635365314d1c4b372e67ec5ef0dcba9f7f4f5b6ef90d0e285a2150887127fa75f847a1ee2e8e4c7a30548729a4f903cf71f42be793177
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,33 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v4.5.0](https://github.com/drwl/annotaterb/tree/v4.5.0) (2024-02-08)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/drwl/annotaterb/compare/v4.4.1...v4.5.0)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Add an automated way to migrate from the old annotate gem [\#73](https://github.com/drwl/annotaterb/issues/73)
|
10
|
+
- Default array value is double-quoted/escaped [\#57](https://github.com/drwl/annotaterb/issues/57)
|
11
|
+
|
12
|
+
**Merged pull requests:**
|
13
|
+
|
14
|
+
- Bump version to v4.5.0 [\#79](https://github.com/drwl/annotaterb/pull/79) ([drwl](https://github.com/drwl))
|
15
|
+
- Update README on the new Rails generator commands [\#78](https://github.com/drwl/annotaterb/pull/78) ([drwl](https://github.com/drwl))
|
16
|
+
- Bump github/codeql-action from 2 to 3 [\#77](https://github.com/drwl/annotaterb/pull/77) ([dependabot[bot]](https://github.com/apps/dependabot))
|
17
|
+
- Add command to generate a configuration file [\#76](https://github.com/drwl/annotaterb/pull/76) ([drwl](https://github.com/drwl))
|
18
|
+
- Bump actions/checkout from 3 to 4 [\#75](https://github.com/drwl/annotaterb/pull/75) ([dependabot[bot]](https://github.com/apps/dependabot))
|
19
|
+
- CI: Configure dependabot to update GH Actions [\#74](https://github.com/drwl/annotaterb/pull/74) ([olleolleolle](https://github.com/olleolleolle))
|
20
|
+
- Refactor `FileBuilder` and `MagicCommentParser` [\#71](https://github.com/drwl/annotaterb/pull/71) ([drwl](https://github.com/drwl))
|
21
|
+
- Test running annotations after a migration [\#70](https://github.com/drwl/annotaterb/pull/70) ([drwl](https://github.com/drwl))
|
22
|
+
- Add integration test for rake task installer [\#69](https://github.com/drwl/annotaterb/pull/69) ([drwl](https://github.com/drwl))
|
23
|
+
- Add integration test for annotating routes [\#68](https://github.com/drwl/annotaterb/pull/68) ([drwl](https://github.com/drwl))
|
24
|
+
- Remove optional args [\#67](https://github.com/drwl/annotaterb/pull/67) ([drwl](https://github.com/drwl))
|
25
|
+
- Remove optional arg from `AnnotationPatternGenerator` [\#66](https://github.com/drwl/annotaterb/pull/66) ([drwl](https://github.com/drwl))
|
26
|
+
- Remove `ARGV` use during runtime [\#65](https://github.com/drwl/annotaterb/pull/65) ([drwl](https://github.com/drwl))
|
27
|
+
- Add integration test for annotating a singular file [\#64](https://github.com/drwl/annotaterb/pull/64) ([drwl](https://github.com/drwl))
|
28
|
+
- Generate changelog for v4.4.1 [\#63](https://github.com/drwl/annotaterb/pull/63) ([drwl](https://github.com/drwl))
|
29
|
+
- Add support for factory\_bot's default suffixed pattern [\#59](https://github.com/drwl/annotaterb/pull/59) ([drwl](https://github.com/drwl))
|
30
|
+
|
3
31
|
## [v4.4.1](https://github.com/drwl/annotaterb/tree/v4.4.1) (2023-09-11)
|
4
32
|
|
5
33
|
[Full Changelog](https://github.com/drwl/annotaterb/compare/v4.4.0...v4.4.1)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.
|
1
|
+
4.6.0
|
@@ -8,8 +8,12 @@ module AnnotateRb
|
|
8
8
|
options[:require].count > 0 && options[:require].each { |path| require path }
|
9
9
|
|
10
10
|
if defined?(::Rails::Application)
|
11
|
-
|
12
|
-
|
11
|
+
if defined?(::Zeitwerk)
|
12
|
+
# Delegate to Zeitwerk to load stuff as needed
|
13
|
+
else
|
14
|
+
klass = ::Rails::Application.send(:subclasses).first
|
15
|
+
klass.eager_load!
|
16
|
+
end
|
13
17
|
else
|
14
18
|
options[:model_dir].each do |dir|
|
15
19
|
::Rake::FileList["#{dir}/**/*.rb"].each do |fname|
|
@@ -5,29 +5,72 @@ module AnnotateRb
|
|
5
5
|
module AnnotatedFile
|
6
6
|
# Generates the file with fresh annotations
|
7
7
|
class Generator
|
8
|
-
def initialize(
|
9
|
-
@file_components = file_components
|
8
|
+
def initialize(file_content, new_annotations, annotation_position, options)
|
10
9
|
@annotation_position = annotation_position
|
11
10
|
@options = options
|
12
11
|
|
13
|
-
@new_wrapped_annotations = wrapped_content(
|
12
|
+
@new_wrapped_annotations = wrapped_content(new_annotations)
|
13
|
+
|
14
|
+
@new_annotations = new_annotations
|
15
|
+
@file_content = file_content
|
16
|
+
|
17
|
+
@parsed_file = FileParser::ParsedFile.new(@file_content, @new_annotations, options).parse
|
14
18
|
end
|
15
19
|
|
20
|
+
# @return [String] Returns the annotated file content to be written back to a file
|
16
21
|
def generate
|
17
22
|
# Need to keep `.to_s` for now since the it can be either a String or Symbol
|
18
23
|
annotation_write_position = @options[@annotation_position].to_s
|
19
24
|
|
25
|
+
# New method: first remove annotations
|
26
|
+
content_without_annotations = if @parsed_file.has_annotations?
|
27
|
+
@file_content.sub(@parsed_file.annotations_with_whitespace, "")
|
28
|
+
elsif @options[:force]
|
29
|
+
@file_content.sub(@parsed_file.annotations_with_whitespace, "")
|
30
|
+
else
|
31
|
+
@file_content
|
32
|
+
end
|
33
|
+
|
34
|
+
# We need to get class start and class end depending on the position
|
35
|
+
parsed = FileParser::CustomParser.new(content_without_annotations, "", 0).tap(&:parse)
|
36
|
+
|
20
37
|
_content = if %w[after bottom].include?(annotation_write_position)
|
21
|
-
|
22
|
-
elsif @file_components.magic_comments.empty?
|
23
|
-
@file_components.magic_comments + @new_wrapped_annotations + @file_components.pure_file_content.lstrip
|
38
|
+
content_annotated_after(parsed, content_without_annotations)
|
24
39
|
else
|
25
|
-
|
40
|
+
content_annotated_before(parsed, content_without_annotations, annotation_write_position)
|
26
41
|
end
|
27
42
|
end
|
28
43
|
|
29
44
|
private
|
30
45
|
|
46
|
+
def content_annotated_before(parsed, content_without_annotations, write_position)
|
47
|
+
same_write_position = @parsed_file.has_annotations? && @parsed_file.annotation_position.to_s == write_position
|
48
|
+
|
49
|
+
# Could error if there's no class or module declaration
|
50
|
+
_constant_name, line_number_before = parsed.starts.first
|
51
|
+
|
52
|
+
content_with_annotations_written_before = []
|
53
|
+
content_with_annotations_written_before << content_without_annotations.lines[0...line_number_before]
|
54
|
+
content_with_annotations_written_before << $/ if @parsed_file.has_leading_whitespace? && same_write_position
|
55
|
+
content_with_annotations_written_before << @new_wrapped_annotations.lines
|
56
|
+
content_with_annotations_written_before << $/ if @parsed_file.has_trailing_whitespace? && same_write_position
|
57
|
+
content_with_annotations_written_before << content_without_annotations.lines[line_number_before..]
|
58
|
+
|
59
|
+
content_with_annotations_written_before.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def content_annotated_after(parsed, content_without_annotations)
|
63
|
+
_constant_name, line_number_after = parsed.ends.last
|
64
|
+
|
65
|
+
content_with_annotations_written_after = []
|
66
|
+
content_with_annotations_written_after << content_without_annotations.lines[0..line_number_after]
|
67
|
+
content_with_annotations_written_after << $/
|
68
|
+
content_with_annotations_written_after << @new_wrapped_annotations.lines
|
69
|
+
content_with_annotations_written_after << content_without_annotations.lines[(line_number_after + 1)..]
|
70
|
+
|
71
|
+
content_with_annotations_written_after.join
|
72
|
+
end
|
73
|
+
|
31
74
|
def wrapped_content(content)
|
32
75
|
wrapper_open = if @options[:wrapper_open]
|
33
76
|
"# #{@options[:wrapper_open]}\n"
|
@@ -5,22 +5,22 @@ module AnnotateRb
|
|
5
5
|
module AnnotatedFile
|
6
6
|
# Updates existing annotations
|
7
7
|
class Updater
|
8
|
-
def initialize(
|
9
|
-
@file_components = file_components
|
10
|
-
@annotation_position = annotation_position
|
8
|
+
def initialize(file_content, new_annotations, _annotation_position, options)
|
11
9
|
@options = options
|
12
10
|
|
13
|
-
@
|
11
|
+
@new_annotations = new_annotations
|
12
|
+
@file_content = file_content
|
13
|
+
|
14
|
+
@parsed_file = FileParser::ParsedFile.new(@file_content, @new_annotations, options).parse
|
14
15
|
end
|
15
16
|
|
17
|
+
# @return [String] Returns the annotated file content to be written back to a file
|
16
18
|
def update
|
17
|
-
return "" if !@
|
18
|
-
|
19
|
-
annotation_pattern = AnnotationPatternGenerator.call(@options)
|
19
|
+
return "" if !@parsed_file.has_annotations?
|
20
20
|
|
21
|
-
new_annotation = @
|
21
|
+
new_annotation = wrapped_content(@new_annotations)
|
22
22
|
|
23
|
-
_content = @
|
23
|
+
_content = @file_content.sub(@parsed_file.annotations, new_annotation)
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
@@ -5,8 +5,13 @@ module AnnotateRb
|
|
5
5
|
class FileNameResolver
|
6
6
|
class << self
|
7
7
|
def call(filename_template, model_name, table_name)
|
8
|
+
# e.g. with a model file name like "app/models/collapsed/example/test_model.rb"
|
9
|
+
# and using a collapsed `model_name` such as "collapsed/test_model"
|
10
|
+
model_name_without_namespace = model_name.split("/").last
|
11
|
+
|
8
12
|
filename_template
|
9
13
|
.gsub("%MODEL_NAME%", model_name)
|
14
|
+
.gsub("%MODEL_NAME_WITHOUT_NS%", model_name_without_namespace)
|
10
15
|
.gsub("%PLURALIZED_MODEL_NAME%", model_name.pluralize)
|
11
16
|
.gsub("%TABLE_NAME%", table_name || model_name.pluralize)
|
12
17
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module FileParser
|
6
|
+
class AnnotationFinder
|
7
|
+
COMPAT_PREFIX = "== Schema Info"
|
8
|
+
COMPAT_PREFIX_MD = "## Schema Info"
|
9
|
+
DEFAULT_ANNOTATION_ENDING = "#"
|
10
|
+
|
11
|
+
class MalformedAnnotation < StandardError; end
|
12
|
+
|
13
|
+
class NoAnnotationFound < StandardError; end
|
14
|
+
|
15
|
+
# Returns the line index (not the line number) that the annotation starts.
|
16
|
+
attr_reader :annotation_start
|
17
|
+
# Returns the line index (not the line number) that the annotation ends, inclusive.
|
18
|
+
attr_reader :annotation_end
|
19
|
+
|
20
|
+
attr_reader :parser
|
21
|
+
|
22
|
+
def initialize(content, wrapper_open, wrapper_close)
|
23
|
+
@content = content
|
24
|
+
@wrapper_open = wrapper_open
|
25
|
+
@wrapper_close = wrapper_close
|
26
|
+
@annotation_start = nil
|
27
|
+
@annotation_end = nil
|
28
|
+
@parser = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Find the annotation's line start and line end
|
32
|
+
def run
|
33
|
+
# CustomParser returns line numbers as 0-indexed
|
34
|
+
@parser = FileParser::CustomParser.new(@content, "", 0).tap(&:parse)
|
35
|
+
comments = @parser.comments
|
36
|
+
|
37
|
+
start = comments.find_index { |comment, _| comment.include?(COMPAT_PREFIX) || comment.include?(COMPAT_PREFIX_MD) }
|
38
|
+
raise NoAnnotationFound if start.nil? # Stop execution because we did not find
|
39
|
+
|
40
|
+
if @wrapper_open
|
41
|
+
prev_comment, _prev_line_number = comments[start - 1]
|
42
|
+
|
43
|
+
# Change start to the line before if wrapper_open is defined and we find the wrapper open comment
|
44
|
+
if prev_comment&.include?(@wrapper_open)
|
45
|
+
start -= 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Find a contiguous block of comments from the starting point
|
50
|
+
ending = start
|
51
|
+
while ending < comments.size - 1
|
52
|
+
_comment, line_number = comments[ending]
|
53
|
+
_next_comment, next_line_number = comments[ending + 1]
|
54
|
+
|
55
|
+
if next_line_number - line_number == 1
|
56
|
+
ending += 1
|
57
|
+
else
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
raise MalformedAnnotation if start == ending
|
63
|
+
|
64
|
+
if @wrapper_close
|
65
|
+
if comments[ending].first.include?(@wrapper_close)
|
66
|
+
# We can end here because it's the end of the annotation block
|
67
|
+
else
|
68
|
+
# Walk back until we find the end of the annotation comment block or the wrapper close to be flexible
|
69
|
+
# We check if @wrapper_close is a substring because `comments` contains strings with the comment character
|
70
|
+
while ending > start && comments[ending].first != DEFAULT_ANNOTATION_ENDING && !comments[ending].first.include?(@wrapper_close)
|
71
|
+
ending -= 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
else
|
75
|
+
# Walk back until we find the end of the annotation comment block
|
76
|
+
while ending > start && comments[ending].first != DEFAULT_ANNOTATION_ENDING
|
77
|
+
ending -= 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# We want .last because we want the line indexes
|
82
|
+
@annotation_start = comments[start].last
|
83
|
+
@annotation_end = comments[ending].last
|
84
|
+
|
85
|
+
[@annotation_start, @annotation_end]
|
86
|
+
end
|
87
|
+
|
88
|
+
def annotation
|
89
|
+
@annotation ||=
|
90
|
+
begin
|
91
|
+
lines = @content.lines
|
92
|
+
lines[@annotation_start..@annotation_end].join
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if annotations are detected in the file content
|
97
|
+
def annotated?
|
98
|
+
@annotation_start && @annotation_end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
module AnnotateRb
|
6
|
+
module ModelAnnotator
|
7
|
+
module FileParser
|
8
|
+
class CustomParser < Ripper
|
9
|
+
# Overview of Ripper: https://kddnewton.com/2022/02/14/formatting-ruby-part-1.html
|
10
|
+
# Ripper API: https://kddnewton.com/ripper-docs/
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def parse(string)
|
14
|
+
_parser = new(string, "", 0).tap(&:parse)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :comments
|
19
|
+
|
20
|
+
def initialize(input, ...)
|
21
|
+
super
|
22
|
+
@_stack_code_block = []
|
23
|
+
@_input = input
|
24
|
+
@_const_event_map = {}
|
25
|
+
|
26
|
+
@comments = []
|
27
|
+
@block_starts = []
|
28
|
+
@block_ends = []
|
29
|
+
@const_type_map = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def starts
|
33
|
+
@block_starts
|
34
|
+
end
|
35
|
+
|
36
|
+
def ends
|
37
|
+
@block_ends
|
38
|
+
end
|
39
|
+
|
40
|
+
def type_map
|
41
|
+
@const_type_map
|
42
|
+
end
|
43
|
+
|
44
|
+
def on_program(...)
|
45
|
+
{
|
46
|
+
comments: @comments,
|
47
|
+
starts: @block_starts,
|
48
|
+
ends: @block_ends,
|
49
|
+
type_map: @const_type_map
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_const_ref(const)
|
54
|
+
add_event(__method__, const, lineno)
|
55
|
+
@block_starts << [const, lineno]
|
56
|
+
super
|
57
|
+
end
|
58
|
+
|
59
|
+
# Used for `class Foo::User`
|
60
|
+
def on_const_path_ref(_left, const)
|
61
|
+
add_event(__method__, const, lineno)
|
62
|
+
@block_starts << [const, lineno]
|
63
|
+
super
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_module(const, _bodystmt)
|
67
|
+
add_event(__method__, const, lineno)
|
68
|
+
@const_type_map[const] = :module unless @const_type_map[const]
|
69
|
+
@block_ends << [const, lineno]
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_class(const, _superclass, _bodystmt)
|
74
|
+
add_event(__method__, const, lineno)
|
75
|
+
@const_type_map[const] = :class unless @const_type_map[const]
|
76
|
+
@block_ends << [const, lineno]
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
# Gets the `RSpec` opening in:
|
81
|
+
# ```ruby
|
82
|
+
# RSpec.describe "Collapsed::TestModel" do
|
83
|
+
# # Deliberately left empty
|
84
|
+
# end
|
85
|
+
# ```
|
86
|
+
# receiver: "RSpec", operator: ".", method: "describe"
|
87
|
+
def on_command_call(receiver, operator, method, args)
|
88
|
+
add_event(__method__, receiver, lineno)
|
89
|
+
@block_starts << [receiver, lineno]
|
90
|
+
|
91
|
+
# We keep track of blocks using a stack
|
92
|
+
@_stack_code_block << receiver
|
93
|
+
super
|
94
|
+
end
|
95
|
+
|
96
|
+
def on_method_add_block(method, block)
|
97
|
+
# When parsing a line with no explicit receiver, the method will be presented in an Array.
|
98
|
+
# It's not immediately clear why.
|
99
|
+
#
|
100
|
+
# Example:
|
101
|
+
# ```ruby
|
102
|
+
# describe "Collapsed::TestModel" do
|
103
|
+
# # Deliberately left empty
|
104
|
+
# end
|
105
|
+
# ```
|
106
|
+
#
|
107
|
+
# => method = ["describe"]
|
108
|
+
if method.is_a?(Array) && method.size == 1
|
109
|
+
method = method.first
|
110
|
+
end
|
111
|
+
|
112
|
+
add_event(__method__, method, lineno)
|
113
|
+
|
114
|
+
if @_stack_code_block.last == method
|
115
|
+
@block_ends << [method, lineno]
|
116
|
+
@_stack_code_block.pop
|
117
|
+
else
|
118
|
+
@block_starts << [method, lineno]
|
119
|
+
end
|
120
|
+
super
|
121
|
+
end
|
122
|
+
|
123
|
+
def on_method_add_arg(method, args)
|
124
|
+
add_event(__method__, method, lineno)
|
125
|
+
@block_starts << [method, lineno]
|
126
|
+
|
127
|
+
# We keep track of blocks using a stack
|
128
|
+
@_stack_code_block << method
|
129
|
+
super
|
130
|
+
end
|
131
|
+
|
132
|
+
# Gets the `FactoryBot` line in:
|
133
|
+
# ```ruby
|
134
|
+
# FactoryBot.define do
|
135
|
+
# factory :user do
|
136
|
+
# ...
|
137
|
+
# end
|
138
|
+
# end
|
139
|
+
# ```
|
140
|
+
def on_call(receiver, operator, message)
|
141
|
+
# We only want to add the parsed line if the beginning of the Ruby
|
142
|
+
if @block_starts.empty?
|
143
|
+
add_event(__method__, receiver, lineno)
|
144
|
+
@block_starts << [receiver, lineno]
|
145
|
+
end
|
146
|
+
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
# Gets the `factory` block start in:
|
151
|
+
# ```ruby
|
152
|
+
# factory :user, aliases: [:author, :commenter] do
|
153
|
+
# ...
|
154
|
+
# end
|
155
|
+
# ```
|
156
|
+
def on_command(message, args)
|
157
|
+
add_event(__method__, message, lineno)
|
158
|
+
@block_starts << [message, lineno]
|
159
|
+
|
160
|
+
# We keep track of blocks using a stack
|
161
|
+
@_stack_code_block << message
|
162
|
+
super
|
163
|
+
end
|
164
|
+
|
165
|
+
# Matches the `end` in:
|
166
|
+
# ```ruby
|
167
|
+
# factory :user, aliases: [:author, :commenter] do
|
168
|
+
# first_name { "John" }
|
169
|
+
# last_name { "Doe" }
|
170
|
+
# date_of_birth { 18.years.ago }
|
171
|
+
# end
|
172
|
+
# ```
|
173
|
+
def on_do_block(block_var, bodystmt)
|
174
|
+
if block_var.blank? && bodystmt.blank?
|
175
|
+
@block_ends << ["end", lineno]
|
176
|
+
add_event(__method__, "end", lineno)
|
177
|
+
end
|
178
|
+
super
|
179
|
+
end
|
180
|
+
|
181
|
+
def on_embdoc_beg(value)
|
182
|
+
add_event(__method__, value, lineno)
|
183
|
+
@comments << [value.strip, lineno]
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
187
|
+
def on_embdoc_end(value)
|
188
|
+
add_event(__method__, value, lineno)
|
189
|
+
@comments << [value.strip, lineno]
|
190
|
+
super
|
191
|
+
end
|
192
|
+
|
193
|
+
def on_embdoc(value)
|
194
|
+
add_event(__method__, value, lineno)
|
195
|
+
@comments << [value.strip, lineno]
|
196
|
+
super
|
197
|
+
end
|
198
|
+
|
199
|
+
def on_comment(value)
|
200
|
+
add_event(__method__, value, lineno)
|
201
|
+
@comments << [value.strip, lineno]
|
202
|
+
super
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
def add_event(event, const, lineno)
|
208
|
+
if !@_const_event_map[lineno]
|
209
|
+
@_const_event_map[lineno] = []
|
210
|
+
end
|
211
|
+
|
212
|
+
@_const_event_map[lineno] << [const, event]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module FileParser
|
6
|
+
class ParsedFile
|
7
|
+
SKIP_ANNOTATION_STRING = "# -*- SkipSchemaAnnotations"
|
8
|
+
|
9
|
+
def initialize(file_content, new_annotations, options)
|
10
|
+
@file_content = file_content
|
11
|
+
@file_lines = @file_content.lines
|
12
|
+
@new_annotations = new_annotations
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse
|
17
|
+
@finder = AnnotationFinder.new(@file_content, @options[:wrapper_open], @options[:wrapper_close])
|
18
|
+
has_annotations = false
|
19
|
+
|
20
|
+
begin
|
21
|
+
@finder.run
|
22
|
+
has_annotations = @finder.annotated?
|
23
|
+
rescue AnnotationFinder::NoAnnotationFound => _e
|
24
|
+
end
|
25
|
+
|
26
|
+
annotations = if has_annotations
|
27
|
+
@file_lines[(@finder.annotation_start)..(@finder.annotation_end)].join
|
28
|
+
else
|
29
|
+
""
|
30
|
+
end
|
31
|
+
|
32
|
+
@diff = AnnotationDiffGenerator.new(annotations, @new_annotations).generate
|
33
|
+
@file_parser = @finder.parser
|
34
|
+
|
35
|
+
has_skip_string = @file_parser.comments.any? { |comment, _lineno| comment.include?(SKIP_ANNOTATION_STRING) }
|
36
|
+
annotations_changed = @diff.changed?
|
37
|
+
|
38
|
+
has_leading_whitespace = false
|
39
|
+
has_trailing_whitespace = false
|
40
|
+
|
41
|
+
annotations_with_whitespace = if has_annotations
|
42
|
+
begin
|
43
|
+
annotation_start = @finder.annotation_start
|
44
|
+
annotation_end = @finder.annotation_end
|
45
|
+
|
46
|
+
if @file_lines[annotation_start - 1]&.strip&.empty?
|
47
|
+
annotation_start -= 1
|
48
|
+
has_leading_whitespace = true
|
49
|
+
end
|
50
|
+
|
51
|
+
if @file_lines[annotation_end + 1]&.strip&.empty?
|
52
|
+
annotation_end += 1
|
53
|
+
has_trailing_whitespace = true
|
54
|
+
end
|
55
|
+
|
56
|
+
@file_lines[annotation_start..annotation_end].join
|
57
|
+
end
|
58
|
+
else
|
59
|
+
""
|
60
|
+
end
|
61
|
+
|
62
|
+
# :before or :after when it's set
|
63
|
+
annotation_position = nil
|
64
|
+
|
65
|
+
if has_annotations
|
66
|
+
const_declaration = @file_parser.starts.first
|
67
|
+
|
68
|
+
# If the file does not have any class or module declaration then const_declaration can be nil
|
69
|
+
_const, line_number = const_declaration
|
70
|
+
|
71
|
+
if line_number
|
72
|
+
annotation_position = if @finder.annotation_start < line_number
|
73
|
+
:before
|
74
|
+
else
|
75
|
+
:after
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
_result = ParsedFileResult.new(
|
81
|
+
has_annotations: has_annotations,
|
82
|
+
has_skip_string: has_skip_string,
|
83
|
+
annotations_changed: annotations_changed,
|
84
|
+
annotations: annotations,
|
85
|
+
annotations_with_whitespace: annotations_with_whitespace,
|
86
|
+
has_leading_whitespace: has_leading_whitespace,
|
87
|
+
has_trailing_whitespace: has_trailing_whitespace,
|
88
|
+
annotation_position: annotation_position
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
module FileParser
|
6
|
+
class ParsedFileResult
|
7
|
+
def initialize(
|
8
|
+
has_annotations:,
|
9
|
+
has_skip_string:,
|
10
|
+
annotations_changed:,
|
11
|
+
annotations:,
|
12
|
+
annotations_with_whitespace:,
|
13
|
+
has_leading_whitespace:,
|
14
|
+
has_trailing_whitespace:,
|
15
|
+
annotation_position:
|
16
|
+
)
|
17
|
+
@has_annotations = has_annotations
|
18
|
+
@has_skip_string = has_skip_string
|
19
|
+
@annotations_changed = annotations_changed
|
20
|
+
@annotations = annotations
|
21
|
+
@annotations_with_whitespace = annotations_with_whitespace
|
22
|
+
@has_leading_whitespace = has_leading_whitespace
|
23
|
+
@has_trailing_whitespace = has_trailing_whitespace
|
24
|
+
@annotation_position = annotation_position
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :annotations, :annotation_position
|
28
|
+
|
29
|
+
# Returns annotations with new line before and after if they exist
|
30
|
+
attr_reader :annotations_with_whitespace
|
31
|
+
|
32
|
+
def annotations_changed?
|
33
|
+
@annotations_changed
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_annotations?
|
37
|
+
@has_annotations
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_skip_string?
|
41
|
+
@has_skip_string
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_leading_whitespace?
|
45
|
+
@has_leading_whitespace
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_trailing_whitespace?
|
49
|
+
@has_trailing_whitespace
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -3,7 +3,10 @@
|
|
3
3
|
module AnnotateRb
|
4
4
|
module ModelAnnotator
|
5
5
|
module FileParser
|
6
|
-
autoload :
|
6
|
+
autoload :AnnotationFinder, "annotate_rb/model_annotator/file_parser/annotation_finder"
|
7
|
+
autoload :CustomParser, "annotate_rb/model_annotator/file_parser/custom_parser"
|
8
|
+
autoload :ParsedFile, "annotate_rb/model_annotator/file_parser/parsed_file"
|
9
|
+
autoload :ParsedFileResult, "annotate_rb/model_annotator/file_parser/parsed_file_result"
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
@@ -8,6 +8,13 @@ module AnnotateRb
|
|
8
8
|
# Check for namespaced models in subdirectories as well as models
|
9
9
|
# in subdirectories without namespacing.
|
10
10
|
def call(file, options)
|
11
|
+
use_zeitwerk = defined?(::Rails) && ::Rails.try(:autoloaders).try(:zeitwerk_enabled?)
|
12
|
+
|
13
|
+
if use_zeitwerk
|
14
|
+
klass = ZeitwerkClassGetter.call(file, options)
|
15
|
+
return klass if klass
|
16
|
+
end
|
17
|
+
|
11
18
|
model_path = file.gsub(/\.rb$/, "")
|
12
19
|
options[:model_dir].each { |dir| model_path = model_path.gsub(/^#{dir}/, "").gsub(/^\//, "") }
|
13
20
|
|
@@ -115,7 +115,16 @@ module AnnotateRb
|
|
115
115
|
|
116
116
|
# Try to search the table without prefix
|
117
117
|
table_name_without_prefix = table_name.to_s.sub(@klass.table_name_prefix, "")
|
118
|
-
|
118
|
+
begin
|
119
|
+
@klass.connection.indexes(table_name_without_prefix)
|
120
|
+
rescue ActiveRecord::StatementInvalid => _e
|
121
|
+
# Mysql2 adapter behaves differently than Sqlite3 and Postgres adapter.
|
122
|
+
# If `table_name_without_prefix` does not exist, Mysql2 will raise,
|
123
|
+
# the other adapters will return an empty array.
|
124
|
+
#
|
125
|
+
# See: https://github.com/rails/rails/issues/51205
|
126
|
+
[]
|
127
|
+
end
|
119
128
|
end
|
120
129
|
|
121
130
|
def with_comments?
|
@@ -24,10 +24,10 @@ module AnnotateRb
|
|
24
24
|
add_related_scaffold_files if !@options[:exclude_scaffolds]
|
25
25
|
add_related_controller_files if !@options[:exclude_controllers]
|
26
26
|
add_related_helper_files if !@options[:exclude_helpers]
|
27
|
-
add_related_admin_files if
|
28
|
-
add_additional_file_patterns if
|
27
|
+
add_related_admin_files if @options[:active_admin]
|
28
|
+
add_additional_file_patterns if @options[:additional_file_patterns].present?
|
29
29
|
|
30
|
-
@list
|
30
|
+
@list.uniq
|
31
31
|
end
|
32
32
|
|
33
33
|
private
|
@@ -12,20 +12,18 @@ module AnnotateRb
|
|
12
12
|
return false unless File.exist?(file_name)
|
13
13
|
old_content = File.read(file_name)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
wrapper_open = if options[:wrapper_open]
|
22
|
-
"# #{options[:wrapper_open]}\n"
|
23
|
-
else
|
24
|
-
""
|
15
|
+
begin
|
16
|
+
parsed_file = FileParser::ParsedFile.new(old_content, "", options).parse
|
17
|
+
rescue FileParser::AnnotationFinder::MalformedAnnotation => e
|
18
|
+
warn "Unable to process #{file_name}: #{e.message}"
|
19
|
+
warn "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
20
|
+
return false
|
25
21
|
end
|
26
22
|
|
27
|
-
|
28
|
-
|
23
|
+
return false if !parsed_file.has_annotations?
|
24
|
+
return false if parsed_file.has_skip_string?
|
25
|
+
|
26
|
+
updated_file_content = old_content.sub(parsed_file.annotations_with_whitespace, "")
|
29
27
|
|
30
28
|
File.open(file_name, "wb") { |f| f.puts updated_file_content }
|
31
29
|
|
@@ -25,17 +25,25 @@ module AnnotateRb
|
|
25
25
|
return false unless File.exist?(file_name)
|
26
26
|
old_content = File.read(file_name)
|
27
27
|
|
28
|
-
|
28
|
+
begin
|
29
|
+
parsed_file = FileParser::ParsedFile.new(old_content, annotation, options).parse
|
30
|
+
rescue FileParser::AnnotationFinder::MalformedAnnotation => e
|
31
|
+
warn "Unable to process #{file_name}: #{e.message}"
|
32
|
+
warn "\t" + e.backtrace.join("\n\t") if @options[:trace]
|
33
|
+
return false
|
34
|
+
end
|
29
35
|
|
30
|
-
return false if
|
31
|
-
return false if !
|
36
|
+
return false if parsed_file.has_skip_string?
|
37
|
+
return false if !parsed_file.annotations_changed? && !options[:force]
|
32
38
|
|
33
39
|
abort "AnnotateRb error. #{file_name} needs to be updated, but annotaterb was run with `--frozen`." if options[:frozen]
|
34
40
|
|
35
|
-
updated_file_content = if !
|
36
|
-
AnnotatedFile::Generator.new(
|
41
|
+
updated_file_content = if !parsed_file.has_annotations?
|
42
|
+
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, options).generate
|
43
|
+
elsif options[:force]
|
44
|
+
AnnotatedFile::Generator.new(old_content, annotation, annotation_position, options).generate
|
37
45
|
else
|
38
|
-
AnnotatedFile::Updater.new(
|
46
|
+
AnnotatedFile::Updater.new(old_content, annotation, annotation_position, options).update
|
39
47
|
end
|
40
48
|
|
41
49
|
File.open(file_name, "wb") { |f| f.puts updated_file_content }
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AnnotateRb
|
4
|
+
module ModelAnnotator
|
5
|
+
class ZeitwerkClassGetter
|
6
|
+
class << self
|
7
|
+
def call(file, options)
|
8
|
+
new(file, options).call
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(file, options)
|
13
|
+
@file = file
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Constant, nil] Attempts to return the model class constant (e.g. User) defined in the model file
|
18
|
+
# can return `nil` if the file does not define the constant.
|
19
|
+
def call
|
20
|
+
return unless defined?(::Zeitwerk)
|
21
|
+
|
22
|
+
@absolute_file_path = File.expand_path(@file)
|
23
|
+
loader = ::Rails.autoloaders.main
|
24
|
+
|
25
|
+
if supports_cpath?
|
26
|
+
constant_using_cpath(loader)
|
27
|
+
else
|
28
|
+
constant(loader)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def constant(loader)
|
35
|
+
root_dirs = loader.dirs(namespaces: true) # or `root_dirs = loader.root_dirs` with zeitwerk < 2.6.1
|
36
|
+
expanded_file = @absolute_file_path
|
37
|
+
|
38
|
+
# root_dir: "/home/dummyapp/app/models"
|
39
|
+
root_dir, namespace = root_dirs.find do |dir, _namespace|
|
40
|
+
expanded_file.start_with?(dir)
|
41
|
+
end
|
42
|
+
|
43
|
+
# expanded_file: "/home/dummyapp/app/models/collapsed/example/test_model.rb"
|
44
|
+
# filepath_relative_to_root_dir: "/collapsed/example/test_model.rb"
|
45
|
+
_, filepath_relative_to_root_dir = expanded_file.split(root_dir)
|
46
|
+
|
47
|
+
# Remove leading / and the .rb extension
|
48
|
+
filepath_relative_to_root_dir = filepath_relative_to_root_dir[1..].sub(/\.rb$/, "")
|
49
|
+
|
50
|
+
# once we have the filepath_relative_to_root_dir, we need to see if it
|
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]) }
|
53
|
+
# if the file is within a collapsed path, we then need to, for each
|
54
|
+
# collapsed path, remove the root dir
|
55
|
+
collapsed = loader.collapse.map { |path| path.split(root_dir)[1].sub(/^\//, "") }.to_set
|
56
|
+
|
57
|
+
collapsed.each do |collapse|
|
58
|
+
# next, we split the collapsed directory, e.g. `domain_name/models`, by
|
59
|
+
# slash, and discard the domain_name
|
60
|
+
_, *collapsed_namespace = collapse.split("/")
|
61
|
+
|
62
|
+
# if there are any collapsed namespaces, e.g. `models`, we then remove
|
63
|
+
# that from `filepath_relative_to_root_dir`.
|
64
|
+
#
|
65
|
+
# This would result in:
|
66
|
+
#
|
67
|
+
# previous filepath_relative_to_root_dir: domain_name/models/model_name
|
68
|
+
# new filepath_relative_to_root_dir: domain_name/model_name
|
69
|
+
if collapsed_namespace.any?
|
70
|
+
filepath_relative_to_root_dir.sub!("/#{collapsed_namespace.last}", "")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
camelize = loader.inflector.camelize(filepath_relative_to_root_dir, nil)
|
76
|
+
namespace.const_get(camelize)
|
77
|
+
rescue NameError => e
|
78
|
+
warn e
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
def constant_using_cpath(loader)
|
83
|
+
begin
|
84
|
+
constant = loader.cpath_expected_at(@absolute_file_path)
|
85
|
+
rescue ::Zeitwerk::Error => e
|
86
|
+
# Raises when file does not exist
|
87
|
+
warn "Zeitwerk unable to find file #{@file}, error:\n#{e.message}"
|
88
|
+
return
|
89
|
+
end
|
90
|
+
|
91
|
+
begin
|
92
|
+
# This uses ActiveSupport::Inflector.constantize
|
93
|
+
klass = constant.constantize
|
94
|
+
rescue NameError => e
|
95
|
+
warn e
|
96
|
+
return
|
97
|
+
end
|
98
|
+
|
99
|
+
klass
|
100
|
+
end
|
101
|
+
|
102
|
+
def supports_cpath?
|
103
|
+
@supports_cpath ||=
|
104
|
+
begin
|
105
|
+
current_version = ::Gem::Version.new(::Zeitwerk::VERSION)
|
106
|
+
required_version = ::Gem::Version.new("2.6.9")
|
107
|
+
|
108
|
+
current_version >= required_version
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -7,7 +7,6 @@ module AnnotateRb
|
|
7
7
|
autoload :BadModelFileError, "annotate_rb/model_annotator/bad_model_file_error"
|
8
8
|
autoload :FileNameResolver, "annotate_rb/model_annotator/file_name_resolver"
|
9
9
|
autoload :SingleFileAnnotationRemover, "annotate_rb/model_annotator/single_file_annotation_remover"
|
10
|
-
autoload :AnnotationPatternGenerator, "annotate_rb/model_annotator/annotation_pattern_generator"
|
11
10
|
autoload :ModelClassGetter, "annotate_rb/model_annotator/model_class_getter"
|
12
11
|
autoload :ModelFilesGetter, "annotate_rb/model_annotator/model_files_getter"
|
13
12
|
autoload :SingleFileAnnotator, "annotate_rb/model_annotator/single_file_annotator"
|
@@ -22,10 +21,10 @@ module AnnotateRb
|
|
22
21
|
autoload :SingleFileRemoveAnnotationInstruction, "annotate_rb/model_annotator/single_file_remove_annotation_instruction"
|
23
22
|
autoload :AnnotationDiffGenerator, "annotate_rb/model_annotator/annotation_diff_generator"
|
24
23
|
autoload :AnnotationDiff, "annotate_rb/model_annotator/annotation_diff"
|
25
|
-
autoload :FileComponents, "annotate_rb/model_annotator/file_components"
|
26
24
|
autoload :ProjectAnnotator, "annotate_rb/model_annotator/project_annotator"
|
27
25
|
autoload :ProjectAnnotationRemover, "annotate_rb/model_annotator/project_annotation_remover"
|
28
26
|
autoload :AnnotatedFile, "annotate_rb/model_annotator/annotated_file"
|
29
27
|
autoload :FileParser, "annotate_rb/model_annotator/file_parser"
|
28
|
+
autoload :ZeitwerkClassGetter, "annotate_rb/model_annotator/zeitwerk_class_getter"
|
30
29
|
end
|
31
30
|
end
|
@@ -6,6 +6,11 @@
|
|
6
6
|
|
7
7
|
# Migration tasks are tasks that we'll "hook" into
|
8
8
|
migration_tasks = %w[db:migrate db:migrate:up db:migrate:down db:migrate:reset db:migrate:redo db:rollback]
|
9
|
+
|
10
|
+
# Support for data_migrate gem (https://github.com/ilyakatz/data-migrate)
|
11
|
+
migration_tasks_with_data = migration_tasks.map { |task| "#{task}:with_data" }
|
12
|
+
migration_tasks += migration_tasks_with_data
|
13
|
+
|
9
14
|
if defined?(Rails::Application) && Rails.version.split(".").first.to_i >= 6
|
10
15
|
require "active_record"
|
11
16
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: annotaterb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew W. Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Annotates Rails/ActiveRecord Models, routes, fixtures, and others based
|
14
14
|
on the database schema.
|
@@ -44,7 +44,6 @@ files:
|
|
44
44
|
- lib/annotate_rb/model_annotator/annotation_decider.rb
|
45
45
|
- lib/annotate_rb/model_annotator/annotation_diff.rb
|
46
46
|
- lib/annotate_rb/model_annotator/annotation_diff_generator.rb
|
47
|
-
- lib/annotate_rb/model_annotator/annotation_pattern_generator.rb
|
48
47
|
- lib/annotate_rb/model_annotator/annotator.rb
|
49
48
|
- lib/annotate_rb/model_annotator/bad_model_file_error.rb
|
50
49
|
- lib/annotate_rb/model_annotator/column_annotation.rb
|
@@ -53,10 +52,12 @@ files:
|
|
53
52
|
- lib/annotate_rb/model_annotator/column_annotation/column_wrapper.rb
|
54
53
|
- lib/annotate_rb/model_annotator/column_annotation/default_value_builder.rb
|
55
54
|
- lib/annotate_rb/model_annotator/column_annotation/type_builder.rb
|
56
|
-
- lib/annotate_rb/model_annotator/file_components.rb
|
57
55
|
- lib/annotate_rb/model_annotator/file_name_resolver.rb
|
58
56
|
- lib/annotate_rb/model_annotator/file_parser.rb
|
59
|
-
- lib/annotate_rb/model_annotator/file_parser/
|
57
|
+
- lib/annotate_rb/model_annotator/file_parser/annotation_finder.rb
|
58
|
+
- lib/annotate_rb/model_annotator/file_parser/custom_parser.rb
|
59
|
+
- lib/annotate_rb/model_annotator/file_parser/parsed_file.rb
|
60
|
+
- lib/annotate_rb/model_annotator/file_parser/parsed_file_result.rb
|
60
61
|
- lib/annotate_rb/model_annotator/foreign_key_annotation.rb
|
61
62
|
- lib/annotate_rb/model_annotator/foreign_key_annotation/annotation_builder.rb
|
62
63
|
- lib/annotate_rb/model_annotator/index_annotation.rb
|
@@ -72,6 +73,7 @@ files:
|
|
72
73
|
- lib/annotate_rb/model_annotator/single_file_annotator.rb
|
73
74
|
- lib/annotate_rb/model_annotator/single_file_annotator_instruction.rb
|
74
75
|
- lib/annotate_rb/model_annotator/single_file_remove_annotation_instruction.rb
|
76
|
+
- lib/annotate_rb/model_annotator/zeitwerk_class_getter.rb
|
75
77
|
- lib/annotate_rb/options.rb
|
76
78
|
- lib/annotate_rb/parser.rb
|
77
79
|
- lib/annotate_rb/rake_bootstrapper.rb
|
@@ -116,7 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
116
118
|
- !ruby/object:Gem::Version
|
117
119
|
version: '0'
|
118
120
|
requirements: []
|
119
|
-
rubygems_version: 3.
|
121
|
+
rubygems_version: 3.5.6
|
120
122
|
signing_key:
|
121
123
|
specification_version: 4
|
122
124
|
summary: A gem for generating annotations for Rails projects.
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnnotateRb
|
4
|
-
module ModelAnnotator
|
5
|
-
class AnnotationPatternGenerator
|
6
|
-
COMPAT_PREFIX = "== Schema Info"
|
7
|
-
COMPAT_PREFIX_MD = "## Schema Info"
|
8
|
-
|
9
|
-
class << self
|
10
|
-
def call(options)
|
11
|
-
if options[:wrapper_open]
|
12
|
-
return /(?:^(\n|\r\n)?# (?:#{options[:wrapper_open]}).*(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*)|^(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*/
|
13
|
-
end
|
14
|
-
/^(\n|\r\n)?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?(\n|\r\n)(#.*(\n|\r\n))*(\n|\r\n)*/o
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,81 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnnotateRb
|
4
|
-
module ModelAnnotator
|
5
|
-
class FileComponents
|
6
|
-
SKIP_ANNOTATION_STRING = "# -*- SkipSchemaAnnotations"
|
7
|
-
SOME_PATTERN = /\A(?<start>\s*).*?\n(?<end>\s*)\z/m # Unsure what this pattern is
|
8
|
-
|
9
|
-
attr_reader :new_annotations
|
10
|
-
|
11
|
-
def initialize(file_content, new_annotations, options)
|
12
|
-
@file_content = file_content
|
13
|
-
@diff = AnnotationDiffGenerator.new(file_content, new_annotations).generate
|
14
|
-
@options = options
|
15
|
-
@annotation_pattern = AnnotationPatternGenerator.call(options)
|
16
|
-
@new_annotations = new_annotations
|
17
|
-
end
|
18
|
-
|
19
|
-
def current_file_content
|
20
|
-
@file_content
|
21
|
-
end
|
22
|
-
|
23
|
-
# TODO: Rename method once it's clear what this actually does
|
24
|
-
def space_before_annotation
|
25
|
-
return @space_before_annotation if defined?(@space_before_annotation)
|
26
|
-
|
27
|
-
match = current_annotations.match(SOME_PATTERN)
|
28
|
-
@space_before_annotation = if match
|
29
|
-
match[:start]
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# TODO: Rename method once it's clear what this actually does
|
34
|
-
def space_after_annotation
|
35
|
-
return @space_after_annotation if defined?(@space_after_annotation)
|
36
|
-
|
37
|
-
match = current_annotations.match(SOME_PATTERN)
|
38
|
-
@space_after_annotation = if match
|
39
|
-
match[:end]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def pure_file_content
|
44
|
-
@pure_file_content ||=
|
45
|
-
begin
|
46
|
-
content_without_magic_comments = @file_content.gsub(FileParser::MagicCommentParser::MAGIC_COMMENTS_REGEX, "")
|
47
|
-
content_without_annotations = content_without_magic_comments.sub(@annotation_pattern, "")
|
48
|
-
|
49
|
-
content_without_annotations
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def magic_comments
|
54
|
-
@magic_comments ||= FileParser::MagicCommentParser.call(@file_content)
|
55
|
-
end
|
56
|
-
|
57
|
-
def has_skip_string?
|
58
|
-
@has_skip_string ||= @file_content.include?(SKIP_ANNOTATION_STRING)
|
59
|
-
end
|
60
|
-
|
61
|
-
def has_annotations?
|
62
|
-
@has_annotations ||= @diff.current_columns.present?
|
63
|
-
end
|
64
|
-
|
65
|
-
def annotations_changed?
|
66
|
-
@has_annotations_changed ||= @diff.changed?
|
67
|
-
end
|
68
|
-
|
69
|
-
def current_annotations
|
70
|
-
@current_annotations ||=
|
71
|
-
if has_annotations?
|
72
|
-
# `#has_annotations?` uses a different regex pattern than the one in `@annotation_pattern`,
|
73
|
-
# this could lead to unexpected behavior
|
74
|
-
@file_content.match(@annotation_pattern).to_s
|
75
|
-
else
|
76
|
-
""
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AnnotateRb
|
4
|
-
module ModelAnnotator
|
5
|
-
module FileParser
|
6
|
-
# Extracts magic comments strings and returns them
|
7
|
-
class MagicCommentParser
|
8
|
-
MAGIC_COMMENTS = [
|
9
|
-
HASH_ENCODING = /(^#\s*encoding:.*(?:\n|r\n))/,
|
10
|
-
HASH_CODING = /(^# coding:.*(?:\n|\r\n))/,
|
11
|
-
HASH_FROZEN_STRING = /(^#\s*frozen_string_literal:.+(?:\n|\r\n))/,
|
12
|
-
STAR_ENCODING = /(^# -\*- encoding\s?:.*(?:\n|\r\n))/,
|
13
|
-
STAR_CODING = /(^# -\*- coding:.*(?:\n|\r\n))/,
|
14
|
-
STAR_FROZEN_STRING = /(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/,
|
15
|
-
SORBET_TYPED_STRING = /(^#\s*typed:.*(?:\n|r\n))/.freeze
|
16
|
-
].freeze
|
17
|
-
|
18
|
-
MAGIC_COMMENTS_REGEX = Regexp.union(*MAGIC_COMMENTS).freeze
|
19
|
-
|
20
|
-
class << self
|
21
|
-
def call(content)
|
22
|
-
magic_comments = content.scan(MAGIC_COMMENTS_REGEX).flatten.compact
|
23
|
-
|
24
|
-
if magic_comments.any?
|
25
|
-
magic_comments.join
|
26
|
-
else
|
27
|
-
""
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|