rails-annotate-solargraph 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7bb66133bae3d570210fe56d3b0b707666551af81bbc2923a8cf7d589ec0ad8
4
- data.tar.gz: 4591722333e2d9b770ba3b33887a85ed37e846d7a8f3a03913cba4ab4627f828
3
+ metadata.gz: 744b5deaf3bfdd5fdf4b81b54abd8f4672336402bce178af67b04ff704b6ceab
4
+ data.tar.gz: 062d49842f237ddb38202975c0c88733fb3a825ebcb4c7cbd7641b45b58ee993
5
5
  SHA512:
6
- metadata.gz: 30d537b5e78042ba0dc990af3030b1ff1fb85232c3f998da008d1eaba6c83b2ab543c2c33adbe87ce181f4f6f9954ac500e077b4134dac9801498455e83e3b0e
7
- data.tar.gz: a36382741d8e9964835a8818a3091d26c4d18c3e798ec0e77636e47e1c7cd172f0f17086bc4869022b1552bfac91cded203def21723e9a99f3d687ab8fcba903
6
+ metadata.gz: 35eb981e6a0b52331164d80fd7238aadae0403cd9f4d93c7dc9a6c00e31714f6b731c23e961542ee5eac64a48c2820de926fcacaea1e6b8ff9b34c501781c05b
7
+ data.tar.gz: 6d0f472969a50ed833398216789d0000b8c288685946ab90621f48edca85f57034350df39a5006ae5ddb6b4a30623658c529584e6eca504b0202ff6b633b3379
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2022-04-16
4
+
5
+ - Associations get fully documented
6
+
7
+ ## [0.1.1] - 2022-04-15
8
+
9
+ - Minor bug fix after the initial release
10
+
3
11
  ## [0.1.0] - 2022-04-15
4
12
 
5
13
  - Initial release
14
+ - Automatic generation of annotations after migrations
15
+ - Manual rake tasks `annotate:solargraph:generate`, `annotate:solargraph:remove`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails-annotate-solargraph (0.1.1)
4
+ rails-annotate-solargraph (0.2.0)
5
5
  rails (>= 5.0, < 8.0)
6
6
 
7
7
  GEM
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ include ::Rails::Annotate::Solargraph::TerminalColors
4
+
3
5
  if ::Rails.env.development?
4
6
  namespace :annotate do
5
7
  namespace :solargraph do
6
8
  desc "Add YARD comments documenting the models' schemas"
7
9
  task generate: :environment do
8
- system 'rails runner "p ::Rails::Annotate::Solargraph.generate"'
10
+ system! 'bundle exec bin/rails runner "::Rails::Annotate::Solargraph.generate"'
9
11
  end
10
12
 
11
13
  desc "Remove YARD comments documenting the models' schemas"
12
14
  task remove: :environment do
13
- system 'rails runner "p ::Rails::Annotate::Solargraph.remove"'
15
+ system! 'bundle exec bin/rails runner "::Rails::Annotate::Solargraph.remove"'
14
16
  end
15
17
  end
16
18
 
@@ -23,4 +25,8 @@ if ::Rails.env.development?
23
25
  end
24
26
  end
25
27
  end
28
+
29
+ def system!(val)
30
+ system(val) || abort(error_string("Command `#{val}` failed"))
31
+ end
26
32
  end
@@ -4,9 +4,15 @@ module Rails
4
4
  module Annotate
5
5
  module Solargraph
6
6
  class Model
7
+ using TerminalColors::Refinement
8
+
9
+ # @return [String]
7
10
  ANNOTATION_START = "\n# %%<RailsAnnotateSolargraph:Start>%%"
11
+ # @return [String]
8
12
  ANNOTATION_END = "%%<RailsAnnotateSolargraph:End>%%\n\n"
13
+ # @return [Regexp]
9
14
  ANNOTATION_REGEXP = /#{ANNOTATION_START}.*#{ANNOTATION_END}/m.freeze
