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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +13 -4
- data/config/default.yml +4 -1
- data/docs/checks/form_action.md +6 -0
- data/docs/checks/form_authenticity_token.md +22 -3
- data/docs/checks/include_in_render.md +62 -0
- data/lib/platformos_check/checks/convert_include_to_render.rb +41 -2
- data/lib/platformos_check/checks/form_action.rb +3 -1
- data/lib/platformos_check/checks/form_authenticity_token.rb +20 -0
- data/lib/platformos_check/checks/img_lazy_loading.rb +6 -2
- data/lib/platformos_check/checks/include_in_render.rb +45 -0
- data/lib/platformos_check/checks/invalid_args.rb +4 -1
- data/lib/platformos_check/checks/undefined_object.rb +55 -26
- data/lib/platformos_check/checks/unreachable_code.rb +9 -9
- data/lib/platformos_check/checks/unused_assign.rb +33 -24
- data/lib/platformos_check/cli.rb +1 -1
- data/lib/platformos_check/graphql_file.rb +4 -0
- data/lib/platformos_check/tags/graphql.rb +3 -0
- data/lib/platformos_check/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e41eee4e2aa717be9663df4efbc1acf79bc8b571784808b80177f9298fdad9e9
|
4
|
+
data.tar.gz: 6bed8f9e1e3e22dbd02cee00a15d5e9ce1787f7826ee09e3e461fcf0161c681d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/docs/checks/form_action.md
CHANGED
@@ -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
|
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
|
-
|
13
|
-
|
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]
|
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,
|
43
|
-
|
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
|
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]
|
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)]
|
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]
|
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]
|
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]
|
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]
|
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 +
|
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 +
|
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,
|
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
|
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
|
-
|
193
|
-
check_object(partial_info, all_global_objects + partial_variables, node,
|
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
|
-
|
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?(
|
98
|
-
return false if
|
97
|
+
def include_node_contains_flow_command?(node)
|
98
|
+
return false if node.nil?
|
99
99
|
|
100
|
-
|
101
|
-
if INCLUDE_FLOW_COMMAND.include?(
|
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
|
104
|
-
include_node_contains_flow_command?(
|
105
|
-
elsif
|
106
|
-
evaluate_include(
|
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
|
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
|
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
|
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
|
-
|
81
|
-
|
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
|
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
|
data/lib/platformos_check/cli.rb
CHANGED
@@ -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
|
|
@@ -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
|
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.
|
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:
|
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
|