rubocop-springest 0.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +27 -0
- data/STYLEGUIDE.md +738 -0
- data/config/default.yml +415 -0
- data/config/rails.yml +118 -0
- data/guides/rails-controller-render-shorthand.md +9 -0
- data/guides/rails-render-inline.md +27 -0
- data/guides/rails-render-literal.md +8 -0
- data/lib/rubocop/cop/github/rails_application_record.rb +29 -0
- data/lib/rubocop/cop/github/rails_controller_render_action_symbol.rb +43 -0
- data/lib/rubocop/cop/github/rails_controller_render_literal.rb +94 -0
- data/lib/rubocop/cop/github/rails_controller_render_paths_exist.rb +69 -0
- data/lib/rubocop/cop/github/rails_controller_render_shorthand.rb +51 -0
- data/lib/rubocop/cop/github/rails_render_inline.rb +29 -0
- data/lib/rubocop/cop/github/rails_render_object_collection.rb +47 -0
- data/lib/rubocop/cop/github/rails_view_render_literal.rb +64 -0
- data/lib/rubocop/cop/github/rails_view_render_paths_exist.rb +59 -0
- data/lib/rubocop/cop/github/rails_view_render_shorthand.rb +38 -0
- data/lib/rubocop/cop/github.rb +10 -0
- metadata +133 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# GitHub/RailsRenderInline
|
2
|
+
|
3
|
+
tldr; Do not use `render inline:`.
|
4
|
+
|
5
|
+
## Rendering plain text
|
6
|
+
|
7
|
+
``` ruby
|
8
|
+
render inline: "Just plain text" # bad
|
9
|
+
```
|
10
|
+
|
11
|
+
The `inline:` option is often misused when plain text is being returned. Instead use `render plain: "Just plain text"`.
|
12
|
+
|
13
|
+
## Rendering a dynamic ERB string
|
14
|
+
|
15
|
+
String `#{}` interpolation is often misused with `render inline:` instead of using `<%= %>` interpolation. This will lead to a memory leak where an ERB method will be compiled and cached for each invocation of `render inline:`.
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
render inline: "Hello #{@name}" # bad
|
19
|
+
```
|
20
|
+
|
21
|
+
## Rendering static ERB strings
|
22
|
+
|
23
|
+
``` ruby
|
24
|
+
render inline: "Hello <%= @name %>" # bad
|
25
|
+
```
|
26
|
+
|
27
|
+
If you are passing a static ERB string to `render inline:`, this string is best moved to a `.erb` template under `app/views`. Template files are able to be precompiled at boot time.
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# GitHub/RailsRenderLiteral
|
2
|
+
|
3
|
+
tldr; `render` MUST be passed a string literal template path.
|
4
|
+
|
5
|
+
* When used in conjunction with `GitHub/RailsViewRenderPathsExist`, linters can ensure the target file exists on disk and would not crash rendering a missing template.
|
6
|
+
* Makes it easier for humans to trace callers of a template. Simply search for the full path of the target template to find **all** call sites.
|
7
|
+
* This same call site tracing enables automated unused template checking. If no callers are found, the template can be safely removed.
|
8
|
+
* Enables render precompilation and inlining optimizations. Target templates can be compiled and inlined on boot time rather than deferring to first render to lazily compile templates.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsApplicationRecord < Cop
|
9
|
+
MSG = "Models should subclass from ApplicationRecord"
|
10
|
+
|
11
|
+
def_node_matcher :active_record_base_const?, <<-PATTERN
|
12
|
+
(const (const nil? :ActiveRecord) :Base)
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :application_record_const?, <<-PATTERN
|
16
|
+
(const nil? :ApplicationRecord)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def on_class(node)
|
20
|
+
klass, superclass, _ = *node
|
21
|
+
|
22
|
+
if active_record_base_const?(superclass) && !(application_record_const?(klass))
|
23
|
+
add_offense(superclass, location: :expression)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsControllerRenderActionSymbol < Cop
|
9
|
+
MSG = "Prefer `render` with string instead of symbol"
|
10
|
+
|
11
|
+
def_node_matcher :render_sym?, <<-PATTERN
|
12
|
+
(send nil? :render $(sym _))
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
16
|
+
(send nil? :render (hash $...))
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def_node_matcher :action_key?, <<-PATTERN
|
20
|
+
(pair (sym {:action :template}) $(sym _))
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def on_send(node)
|
24
|
+
if sym_node = render_sym?(node)
|
25
|
+
add_offense(sym_node, location: :expression)
|
26
|
+
elsif option_pairs = render_with_options?(node)
|
27
|
+
option_pairs.each do |pair|
|
28
|
+
if sym_node = action_key?(pair)
|
29
|
+
add_offense(sym_node, location: :expression)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def autocorrect(node)
|
36
|
+
lambda do |corrector|
|
37
|
+
corrector.replace(node.source_range, "\"#{node.children[0]}\"")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsControllerRenderLiteral < Cop
|
9
|
+
MSG = "render must be used with a string literal"
|
10
|
+
|
11
|
+
def_node_matcher :literal?, <<-PATTERN
|
12
|
+
({str sym true false nil?} ...)
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :render?, <<-PATTERN
|
16
|
+
(send nil? :render ...)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def_node_matcher :render_literal?, <<-PATTERN
|
20
|
+
(send nil? :render ({str sym} $_) $...)
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
24
|
+
(send nil? :render (hash $...))
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def_node_matcher :ignore_key?, <<-PATTERN
|
28
|
+
(pair (sym {
|
29
|
+
:body
|
30
|
+
:file
|
31
|
+
:html
|
32
|
+
:inline
|
33
|
+
:js
|
34
|
+
:json
|
35
|
+
:nothing
|
36
|
+
:plain
|
37
|
+
:text
|
38
|
+
:xml
|
39
|
+
}) $_)
|
40
|
+
PATTERN
|
41
|
+
|
42
|
+
def_node_matcher :template_key?, <<-PATTERN
|
43
|
+
(pair (sym {
|
44
|
+
:action
|
45
|
+
:partial
|
46
|
+
:template
|
47
|
+
}) $_)
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def_node_matcher :layout_key?, <<-PATTERN
|
51
|
+
(pair (sym :layout) $_)
|
52
|
+
PATTERN
|
53
|
+
|
54
|
+
def_node_matcher :options_key?, <<-PATTERN
|
55
|
+
(pair (sym {
|
56
|
+
:content_type
|
57
|
+
:location
|
58
|
+
:status
|
59
|
+
:formats
|
60
|
+
}) ...)
|
61
|
+
PATTERN
|
62
|
+
|
63
|
+
def on_send(node)
|
64
|
+
return unless render?(node)
|
65
|
+
|
66
|
+
if render_literal?(node)
|
67
|
+
elsif option_pairs = render_with_options?(node)
|
68
|
+
option_pairs = option_pairs.reject { |pair| options_key?(pair) }
|
69
|
+
|
70
|
+
if option_pairs.any? { |pair| ignore_key?(pair) }
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
if template_node = option_pairs.map { |pair| template_key?(pair) }.compact.first
|
75
|
+
if !literal?(template_node)
|
76
|
+
add_offense(node, location: :expression)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
add_offense(node, location: :expression)
|
80
|
+
end
|
81
|
+
|
82
|
+
if layout_node = option_pairs.map { |pair| layout_key?(pair) }.compact.first
|
83
|
+
if !literal?(layout_node)
|
84
|
+
add_offense(node, location: :expression)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
else
|
88
|
+
add_offense(node, location: :expression)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsControllerRenderPathsExist < Cop
|
9
|
+
def_node_matcher :render?, <<-PATTERN
|
10
|
+
(send nil? :render $...)
|
11
|
+
PATTERN
|
12
|
+
|
13
|
+
def_node_matcher :render_str?, <<-PATTERN
|
14
|
+
(send nil? :render $({str sym} $_) ...)
|
15
|
+
PATTERN
|
16
|
+
|
17
|
+
def_node_matcher :render_options?, <<-PATTERN
|
18
|
+
(send nil? :render (hash $...))
|
19
|
+
PATTERN
|
20
|
+
|
21
|
+
def_node_matcher :render_key?, <<-PATTERN
|
22
|
+
(pair (sym ${:action :partial :template}) $({str sym} $_))
|
23
|
+
PATTERN
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
return unless cop_config["ViewPath"]
|
27
|
+
|
28
|
+
if args = render_str?(node)
|
29
|
+
node, path = args
|
30
|
+
unless resolve_template(path.to_s)
|
31
|
+
add_offense(node, location: :expression, message: "Template could not be found")
|
32
|
+
end
|
33
|
+
elsif pairs = render_options?(node)
|
34
|
+
if pair = pairs.detect { |p| render_key?(p) }
|
35
|
+
key, node, path = render_key?(pair)
|
36
|
+
|
37
|
+
case key
|
38
|
+
when :action, :template
|
39
|
+
unless resolve_template(path.to_s)
|
40
|
+
add_offense(node, location: :expression, message: "Template could not be found")
|
41
|
+
end
|
42
|
+
when :partial
|
43
|
+
unless resolve_partial(path.to_s)
|
44
|
+
add_offense(node, location: :expression, message: "Partial template could not be found")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_template(path)
|
52
|
+
cop_config["ViewPath"].each do |view_path|
|
53
|
+
if m = Dir[File.join(config.path_relative_to_config(view_path), path) + "*"].first
|
54
|
+
return m
|
55
|
+
end
|
56
|
+
end
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def resolve_partial(path)
|
61
|
+
parts = path.split(File::SEPARATOR)
|
62
|
+
parts << "_#{parts.pop}"
|
63
|
+
path = parts.join(File::SEPARATOR)
|
64
|
+
resolve_template(path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsControllerRenderShorthand < Cop
|
9
|
+
MSG = "Prefer `render` template shorthand"
|
10
|
+
|
11
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
12
|
+
(send nil? :render (hash $...))
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :action_key?, <<-PATTERN
|
16
|
+
(pair (sym {:action :template}) $({str sym} _))
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def_node_matcher :str, <<-PATTERN
|
20
|
+
({str sym} $_)
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def investigate(*)
|
24
|
+
@autocorrect = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def autocorrect(node)
|
28
|
+
@autocorrect[node]
|
29
|
+
end
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
if option_pairs = render_with_options?(node)
|
33
|
+
option_pairs.each do |pair|
|
34
|
+
if value_node = action_key?(pair)
|
35
|
+
comma = option_pairs.length > 1 ? ", " : ""
|
36
|
+
corrected_source = node.source
|
37
|
+
.sub(/#{pair.source}(,\s*)?/, "")
|
38
|
+
.sub("render ", "render \"#{str(value_node)}\"#{comma}")
|
39
|
+
|
40
|
+
@autocorrect[node] = lambda do |corrector|
|
41
|
+
corrector.replace(node.source_range, corrected_source)
|
42
|
+
end
|
43
|
+
add_offense(node, location: :expression, message: "Use `#{corrected_source}` instead")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsRenderInline < Cop
|
9
|
+
MSG = "Avoid `render inline:`"
|
10
|
+
|
11
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
12
|
+
(send nil? :render (hash $...))
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :inline_key?, <<-PATTERN
|
16
|
+
(pair (sym :inline) $_)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def on_send(node)
|
20
|
+
if option_pairs = render_with_options?(node)
|
21
|
+
if option_pairs.detect { |pair| inline_key?(pair) }
|
22
|
+
add_offense(node, location: :expression)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsRenderObjectCollection < Cop
|
9
|
+
MSG = "Avoid `render object:`"
|
10
|
+
|
11
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
12
|
+
(send nil? :render (hash $...) ...)
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :partial_key?, <<-PATTERN
|
16
|
+
(pair (sym :partial) $_)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def_node_matcher :object_key?, <<-PATTERN
|
20
|
+
(pair (sym ${:object :collection :spacer_template}) $_)
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def on_send(node)
|
24
|
+
if option_pairs = render_with_options?(node)
|
25
|
+
partial_pair = option_pairs.detect { |pair| partial_key?(pair) }
|
26
|
+
object_pair = option_pairs.detect { |pair| object_key?(pair) }
|
27
|
+
|
28
|
+
if partial_pair && object_pair
|
29
|
+
partial_name = partial_key?(partial_pair)
|
30
|
+
object_sym, object_node = object_key?(object_pair)
|
31
|
+
|
32
|
+
case object_sym
|
33
|
+
when :object
|
34
|
+
if partial_name.children[0].is_a?(String)
|
35
|
+
suggestion = ", instead `render partial: #{partial_name.source}, locals: { #{File.basename(partial_name.children[0], '.html.erb')}: #{object_node.source} }`"
|
36
|
+
end
|
37
|
+
add_offense(node, location: :expression, message: "Avoid `render object:`#{suggestion}")
|
38
|
+
when :collection, :spacer_template
|
39
|
+
add_offense(node, location: :expression, message: "Avoid `render collection:`")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubocop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Cop
|
7
|
+
module GitHub
|
8
|
+
class RailsViewRenderLiteral < Cop
|
9
|
+
MSG = "render must be used with a string literal"
|
10
|
+
|
11
|
+
def_node_matcher :literal?, <<-PATTERN
|
12
|
+
({str sym true false nil?} ...)
|
13
|
+
PATTERN
|
14
|
+
|
15
|
+
def_node_matcher :render?, <<-PATTERN
|
16
|
+
(send nil? :render $...)
|
17
|
+
PATTERN
|
18
|
+
|
19
|
+
def_node_matcher :render_literal?, <<-PATTERN
|
20
|
+
(send nil? :render ({str sym} $_) $...)
|
21
|
+
PATTERN
|
22
|
+
|
23
|
+
def_node_matcher :render_with_options?, <<-PATTERN
|
24
|
+
(send nil? :render (hash $...) ...)
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def_node_matcher :ignore_key?, <<-PATTERN
|
28
|
+
(pair (sym {
|
29
|
+
:inline
|
30
|
+
}) $_)
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def_node_matcher :partial_key?, <<-PATTERN
|
34
|
+
(pair (sym {
|
35
|
+
:file
|
36
|
+
:layout
|
37
|
+
:partial
|
38
|
+
}) $_)
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def on_send(node)
|
42
|
+
return unless render?(node)
|
43
|
+
|
44
|
+
if render_literal?(node)
|
45
|
+
elsif option_pairs = render_with_options?(node)
|
46
|
+
if option_pairs.any? { |pair| ignore_key?(pair) }
|
47
|
+
return
|
48
|
+
end
|
49
|
+
|
50
|
+
if partial_node = option_pairs.map { |pair| partial_key?(pair) }.compact.first
|
51
|
+
if !literal?(partial_node)
|
52
|
+
add_offense(node, location: :expression)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
add_offense(node, location: :expression)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
add_offense(node, location: :expression)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|