15
+ # @return [Regexp]
10
16
  MAGIC_COMMENT_REGEXP = /(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/.freeze
11
17
 
12
18
  class << self
@@ -51,7 +57,7 @@ module Rails
51
57
  # @param :write [Boolean]
52
58
  # @return [String] New file content.
53
59
  def annotate(write: true)
54
- file_content = remove_annotation write: false
60
+ old_content, file_content = remove_annotation write: false
55
61
 
56
62
  if CONFIG.annotation_position == :top
57
63
  magic_comments = file_content.scan(MAGIC_COMMENT_REGEXP).flatten.compact.join
@@ -63,35 +69,43 @@ module Rails
63
69
  end
64
70
 
65
71
  return new_file_content unless write
66
- return new_file_content if file_content == new_file_content
72
+ # debugger
73
+ return new_file_content if old_content == new_file_content
67
74
 
68
- ::File.write @file_name, new_file_content
75
+ write_file @file_name, new_file_content
69
76
  new_file_content
70
77
  end
71
78
 
72
79
  # @param :write [Boolean]
73
- # @return [String] New file content.
80
+ # @return [Array<String>] Old file content followed by new content.
74
81
  def remove_annotation(write: true)
75
82
  file_content = ::File.read(@file_name)
76
83
  new_file_content = file_content.sub(ANNOTATION_REGEXP, '')
77
- return new_file_content unless write
78
- return new_file_content if file_content == new_file_content
84
+ result = [file_content, new_file_content]
85
+ return result unless write
86
+ return result if file_content == new_file_content
79
87
 
80
- ::File.write @file_name, new_file_content
81
- new_file_content
88
+ write_file @file_name, new_file_content
89
+ result
82
90
  end
83
91
 
84
92
  # @return [String]
85
93
  def annotation
86
- result = ::String.new
87
- result << <<~DOC
94
+ doc_string = ::String.new
95
+ doc_string << <<~DOC
88
96
  #{ANNOTATION_START}
89
97
  # @!parse
90
98
  # class #{@klass} < #{@klass.superclass}
91
99
  DOC
92
100
 
101
+ @klass.reflections.sort.each do |attr_name, reflection|
102
+ next document_polymorphic_relation(doc_string, attr_name, reflection) if reflection.polymorphic?
103
+
104
+ document_relation(doc_string, attr_name, reflection)
105
+ end
106
+
93
107
  @klass.attribute_types.each do |name, attr_type|
94
- result << <<~DOC
108
+ doc_string << <<~DOC
95
109
  # # Database column `#{@klass.table_name}.#{name}`, type: `#{attr_type.type}`.
96
110
  # # @param val [#{yard_type attr_type}, nil]
97
111
  # def #{name}=(val); end
@@ -101,7 +115,7 @@ module Rails
101
115
  DOC
102
116
  end
103
117
 
104
- result << <<~DOC.chomp
118
+ doc_string << <<~DOC.chomp
105
119
  # end
106
120
  # #{ANNOTATION_END}
107
121
  DOC
@@ -109,6 +123,73 @@ module Rails
109
123
 
110
124
  private
111
125
 
126
+ # @param file_name [String]
127
+ # @return [String]
128
+ def relative_file_name(file_name)
129
+ file_name.delete_prefix("#{::Rails.root}/")
130
+ end
131
+
132
+ # @param file_name [String]
133
+ # @param content [String]
134
+ # @return [void]
135
+ def write_file(file_name, content)
136
+ ::File.write(file_name, content)
137
+ puts "modify".rjust(12).with_styles(:bold, :green) + " #{relative_file_name(file_name)}"
138
+ end
139
+
140
+ # @return [String]
141
+ def klass_relation_name
142
+ @klass.table_name[..-2]
143
+ end
144
+
145
+ # @param doc_string [String]
146
+ # @param attr_name [String]
147
+ # @param reflection [ActiveRecord::Reflection::AbstractReflection]
148
+ # @return [void]
149
+ def document_relation(doc_string, attr_name, reflection)
150
+ db_description = \
151
+ case reflection
152
+ when ::ActiveRecord::Reflection::BelongsToReflection
153
+ type_docstring = reflection.klass
154
+ "`belongs_to` relation with `#{reflection.klass}`. Database column `#{@klass.table_name}.#{reflection.foreign_key}`."
155
+ when ::ActiveRecord::Reflection::HasOneReflection
156
+ type_docstring = reflection.klass
157
+ "`has_one` relation with `#{reflection.klass}`. Database column `#{reflection.klass.table_name}.#{reflection.foreign_key}`."
158
+ when ::ActiveRecord::Reflection::HasManyReflection
159
+ type_docstring = "Array<#{reflection.klass}>"
160
+ "`has_many` relation with `#{reflection.klass}`. Database column `#{reflection.klass.table_name}.#{reflection.foreign_key}`."
161
+ end
162
+
163
+ doc_string << <<~DOC
164
+ # # #{db_description}
165
+ # # @param val [#{type_docstring}, nil]
166
+ # def #{attr_name}=(val); end
167
+ # # #{db_description}
168
+ # # @return [#{type_docstring}, nil]
169
+ # def #{attr_name}; end
170
+ DOC
171
+ end
172
+
173
+ # @param doc_string [String]
174
+ # @param attr_name [String]
175
+ # @param reflection [ActiveRecord::Reflection::AbstractReflection]
176
+ # @return [void]
177
+ def document_polymorphic_relation(doc_string, attr_name, reflection)
178
+ classes = Solargraph.model_classes.select do |model_class|
179
+ model_class.reflections[klass_relation_name]&.options&.[](:as)&.to_sym == attr_name.to_sym
180
+ end
181
+
182
+ classes_string = classes.join(', ')
183
+ doc_string << <<~DOC
184
+ # # Polymorphic relation. Database columns `#{@klass.table_name}.#{attr_name}_id` and `#{@klass.table_name}.#{attr_name}_type`.
185
+ # # @param val [#{classes_string}, nil]
186
+ # def #{attr_name}=(val); end
187
+ # # Polymorphic relation. Database columns `#{@klass.table_name}.#{attr_name}_id` and `#{@klass.table_name}.#{attr_name}_type`.
188
+ # # @return [#{classes_string}, nil]
189
+ # def #{attr_name}; end
190
+ DOC
191
+ end
192
+
112
193
  # @param attr_type [ActiveModel::Type::Value]
113
194
  # @return [String]
114
195
  def yard_type(attr_type)
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module Annotate
5
+ module Solargraph
6
+ module TerminalColors
7
+ extend self
8
+
9
+ # @return [Hash{Symbol => String}]
10
+ MAP = {
11
+ blue: (BLUE = "\033[94m"),
12
+ cyan: (CYAN = "\033[96m"),
13
+ green: (GREEN = "\033[92m"),
14
+ yellow: (YELLOW = "\033[93m"),
15
+ red: (RED = "\033[91m"),
16
+ terminate: (TERMINATE = "\033[0m"),
17
+ bold: (BOLD = "\033[1m"),
18
+ italic: (ITALIC = "\033[3m"),
19
+ underline: (UNDERLINE = "\033[4m")
20
+ }.freeze
21
+
22
+ class << self
23
+ # Style a string with an ASCII escape code
24
+ #
25
+ # @param string [String]
26
+ # @param style [Symbol]
27
+ # @return [String]
28
+ def with_style(string, style)
29
+ "#{MAP[style]}#{string}#{TERMINATE}"
30
+ end
31
+
32
+ # Style a string with multiple ASCII escape codes
33
+ #
34
+ # @param string [String]
35
+ # @param styles [Array<Symbol>]
36
+ # @return [String]
37
+ def with_styles(string, *styles)
38
+ result = ::String.new
39
+ styles.each do |style|
40
+ result << MAP[style]
41
+ end
42
+
43
+ result << "#{string}#{TERMINATE}"
44
+ end
45
+ end
46
+
47
+ def title_string(string)
48
+ TerminalColors.with_styles "== #{string} ==", :cyan, :underline, :italic
49
+ end
50
+
51
+ def error_string(string)
52
+ TerminalColors.with_styles "!! #{string} !!", :bold, :red
53
+ end
54
+
55
+ def title(string)
56
+ puts "\n", title_string(string)
57
+ end
58
+
59
+ def error(string)
60
+ puts "\n", error_string(string)
61
+ end
62
+
63
+ module Refinement
64
+ refine ::String do
65
+ # @param styles [Array<Symbol]>
66
+ # @return [self] Colored string
67
+ def with_styles(*styles)
68
+ ::Rails::Annotate::Solargraph::TerminalColors.with_styles(self, *styles)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -3,7 +3,7 @@
3
3
  module Rails
4
4
  module Annotate
5
5
  module Solargraph
6
- VERSION = '0.1.1'
6
+ VERSION = '0.2.0'
7
7
  end
8
8
  end
9
9
  end
@@ -4,6 +4,7 @@ require 'set'
4
4
 
5
5
  require_relative "solargraph/version"
6
6
  require_relative "solargraph/configuration"
7
+ require_relative "solargraph/terminal_colors"
7
8
  require_relative "solargraph/model"
8
9
 
9
10
  module Rails
@@ -16,15 +17,19 @@ module Rails
16
17
  RAKEFILE_NAME = 'rails_annotate_solargraph.rake'
17
18
  # @return [Configuration]
18
19
  CONFIG = Configuration.new
20
+ # @return [Set<Symbol>]
21
+ VALID_MODIFICATION_METHODS = ::Set[:annotate, :remove_annotation].freeze
19
22
 
20
23
  class << self
21
24
  # @return [Array<String>] Array of changed files.
22
25
  def generate
26
+ title 'Generating model schema annotations'
23
27
  modify_models :annotate
24
28
  end
25
29
 
26
30
  # @return [Array<String>] Array of changed files.
27
31
  def remove
32
+ title 'Removing model schema annotations'
28
33
  modify_models :remove_annotation
29
34
  end
30
35
 
@@ -35,10 +40,15 @@ module Rails
35
40
 
36
41
  alias call generate
37
42
 
38
- VALID_MODIFICATION_METHODS = ::Set[:annotate, :remove_annotation]
43
+ # @return [Array<ActiveRecord::Base>]
44
+ def model_classes
45
+ @model_classes ||= (::ApplicationRecord rescue ::ActiveRecord::Base).subclasses.sort_by(&:name)
46
+ end
39
47
 
40
48
  private
41
49
 
50
+ include TerminalColors
51
+
42
52
  # @param method [Symbol] Name of the method that will be called on every loaded Model
43
53
  # @return [Array<String>] Array of changed files.
44
54
  def modify_models(method)
@@ -49,7 +59,7 @@ module Rails
49
59
  model_files = ::Dir[::File.join(::Rails.root, MODEL_DIR, '**/*.rb')].map { |file| file.sub("#{::Rails.root}/", '') }.to_set
50
60
 
51
61
  ::Rails.application.eager_load!
52
- (::ApplicationRecord rescue ::ActiveRecord::Base).subclasses.each do |subclass|
62
+ model_classes.each do |subclass|
53
63
  subclass_file = ::File.join MODEL_DIR, "#{subclass.to_s.underscore}.rb"
54
64
  next unless model_files.include? subclass_file
55
65
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-annotate-solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Drewniak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-15 00:00:00.000000000 Z
11
+ date: 2022-04-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -57,6 +57,7 @@ files:
57
57
  - lib/rails/annotate/solargraph.rb
58
58
  - lib/rails/annotate/solargraph/configuration.rb
59
59
  - lib/rails/annotate/solargraph/model.rb
60
+ - lib/rails/annotate/solargraph/terminal_colors.rb
60
61
  - lib/rails/annotate/solargraph/version.rb
61
62
  - rails-annotate-solargraph.gemspec
62
63
  - sig/rails/annotate/solargraph.rbs