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.
@@ -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