platformos-check 0.4.8 → 0.4.9

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: 579d15b95d0e00e65eb562778f0870bc28698bfe2f6c97ca66e7ca81a18b855c
4
- data.tar.gz: 2f6afe88f067cccc169d62a9744399b5b11294537dd8c7653ca9d5ed9165f1f3
3
+ metadata.gz: e41eee4e2aa717be9663df4efbc1acf79bc8b571784808b80177f9298fdad9e9
4
+ data.tar.gz: 6bed8f9e1e3e22dbd02cee00a15d5e9ce1787f7826ee09e3e461fcf0161c681d
5
5
  SHA512:
6
- metadata.gz: 2fa86abf31229208eaff48e7f95b212977682bd560234d99d2fa9b3162a33ca7139b8faad2960152951295b056ef41b834e9faa756b0627f02a14034f6d4e468
7
- data.tar.gz: 18fa38e2613faf4e31dfdf0a432e4fbe3a8ced0b18f8f194698bf73606affd770cf2589dadb852c26c19f5ec31146535e95e863d7c31ab9c8977feb67fa99a07
6
+ metadata.gz: 71a9feab2a3d5e31a2436652036bf1669c65424f24cfa550feedb49bd43e03264f2feb51b0367c84a79805c3cda0d24ed3ccccdd27d2b63a09dd15f3e26a94b4
7
+ data.tar.gz: 56961261716dae13fce53dadb2c6e03d8c3f268195748e81783cfe4dd7162c18ffb05063530975939ad2805f2a2e33d5d63aab1da51de65f3c98e17e1e2df2b4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ v0.4.9 / 2024-01-10
2
+ ==================
3
+
4
+ * Skip FormAuthenticityToken check for GET forms
5
+ * Skip FormAuthenticityToken for action which is not relative path
6
+ * Fix FormAction to not report offenses on valid scenarios
7
+ * UnusedAssign will not automatically remove assign if it might change the business logic (which is a scenario when filters modifying objects are used)
8
+ * UnusedAssign will automatically rename result of background tag if variable not used
9
+ * Fix reporting UndefinedObject's missing argument offenses when the same partial is used multiple times (previously offenses where displayed only for the last render)
10
+ * Add autocorrector for UndefinedObject's missing argument error (explicitly provide null)
11
+ * Add autocorrector for ImgLazyLoading
12
+ * ConvertIncludeToRender will not report offense as autocorrect
13
+ * Improve inline GraphQL syntax check to raise error if result variable not provided
14
+ * Add autocorrector for UndefinedObject (Unused Argument offense) (FIXME: for N unused arguments in the same line it needs to be invoked N times)
15
+ * Add autocorrector for InvalidArgs - remove duplicates arguments
16
+ * Do not report ConvertIncludeToRender offenses for valid use cases (using `break` and using variable as a template name)
17
+ * Add IncludeInRender check
18
+ * Improve autocorrector for UndefinedObject's missing argument error - if variable is defined, it will be passed instead of hardcoding null
19
+ * Re-enable autocorrector for ConvertIncludeToRender
20
+ * Make UndefinedObject more clever - it will report undefined object if variable is used before declaration
21
+
1
22
  v0.4.8 / 2023-12-20
2
23
  ==================
3
24
 
data/README.md CHANGED
@@ -11,7 +11,7 @@ PlatformOS Check currently checks for the following:
11
11
  ✅ Liquid syntax errors
12
12
  ✅ JSON syntax errors
13
13
  ✅ Missing partials and graphqls
14
- ✅ Unused `{% assign ... %}`
14
+ ✅ Unused variables (via `{% assign var = ... %}`, {% function var = ... %} etc.)
15
15
  ✅ Unused partials
16
16
  ✅ Template length
17
17
  ✅ Deprecated tags
@@ -23,14 +23,14 @@ PlatformOS Check currently checks for the following:
23
23
  ✅ Deprecated filters
24
24
  ✅ Missing `platformos-check-enable` comment
25
25
  ✅ Invalid arguments provided to `{% graphql %}` tags
26
+ ✅ Missing `authenticity_token` in `<form>`
27
+ ✅ Unreachable code
26
28
 
27
29
  As well as checks that prevent easy to spot performance problems:
28
30
 
31
+ ✅ [GraphQL in for loop](/docs/checks/graphql_in_for_loop.md)
29
32
  ✅ Use of [parser-blocking](/docs/checks/parser_blocking_javascript.md) JavaScript
