rubocop-springest 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ # GitHub/RailsControllerRenderShorthand
2
+
3
+ Prefer `render "path/to/template"` shorthand in controllers.
4
+
5
+ ``` ruby
6
+ render template: "products/show"
7
+ # can be written as
8
+ render "products/show"
9
+ ```
@@ -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