30
- ✅ [Use of non-platformOS domains for assets](/docs/checks/remote_asset.md)
31
33
  ✅ [Missing width and height attributes on `img` tags](/docs/checks/img_width_and_height.md)
32
- ✅ [Too much JavaScript](/docs/checks/asset_size_javascript.md)
33
- ✅ [Too much CSS](/docs/checks/asset_size_css.md)
34
34
 
35
35
  For detailed descriptions and configuration options, [take a look at the complete list.](/docs/checks/)
36
36
 
@@ -52,12 +52,21 @@ With more to come! Suggestions welcome ([create an issue](https://github.com/Pla
52
52
  ### Install ruby and platform-check gem
53
53
 
54
54
  1. Download the latest version of Ruby - https://www.ruby-lang.org/en/documentation/installation/
55
+
56
+ Verify that you've installed at least version 3.2:
57
+
58
+ `ruby -v`
59
+
60
+ ⚠️ **Note:** You might need to restart the terminal after installing.
61
+ ⚠️ **Note:*** Please make sure you install ruby for your user, not the root
62
+
55
63
  2. Install platformos-check gem
56
64
 
57
65
  `gem install platformos-check`
58
66
 
59
67
  You can verify the installation was successful by invoking `platformos-check --version`. If you chose this method, use `platformos-check-language-server` as a path to your language server instead of `/Users/<username/platformos-check-language-server`
60
68
 
69
+ ⚠️ **Note:*** Please make sure you install the gem for your user, not the root - i.e. without `sudo`
61
70
 
62
71
  ### Using Docker
63
72
 
data/config/default.yml CHANGED
@@ -15,6 +15,10 @@ ConvertIncludeToRender:
15
15
  enabled: true
16
16
  ignore: []
17
17
 
18
+ IncludeInRender:
19
+ enabled: true
20
+ ignore: []
21
+
18
22
  LiquidTag:
19
23
  enabled: true
20
24
  ignore: []
@@ -69,7 +73,6 @@ ValidYaml:
69
73
  UndefinedObject:
70
74
  enabled: true
71
75
  ignore: []
72
- config_type: :default
73
76
 
74
77
  DeprecatedFilter:
75
78
  enabled: true
@@ -30,6 +30,12 @@ This check is aimed at ensuring you have not forgotten to start the path with /.
30
30
  </form>
31
31
  ```
32
32
 
33
+ ```liquid
34
+ <form action="https://example.com/external">
35
+ ...
36
+ </form>
37
+ ```
38
+
33
39
  ## Check Options
34
40
 
35
41
  The default configuration for this check is the following:
@@ -3,7 +3,7 @@
3
3
  In platformOS all POST/PATCH/PUT/DELETE requests are protected from [CSRF Attacks][csrf-attack] through [authenticity_token][page-csrf]
4
4
  Form action defines the endpoint to which browser will make a request after submitting it.
5
5
 
6
- As a general rule you should include hidden input `<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">` in every form. Missing it will result in session invalidation and any logged in user will be automatically logged out.
6
+ As a general rule you should include hidden input `<input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">` in every form. Missing it will result in session invalidation and the logged in user will be automatically logged out.
7
7
 
8
8
  ## Check Details
9
9
 
@@ -12,18 +12,37 @@ This check is aimed at ensuring you have not forgotten to include authenticity_t
12
12
  :-1: Examples of **incorrect** code for this check:
13
13
 
14
14
  ```liquid
15
- <form action="dummy/create">
15
+ <form action="/dummy/create" method="post">
16
16
  </form>
17
17
  ```
18
18
 
19
19
  :+1: Examples of **correct** code for this check:
20
20
 
21
+ With token:
21
22
  ```liquid
22
- <form action="/dummy/create">
23
+ <form action="/dummy/create" method="post">
23
24
  <input type="hidden" name="authenticity_token" value="{{ context.authenticity_token }}">
24
25
  </form>
25
26
  ```
26
27
 
28
+ For GET request:
29
+ ```liquid
30
+ <form action="/dummy/create">
31
+ </form>
32
+ ```
33
+
34
+ For external request:
35
+ ```liquid
36
+ <form action="https://example.com/dummy/create" method="post">
37
+ </form>
38
+ ```
39
+
40
+ For parameterized request:
41
+ ```liquid
42
+ <form action="{{ context.constants.MY_REQUEST_URL }}" method="post">
43
+ </form>
44
+ ```
45
+
27
46
  ## Check Options
28
47
 
29
48
  The default configuration for this check is the following:
@@ -0,0 +1,62 @@
1
+ # Reports usage of `include` tag inside `render` (`IncludeInRender`)
2
+
3
+ Runtime error is used when `include` tag is used inside `render` tag.
4
+
5
+ ## Check Details
6
+
7
+ This check is aimed at eliminating the use of `include` tags `render` tag.
8
+
9
+ :-1: Examples of **incorrect** code for this check:
10
+
11
+ ```liquid
12
+ {% liquid
13
+ # app/views/pages/index.liquid
14
+ render 'foo'
15
+ %}
16
+ ```liquid
17
+ {% liquid
18
+ # app/views/partials/foo.liquid
19
+ include 'bar'
20
+ %}
21
+ ```
22
+
23
+ :+1: Examples of **correct** code for this check:
24
+
25
+ ```liquid
26
+ {% liquid
27
+ # app/views/pages/index.liquid
28
+ render 'foo'
29
+ %}
30
+ ```liquid
31
+ {% liquid
32
+ # app/views/partials/foo.liquid
33
+ render 'bar'
34
+ %}
35
+ ```
36
+
37
+ ## Check Options
38
+
39
+ The default configuration for this check is the following:
40
+
41
+ ```yaml
42
+ IncludeInRender:
43
+ enabled: true
44
+ ```
45
+
46
+ ## When Not To Use It
47
+
48
+ It is discouraged to disable this rule.
49
+
50
+ ## Version
51
+
52
+ This check has been introduced in PlatformOS Check 0.4.9.
53
+
54
+ ## Resources
55
+
56
+ - [Deprecated Tags Reference][deprecated]
57
+ - [Rule Source][codesource]
58
+ - [Documentation Source][docsource]
59
+
60
+ [deprecated]: https://documentation.platformos.com/api-reference/liquid/include
61
+ [codesource]: /lib/platformos_check/checks/convert_include_to_render.rb
62
+ [docsource]: /docs/checks/convert_include_to_render.md
@@ -3,15 +3,54 @@
3
3
  module PlatformosCheck
4
4
  # Recommends replacing `include` for `render`
5
5
  class ConvertIncludeToRender < LiquidCheck
6
+ RENDER_INCOMPATIBLE_TAGS = %w[break include].freeze
7
+
6
8
  severity :suggestion
7
9
  category :liquid
8
10
  doc docs_url(__FILE__)
9
11
 
12
+ def initialize
13
+ @processed_files = {}
14
+ end
15
+
10
16
  def on_include(node)
17
+ return if allowed_usecase?(node)
18
+
11
19
  add_offense("`include` is deprecated - convert it to `render`", node:) do |corrector|
12
- # We need to fix #445 and pass the variables from the context or don't replace at all.
13
- # corrector.replace(node, "render \'#{node.value.template_name_expr}\' ")
20
+ match = node.markup.match(/(?<include>include\s*)/)
21
+ corrector.replace(node, node.markup.sub(match[:include], 'render '), node.start_index...node.end_index)
22
+ end
23
+ end
24
+
25
+ protected
26
+
27
+ def allowed_usecase?(node)
28
+ return true if name_is_variable?(node)
29
+ return true if include_node_contains_render_incompatible_tag?(root_node_from_include(node.value.template_name_expr))
30
+
31
+ false
32
+ end
33
+
34
+ def name_is_variable?(node)
35
+ !node.value.template_name_expr.is_a?(String)
36
+ end
37
+
38
+ def include_node_contains_render_incompatible_tag?(node)
39
+ return false if node.nil?
40
+
41
+ node.nodelist.any? do |n|
42
+ if RENDER_INCOMPATIBLE_TAGS.include?(n.respond_to?(:tag_name) && n.tag_name)
43
+ true
44
+ elsif n.respond_to?(:nodelist) && n.nodelist
45
+ include_node_contains_render_incompatible_tag?(n)
46
+ else
47
+ false
48
+ end
14
49
  end
15
50
  end
51
+
52
+ def root_node_from_include(path)
53
+ @platformos_app.grouped_files[PlatformosCheck::PartialFile][path]&.parse&.root
54
+ end
16
55
  end
17
56
  end
@@ -6,11 +6,13 @@ module PlatformosCheck
6
6
  categories :html
7
7
  doc docs_url(__FILE__)
8
8
 
9
+ VALID_ACTION_START = ['/', '{%', '{{', '#', 'http'].freeze
10
+
9
11
  def on_form(node)
10
12
  action = node.attributes["action"]&.strip
11
13
  return if action.nil?
12
14
  return if action.empty?
13
- return if action.start_with?('/', '{{')
15
+ return if action.start_with?(*VALID_ACTION_START)
14
16
 
15
17
  add_offense("Use action=\"/#{action}\" (start with /) to ensure the form can be submitted multiple times in case of validation errors", node:)
16
18
  end
@@ -9,6 +9,9 @@ module PlatformosCheck
9
9
  AUTHENTICITY_TOKEN_VALUE = /\A\s*{{\s*context\.authenticity_token\s*}}\s*\z/
10
10
 
11
11
  def on_form(node)
12
+ return if method_is_get(node.attributes['method'])
13
+ return unless action_is_relative_url(node.attributes['action'])
14
+
12
15
  authenticity_toke_inputs = node.children.select { |c| c.name == 'input' && c.attributes['name'] == 'authenticity_token' && c.attributes['value']&.match?(AUTHENTICITY_TOKEN_VALUE) }
13
16
  return if authenticity_toke_inputs.size == 1
14
17
  return add_offense('Duplicated authenticity_token inputs', node:) if authenticity_toke_inputs.size > 1
@@ -17,5 +20,22 @@ module PlatformosCheck
17
20
  corrector.insert_after(node, "\n<input type=\"hidden\" name=\"authenticity_token\" value=\"{{ context.authenticity_token }}\">")
18
21
  end
19
22
  end
23
+
24
+ protected
25
+
26
+ def method_is_get(method)
27
+ return true if method.nil?
28
+
29
+ method = method.downcase.strip
30
+ return true if method == ''
31
+
32
+ method == 'get'
33
+ end
34
+
35
+ def action_is_relative_url(action)
36
+ return true if action.nil?
37
+
38
+ action.lstrip[0] == '/'
39
+ end
20
40
  end
21
41
  end
@@ -6,13 +6,17 @@ module PlatformosCheck
6
6
  categories :html, :performance
7
7
  doc docs_url(__FILE__)
8
8
 
9
- ACCEPTED_LOADING_VALUES = %w[lazy eager]
9
+ ACCEPTED_LOADING_VALUES = Set.new(%w[lazy eager]).freeze
10
+ LOADING_DEFAULT_ATTRIBUTE = ' loading="eager"'
10
11
 
11
12
  def on_img(node)
12
13
  loading = node.attributes["loading"]&.downcase
13
14
  return if ACCEPTED_LOADING_VALUES.include?(loading)
14
15
 
15
- add_offense("Use loading=\"eager\" for images visible in the viewport on load and loading=\"lazy\" for others", node:)
16
+ add_offense("Use loading=\"eager\" for images visible in the viewport on load and loading=\"lazy\" for others", node:) do |corrector|
17
+ start_pos = node.start_index + node.markup.index('>')
18
+ corrector.insert_after(node, LOADING_DEFAULT_ATTRIBUTE, start_pos...start_pos)
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ # Recommends replacing `include` for `render`
5
+ class IncludeInRender < LiquidCheck
6
+ severity :error
7
+ category :liquid
8
+ doc docs_url(__FILE__)
9
+
10
+ def initialize
11
+ @processed_files = {}
12
+ end
13
+
14
+ def on_render(node)
15
+ path = node.value.template_name_expr
16
+ return unless include_tag_in_render?(root_node_for_render(path))
17
+
18
+ add_offense("`render` context does not allow to use `include`, either remove all includes from `#{app_file_for_path(path).relative_path}` or change `render` to `include`", node:)
19
+ end
20
+
21
+ protected
22
+
23
+ def include_tag_in_render?(node)
24
+ return false if node.nil?
25
+
26
+ node.nodelist.any? do |n|
27
+ if n.respond_to?(:tag_name) && n.tag_name == 'include'
28
+ true
29
+ elsif n.respond_to?(:nodelist) && n.nodelist
30
+ include_tag_in_render?(n)
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
36
+
37
+ def root_node_for_render(path)
38
+ app_file_for_path(path)&.parse&.root
39
+ end
40
+
41
+ def app_file_for_path(path)
42
+ @platformos_app.grouped_files[PlatformosCheck::PartialFile][path]
43
+ end
44
+ end
45
+ end
@@ -42,7 +42,10 @@ module PlatformosCheck
42
42
 
43
43
  def add_duplicated_key_offense(node)
44
44
  node.value.duplicated_attrs.each do |duplicated_arg|
45
- add_offense("Duplicated argument `#{duplicated_arg}`", node:)
45
+ add_offense("Duplicated argument `#{duplicated_arg}`", node:) do |corrector|
46
+ match = node.markup.match(/(?<attribute>,?\s*#{duplicated_arg}\s*:\s*#{Liquid::QuotedFragment})\s*/)
47
+ corrector.replace(node, node.markup.sub(match[:attribute], ''), node.start_index...node.end_index)
48
+ end
46
49
  end
47
50
  end
48
51
  end
@@ -6,6 +6,9 @@ module PlatformosCheck
6
6
  doc docs_url(__FILE__)
7
7
  severity :error
8
8
 
9
+ NOTIFICATION_GLOBAL_OBJECTS = %w[data response form].freeze
10
+ FORM_GLOBAL_OBJECTS = %w[form form_builder].freeze
11
+
9
12
  class TemplateInfo
10
13
  def initialize(app_file: nil)
11
14
  @all_variable_lookups = {}
@@ -19,7 +22,8 @@ module PlatformosCheck
19
22
  attr_reader :all_assigns, :all_captures, :all_forloops, :app_file, :all_renders
20
23
 
21
24
  def add_render(name:, node:)
22
- @all_renders[name] = node
25
+ @all_renders[name] ||= []
26
+ @all_renders[name] << node
23
27
  end
24
28
 
25
29
  def add_variable_lookup(name:, node:)
@@ -39,8 +43,10 @@ module PlatformosCheck
39
43
  end
40
44
 
41
45
  def each_partial
42
- @all_renders.each do |(name, info)|
43
- yield [name, info]
46
+ @all_renders.each do |(name, nodes)|
47
+ nodes.each do |node|
48
+ yield [name, node]
49
+ end
44
50
  end
45
51
  end
46
52
 
@@ -56,14 +62,17 @@ module PlatformosCheck
56
62
  yield [key, info]
57
63
  end
58
64
  end
65
+
66
+ def first_declaration(name)
67
+ [all_assigns[name], all_captures[name]].compact.sort_by(&:line_number).first
68
+ end
59
69
  end
60
70
 
61
71
  def self.single_file(**_args)
62
72
  true
63
73
  end
64
74
 
65
- def initialize(config_type: :default)
66
- @config_type = config_type
75
+ def initialize
67
76
  @files = {}
68
77
  end
69
78
 
@@ -72,15 +81,15 @@ module PlatformosCheck
72
81
  end
73
82
 
74
83
  def on_assign(node)
75
- @files[node.app_file.name].all_assigns[node.value.to] = node
84
+ @files[node.app_file.name].all_assigns[node.value.to] ||= node
76
85
  end
77
86
 
78
87
  def on_capture(node)
79
- @files[node.app_file.name].all_captures[node.value.instance_variable_get(:@to)] = node
88
+ @files[node.app_file.name].all_captures[node.value.instance_variable_get(:@to)] ||= node
80
89
  end
81
90
 
82
91
  def on_parse_json(node)
83
- @files[node.app_file.name].all_captures[node.value.to] = node
92
+ @files[node.app_file.name].all_captures[node.value.to] ||= node
84
93
  end
85
94
 
86
95
  def on_for(node)
@@ -102,7 +111,7 @@ module PlatformosCheck
102
111
  end
103
112
 
104
113
  def on_function(node)
105
- @files[node.app_file.name].all_assigns[node.value.to] = node
114
+ @files[node.app_file.name].all_assigns[node.value.to] ||= node
106
115
 
107
116
  return unless node.value.from.is_a?(String)
108
117
 
@@ -113,13 +122,13 @@ module PlatformosCheck
113
122
  end
114
123
 
115
124
  def on_graphql(node)
116
- @files[node.app_file.name].all_assigns[node.value.to] = node
125
+ @files[node.app_file.name].all_assigns[node.value.to] ||= node
117
126
  end
118
127
 
119
128
  def on_background(node)
120
129
  return unless node.value.partial_syntax
121
130
 
122
- @files[node.app_file.name].all_assigns[node.value.to] = node
131
+ @files[node.app_file.name].all_assigns[node.value.to] ||= node
123
132
 
124
133
  return unless node.value.partial_name.is_a?(String)
125
134
 
@@ -155,10 +164,10 @@ module PlatformosCheck
155
164
  each_template do |(_name, info)|
156
165
  if info.app_file.notification?
157
166
  # NOTE: `data` comes from graphql for notifications
158
- check_object(info, all_global_objects + %w[data response form])
167
+ check_object(info, all_global_objects + NOTIFICATION_GLOBAL_OBJECTS)
159
168
  elsif info.app_file.form?
160
169
  # NOTE: `data` comes from graphql for notifications
161
- check_object(info, all_global_objects + %w[form form_builder])
170
+ check_object(info, all_global_objects + FORM_GLOBAL_OBJECTS)
162
171
  else
163
172
  check_object(info, all_global_objects)
164
173
  end
@@ -167,42 +176,37 @@ module PlatformosCheck
167
176
 
168
177
  private
169
178
 
170
- attr_reader :config_type
171
-
172
179
  def each_template
173
180
  @files.each do |(name, info)|
174
181
  yield [name, info]
175
182
  end
176
183
  end
177
184
 
178
- def check_object(info, all_global_objects, render_node = nil, visited_partials = Set.new, level = 0)
185
+ def check_object(info, all_global_objects, render_node = nil, level = 0)
179
186
  return if level > 1
180
187
 
181
188
  check_undefined(info, all_global_objects, render_node) unless info.app_file.partial? && render_node.nil? # ||
182
189
 
183
190
  info.each_partial do |(partial_name, node)|
184
- next if visited_partials.include?(partial_name)
191
+ next unless @files[partial_name] # NOTE: undefined partial
185
192
 
186
193
  partial_info = @files[partial_name]
187
-
188
- next unless partial_info # NOTE: undefined partial
189
-
190
194
  partial_variables = node.value.attributes.keys +
191
195
  [node.value.instance_variable_get(:@alias_name)]
192
- visited_partials << partial_name
193
- check_object(partial_info, all_global_objects + partial_variables, node, visited_partials, level + 1)
196
+
197
+ check_object(partial_info, all_global_objects + partial_variables, node, level + 1)
194
198
  end
195
199
  end
196
200
 
197
201
  def check_undefined(info, all_global_objects, render_node)
198
- all_variables = info.all_variables
199
202
  potentially_unused_variables = render_node.value.attributes.keys if render_node
203
+ missing_arguments = []
200
204
  info.each_variable_lookup(!!render_node) do |(key, node)|
201
205
  name, line_number = key
202
206
 
203
207
  potentially_unused_variables&.delete(name)
204
208
 
205
- next if all_variables.include?(name)
209
+ next if info.all_variables.include?(name) && variable_declared_before_used?(name, info, line_number)
206
210
  next if all_global_objects.include?(name)
207
211
 
208
212
  node = node.parent
@@ -211,7 +215,7 @@ module PlatformosCheck
211
215
  next if node.variable? && node.filters.any? { |(filter_name)| filter_name == "default" }
212
216
 
213
217
  if render_node
214
- add_offense("Missing argument `#{name}`", node: render_node)
218
+ missing_arguments << name
215
219
  elsif !info.app_file.partial?
216
220
  add_offense("Undefined object `#{name}`", node:, line_number:)
217
221
  end
@@ -219,8 +223,33 @@ module PlatformosCheck
219
223
 
220
224
  potentially_unused_variables -= render_node.value.internal_attributes if render_node && render_node.value.respond_to?(:internal_attributes)
221
225
  potentially_unused_variables&.each do |name|
222
- add_offense("Unused argument `#{name}`", node: render_node)
226
+ add_offense("Unused argument `#{name}`", node: render_node) do |corrector|
227
+ match = render_node.markup.match(/(?<attribute>,?\s*#{name}\s*:\s*#{Liquid::QuotedFragment})\s*/)
228
+
229
+ corrector.replace(render_node, render_node.markup.sub(match[:attribute], ''), render_node.start_index...render_node.end_index)
230
+ end
223
231
  end
232
+
233
+ return if missing_arguments.empty?
234
+
235
+ add_offense("Missing arguments: #{missing_arguments.map { |name| "`#{name}`" }.join(', ')}", node: render_node) do |corrector|
236
+ new_attributes = ''
237
+ missing_arguments.each do |name|
238
+ new_attributes += ", #{name}: "
239
+ new_attributes += @files[render_node.app_file.name].all_assigns.key?(name) ? name : 'null'
240
+ end
241
+
242
+ start_pos = render_node.end_index
243
+ start_pos -= 1 while start_pos > 0 && render_node.source[start_pos - 1] == ' '
244
+ corrector.replace(render_node, new_attributes, start_pos...start_pos)
245
+ end
246
+ end
247
+
248
+ def variable_declared_before_used?(name, info, line_number_when_used)
249
+ declaration = info.first_declaration(name)
250
+ return true if declaration.nil?
251
+
252
+ declaration.line_number <= line_number_when_used
224
253
  end
225
254
  end
226
255
  end
@@ -8,7 +8,7 @@ module PlatformosCheck
8
8
 
9
9
  FLOW_COMMAND = %i[break continue return]
10
10
  CONDITION_TYPES = Set.new(%i[condition else_condition])
11
- INCLUDE_FLOW_COMMAND = %w[break]
11
+ INCLUDE_FLOW_COMMAND = %w[break].freeze
12
12
 
13
13
  def on_document(node)
14
14
  @processed_files = {}
@@ -94,16 +94,16 @@ module PlatformosCheck
94
94
  @processed_files[path]
95
95
  end
96
96
 
97
- def include_node_contains_flow_command?(root)
98
- return false if root.nil?
97
+ def include_node_contains_flow_command?(node)
98
+ return false if node.nil?
99
99
 
100
- root.nodelist.any? do |node|
101
- if INCLUDE_FLOW_COMMAND.include?(node.respond_to?(:tag_name) && node.tag_name)
100
+ node.nodelist.any? do |n|
101
+ if INCLUDE_FLOW_COMMAND.include?(n.respond_to?(:tag_name) && n.tag_name)
102
102
  true
103
- elsif node.respond_to?(:nodelist) && node.nodelist
104
- include_node_contains_flow_command?(node)
105
- elsif node.respond_to?(:tag_name) && node.tag_name == 'include' && node.template_name_expr.is_a?(String)
106
- evaluate_include(node.template_name_expr)
103
+ elsif n.respond_to?(:nodelist) && n.nodelist
104
+ include_node_contains_flow_command?(n)
105
+ elsif n.respond_to?(:tag_name) && n.tag_name == 'include' && n.template_name_expr.is_a?(String)
106
+ evaluate_include(n.template_name_expr)
107
107
  else
108
108
  false
109
109
  end
@@ -7,6 +7,13 @@ module PlatformosCheck
7
7
  category :liquid
8
8
  doc docs_url(__FILE__)
9
9
 
10
+ TAGS_FOR_AUTO_VARIABLE_PREPEND = Set.new(%i[graphql function background]).freeze
11
+ FILTERS_THAT_MODIFY_OBJECT = Set.new(%w[array_add add_to_array
12
+ prepend_to_array array_prepend
13
+ assign_to_hash_key hash_add_key add_hash_key
14
+ remove_hash_key hash_delete_key delete_hash_key]).freeze
15
+ PREPEND_CHARACTER = '_'
16
+
10
17
  class TemplateInfo < Struct.new(:used_assigns, :assign_nodes, :includes)
11
18
  def collect_used_assigns(templates, visited = Set.new)
12
19
  collected = used_assigns
@@ -34,7 +41,8 @@ module PlatformosCheck
34
41
  end
35
42
 
36
43
  def on_assign(node)
37
- return if ignore_underscored?(node)
44
+ return if ignore_prepended?(node)
45
+ return if node.value.from.filters.any? { |filter_name, *_arguments| FILTERS_THAT_MODIFY_OBJECT.include?(filter_name) }
38
46
 
39
47
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
40
48
  end
@@ -44,13 +52,21 @@ module PlatformosCheck
44
52
  end
45
53
 
46
54
  def on_function(node)
47
- return if ignore_underscored?(node)
55
+ return if ignore_prepended?(node)
48
56
 
49
57
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
50
58
  end
51
59
 
52
60
  def on_graphql(node)
53
- return if ignore_underscored?(node)
61
+ return if node.value.to.nil?
62
+ return if ignore_prepended?(node)
63
+
64
+ @templates[node.app_file.name].assign_nodes[node.value.to] = node
65
+ end
66
+
67
+ def on_background(node)
68
+ return if node.value.to.nil?
69
+ return if ignore_prepended?(node)
54
70
 
55
71
  @templates[node.app_file.name].assign_nodes[node.value.to] = node
56
72
  end
@@ -77,25 +93,8 @@ module PlatformosCheck
77
93
  next if used.include?(name)
78
94
 
79
95
  add_offense("`#{name}` is never used", node:) do |corrector|
80
- case node.type_name
81
- when :graphql
82
- offset = node.markup.match(/^graphql\s+/)[0].size
83
-
84
- corrector.insert_before(
85
- node,
86
- '_',
87
- (node.start_index + offset)...(node.start_index + offset)
88
- )
89
- when :function
90
- offset = node.markup.match(/^function\s+/)[0].size
91
-
92
- corrector.insert_before(
93
- node,
94
- '_',
95
- (node.start_index + offset)...(node.start_index + offset)
96
- )
97
- when :parse_json
98
- # noop
96
+ if TAGS_FOR_AUTO_VARIABLE_PREPEND.include?(node.type_name)
97
+ prepend_variable(node, corrector)
99
98
  else
100
99
  corrector.remove(node)
101
100
  end
@@ -106,8 +105,18 @@ module PlatformosCheck
106
105
 
107
106
  private
108
107
 
109
- def ignore_underscored?(node)
110
- node.value.to.start_with?('_')
108
+ def ignore_prepended?(node)
109
+ node.value.to.start_with?(PREPEND_CHARACTER)
110
+ end
111
+
112
+ def prepend_variable(node, corrector)
113
+ offset = node.markup.match(/^#{node.type_name}\s+/)[0].size
114
+
115
+ corrector.insert_before(
116
+ node,
117
+ PREPEND_CHARACTER,
118
+ (node.start_index + offset)...(node.start_index + offset)
119
+ )
111
120
  end
112
121
  end
113
122
  end
@@ -188,7 +188,7 @@ module PlatformosCheck
188
188
  def check(out_stream = STDOUT)
189
189
  update_docs
190
190
 
191
- warn "Checking #{@config.root} ..."
191
+ warn "Checking #{@config.root}:"
192
192
  storage = PlatformosCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
193
193
  raise Abort, "No platformos_app files found." if storage.platformos_app.all.empty?
194
194
 
@@ -71,6 +71,10 @@ module PlatformosCheck
71
71
  end
72
72
  end
73
73
 
74
+ def optional_arguments
75
+ @optional_arguments ||= defined_arguments - required_arguments
76
+ end
77
+
74
78
  def defined_arguments
75
79
  @defined_arguments ||= variables.map(&:name)
76
80
  end
@@ -5,6 +5,7 @@ module PlatformosCheck
5
5
  class Graphql < Base
6
6
  QUERY_NAME_SYNTAX = /(#{Liquid::VariableSignature}+)\s*=\s*(.*)\s*/om
7
7
  INLINE_SYNTAX = /(#{Liquid::QuotedFragment}+)(\s*(#{Liquid::QuotedFragment}+))?/o
8
+ INLINE_SYNTAX_WITHOUT_RESULT_VARIABLE = /\A([\w\-\.\[\]])+\s*:\s*/om
8
9
  CLOSE_TAG_SYNTAX = /\A(.*)(?-mix:\{%-?)\s*(\w+)\s*(.*)?(?-mix:%\})\z/m # based on Liquid::Raw::FullTokenPossiblyInvalid
9
10
 
10
11
  attr_reader :to, :from, :inline_query, :value_expr, :partial_name, :attributes_expr, :attributes
@@ -28,6 +29,8 @@ module PlatformosCheck
28
29
  @partial_name = value_expr
29
30
  @from = Liquid::Variable.new(after_assign_markup.join('|'), options)
30
31
  elsif INLINE_SYNTAX.match?(markup)
32
+ raise Liquid::SyntaxError, 'Invalid syntax for inline graphql tag - missing result name. Valid syntax: graphql result, arg1: var1, ...' if markup.match?(INLINE_SYNTAX_WITHOUT_RESULT_VARIABLE)
33
+
31
34
  @inline_query = true
32
35
  parse_markup(tag_name, markup)
33
36
  @attributes = attributes_expr.keys
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlatformosCheck
4
- VERSION = "0.4.8"
4
+ VERSION = "0.4.9"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: platformos-check
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.8
4
+ version: 0.4.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Bliszczyk
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2023-12-29 00:00:00.000000000 Z
13
+ date: 2024-01-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: graphql
@@ -123,6 +123,7 @@ files:
123
123
  - docs/checks/html_parsing_error.md
124
124
  - docs/checks/img_lazy_loading.md
125
125
  - docs/checks/img_width_and_height.md
126
+ - docs/checks/include_in_render.md
126
127
  - docs/checks/invalid_args.md
127
128
  - docs/checks/liquid_tag.md
128
129
  - docs/checks/missing_enable_comment.md
@@ -169,6 +170,7 @@ files:
169
170
  - lib/platformos_check/checks/html_parsing_error.rb
170
171
  - lib/platformos_check/checks/img_lazy_loading.rb
171
172
  - lib/platformos_check/checks/img_width_and_height.rb
173
+ - lib/platformos_check/checks/include_in_render.rb
172
174
  - lib/platformos_check/checks/invalid_args.rb
173
175
  - lib/platformos_check/checks/liquid_tag.rb
174
176
  - lib/platformos_check/checks/missing_enable_comment.rb