platformos-check 0.4.6 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/config/default.yml +8 -0
- data/docs/checks/form_action.md +1 -1
- data/docs/checks/form_authenticity_token.md +1 -1
- data/docs/checks/graphql_in_for_loop.md +56 -0
- data/docs/checks/unreachable_code.md +66 -0
- data/lib/platformos_check/analyzer.rb +6 -1
- data/lib/platformos_check/checks/graphql_in_for_loop.rb +108 -0
- data/lib/platformos_check/checks/unreachable_code.rb +113 -0
- data/lib/platformos_check/liquid_file.rb +1 -1
- data/lib/platformos_check/tags/context.rb +13 -0
- data/lib/platformos_check/tags.rb +2 -0
- data/lib/platformos_check/version.rb +1 -1
- data/lib/platformos_check.rb +1 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 579d15b95d0e00e65eb562778f0870bc28698bfe2f6c97ca66e7ca81a18b855c
|
4
|
+
data.tar.gz: 2f6afe88f067cccc169d62a9744399b5b11294537dd8c7653ca9d5ed9165f1f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fa86abf31229208eaff48e7f95b212977682bd560234d99d2fa9b3162a33ca7139b8faad2960152951295b056ef41b834e9faa756b0627f02a14034f6d4e468
|
7
|
+
data.tar.gz: 18fa38e2613faf4e31dfdf0a432e4fbe3a8ced0b18f8f194698bf73606affd770cf2589dadb852c26c19f5ec31146535e95e863d7c31ab9c8977feb67fa99a07
|
data/CHANGELOG.md
CHANGED
data/config/default.yml
CHANGED
@@ -50,6 +50,10 @@ UnknownFilter:
|
|
50
50
|
enabled: true
|
51
51
|
ignore: []
|
52
52
|
|
53
|
+
UnreachableCode:
|
54
|
+
enabled: true
|
55
|
+
ignore: []
|
56
|
+
|
53
57
|
UnusedAssign:
|
54
58
|
enabled: true
|
55
59
|
ignore: []
|
@@ -95,6 +99,10 @@ FormAuthenticityToken:
|
|
95
99
|
enabled: true
|
96
100
|
ignore: []
|
97
101
|
|
102
|
+
GraphqlInForLoop:
|
103
|
+
enabled: true
|
104
|
+
ignore: []
|
105
|
+
|
98
106
|
HtmlParsingError:
|
99
107
|
enabled: true
|
100
108
|
ignore: []
|
data/docs/checks/form_action.md
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
# GraphQL in for loop (`GraphqlInForLoop`)
|
2
|
+
|
3
|
+
This check is aimed towards identifying performance problems before they arise. Invoking GraphQL queries/mutations inside the `for loop` can lead to performance issues such as increased database load, higher latency, and inefficient use of network resources. It is particularly problematic when dealing with large datasets or when the number of entities grows.
|
4
|
+
|
5
|
+
Invoking a GraphQL query within a `for loop` might also be a sign of a so called N+1 problem. The N+1 pattern often arises when dealing with relationships between entities. In an attempt to retrieve related data, developers may inadvertently end up executing a large number of queries, resulting in a significant performance overhead.
|
6
|
+
|
7
|
+
To address this problem, developers can use techniques like eager loading, which involves fetching the related data in a single query instead of issuing separate queries for each entity. This helps reduce the number of database round-trips and improves overall system performance. In platformOS the most common technique to fix the N+1 issue is by [using related_records][relalted_records].
|
8
|
+
|
9
|
+
## Check Details
|
10
|
+
|
11
|
+
This check is aimed towards identifying GraphQL queries invoked within a for loop.
|
12
|
+
|
13
|
+
:-1: Examples of **incorrect** code for this check:
|
14
|
+
|
15
|
+
```liquid
|
16
|
+
{% assign arr = 'a,b,c' | split: ','}
|
17
|
+
{% for el in arr %}
|
18
|
+
{% graphql g = 'my/graphql', el: el %}
|
19
|
+
{% print el %}
|
20
|
+
{% endfor %}
|
21
|
+
|
22
|
+
```
|
23
|
+
|
24
|
+
:+1: Examples of **correct** code for this check:
|
25
|
+
|
26
|
+
```liquid
|
27
|
+
{% assign arr = 'a,b,c' | split: ','}
|
28
|
+
{% graphql g = 'my/graphql', arr: arr %}
|
29
|
+
```
|
30
|
+
|
31
|
+
## Check Options
|
32
|
+
|
33
|
+
The default configuration for this check is the following:
|
34
|
+
|
35
|
+
```yaml
|
36
|
+
GraphqlInForLoop:
|
37
|
+
enabled: true
|
38
|
+
```
|
39
|
+
|
40
|
+
## When Not To Use It
|
41
|
+
|
42
|
+
In the perfect world, there should be no cases where disabling this rule is needed - platformOS most likely already has a way to solve a problem without using GraphQL query / mutation in the for loop.
|
43
|
+
|
44
|
+
## Version
|
45
|
+
|
46
|
+
This check has been introduced in PlatformOS Check 0.4.9.
|
47
|
+
|
48
|
+
## Resources
|
49
|
+
|
50
|
+
- [Rule Source][codesource]
|
51
|
+
- [Documentation Source][docsource]
|
52
|
+
- [platformOS - loading related records][related_records]
|
53
|
+
|
54
|
+
[codesource]: /lib/platformos_check/checks/graphql_in_for_loop.rb
|
55
|
+
[docsource]: /docs/checks/graphql_in_for_loop.md
|
56
|
+
[related_records]: https://documentation.platformos.com/developer-guide/records/loading-related-records
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Unreachable code (`UnreachableCode`)
|
2
|
+
|
3
|
+
Unreachable code reports code that will never be reached, no matter what.
|
4
|
+
|
5
|
+
## Check Details
|
6
|
+
|
7
|
+
This check is aimed at ensuring you will not accidentally write code that will never be reached.
|
8
|
+
|
9
|
+
:-1: Examples of **incorrect** code for this check:
|
10
|
+
|
11
|
+
```liquid
|
12
|
+
assign x = "hello"
|
13
|
+
break
|
14
|
+
log x
|
15
|
+
```
|
16
|
+
|
17
|
+
```liquid
|
18
|
+
if x
|
19
|
+
log x
|
20
|
+
else
|
21
|
+
break
|
22
|
+
log "Stop"
|
23
|
+
endif
|
24
|
+
```
|
25
|
+
|
26
|
+
:+1: Examples of **correct** code for this check:
|
27
|
+
|
28
|
+
```liquid
|
29
|
+
assign x = "hello"
|
30
|
+
log x
|
31
|
+
break
|
32
|
+
```
|
33
|
+
|
34
|
+
```liquid
|
35
|
+
if x
|
36
|
+
log x
|
37
|
+
else
|
38
|
+
log "Stop"
|
39
|
+
break
|
40
|
+
endif
|
41
|
+
```
|
42
|
+
|
43
|
+
## Check Options
|
44
|
+
|
45
|
+
The default configuration for this check is the following:
|
46
|
+
|
47
|
+
```yaml
|
48
|
+
UnreachableCode:
|
49
|
+
enabled: true
|
50
|
+
```
|
51
|
+
|
52
|
+
## When Not To Use It
|
53
|
+
|
54
|
+
There should be no cases where disabling this rule is needed.
|
55
|
+
|
56
|
+
## Version
|
57
|
+
|
58
|
+
This check has been introduced in PlatformOS Check 0.4.7.
|
59
|
+
|
60
|
+
## Resources
|
61
|
+
|
62
|
+
- [Rule Source][codesource]
|
63
|
+
- [Documentation Source][docsource]
|
64
|
+
|
65
|
+
[codesource]: /lib/platformos_check/checks/unreachable_code.rb
|
66
|
+
[docsource]: /docs/checks/unreachable_code.md
|
@@ -2,13 +2,15 @@
|
|
2
2
|
|
3
3
|
module PlatformosCheck
|
4
4
|
class Analyzer
|
5
|
-
def initialize(platformos_app, checks = Check.all.map(&:new), auto_correct = false)
|
5
|
+
def initialize(platformos_app, checks = Check.all.map(&:new), auto_correct = false, store_warnings = false)
|
6
6
|
@platformos_app = platformos_app
|
7
7
|
@auto_correct = auto_correct
|
8
|
+
@store_warnings = store_warnings
|
8
9
|
|
9
10
|
@liquid_checks = Checks.new
|
10
11
|
@yaml_checks = Checks.new
|
11
12
|
@html_checks = Checks.new
|
13
|
+
@warnings = {}
|
12
14
|
|
13
15
|
checks.each do |check|
|
14
16
|
check.platformos_app = @platformos_app
|
@@ -30,6 +32,8 @@ module PlatformosCheck
|
|
30
32
|
@html_checks.flat_map(&:offenses)
|
31
33
|
end
|
32
34
|
|
35
|
+
attr_reader :warnings
|
36
|
+
|
33
37
|
def yaml_file_count
|
34
38
|
@yaml_file_count ||= @platformos_app.yaml.size
|
35
39
|
end
|
@@ -54,6 +58,7 @@ module PlatformosCheck
|
|
54
58
|
yield(liquid_file.relative_path.to_s, i, total_file_count) if block_given?
|
55
59
|
liquid_visitor.visit_liquid_file(liquid_file)
|
56
60
|
html_visitor.visit_liquid_file(liquid_file)
|
61
|
+
@warnings[liquid_file.path] = liquid_file.warnings if @store_warnings && !liquid_file.warnings&.empty?
|
57
62
|
end
|
58
63
|
end
|
59
64
|
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class GraphqlInForLoop < LiquidCheck
|
5
|
+
severity :suggestion
|
6
|
+
categories :liquid, :performance
|
7
|
+
doc docs_url(__FILE__)
|
8
|
+
|
9
|
+
PARTIAL_TAG = %i[render include]
|
10
|
+
OFFENSE_MSG = "Do not invoke GraphQL in a for loop"
|
11
|
+
|
12
|
+
class PartialInfo
|
13
|
+
attr_reader :node, :app_file
|
14
|
+
|
15
|
+
def initialize(node:, app_file:)
|
16
|
+
@node = node
|
17
|
+
@app_file = app_file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.single_file(**_args)
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@partials = []
|
27
|
+
@all_partials = Set.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_for(_node)
|
31
|
+
@in_for = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_graphql(node)
|
35
|
+
add_graphql_offense(node:, graphql_node: node) if should_report?
|
36
|
+
end
|
37
|
+
|
38
|
+
def after_for(_node)
|
39
|
+
@in_for = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_background(_node)
|
43
|
+
@in_background = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def after_background(_node)
|
47
|
+
@in_background = false
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_include(node)
|
51
|
+
return unless should_report?
|
52
|
+
|
53
|
+
add_partial(path: node.value.template_name_expr, node:)
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_render(node)
|
57
|
+
return unless should_report?
|
58
|
+
|
59
|
+
add_partial(path: node.value.template_name_expr, node:)
|
60
|
+
end
|
61
|
+
|
62
|
+
def on_function(node)
|
63
|
+
return unless should_report?
|
64
|
+
|
65
|
+
add_partial(path: node.value.from, node:)
|
66
|
+
end
|
67
|
+
|
68
|
+
def on_end
|
69
|
+
while (partial_info = @partials.shift)
|
70
|
+
report_offense_on_graphql(LiquidNode.new(partial_info.app_file.parse.root, nil, partial_info.app_file), offense_node: partial_info.node)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def add_partial(path:, node:)
|
77
|
+
return unless path.is_a?(String)
|
78
|
+
return if @all_partials.include?(path)
|
79
|
+
return if @platformos_app.grouped_files[PlatformosCheck::PartialFile][path].nil?
|
80
|
+
|
81
|
+
@all_partials << path
|
82
|
+
@partials << PartialInfo.new(node:, app_file: @platformos_app.grouped_files[PlatformosCheck::PartialFile][path])
|
83
|
+
end
|
84
|
+
|
85
|
+
def should_report?
|
86
|
+
@in_for && !@in_background
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_graphql_offense(node:, graphql_node:)
|
90
|
+
return add_offense(OFFENSE_MSG, node:) unless graphql_node.value.partial_name
|
91
|
+
|
92
|
+
partial_name = graphql_node.value.partial_name.is_a?(String) ? graphql_node.value.partial_name : "variable: #{graphql_node.value.partial_name.name}"
|
93
|
+
add_offense("#{OFFENSE_MSG} (#{partial_name})", node:)
|
94
|
+
end
|
95
|
+
|
96
|
+
def report_offense_on_graphql(node, offense_node:)
|
97
|
+
if node.type_name == :graphql
|
98
|
+
add_graphql_offense(node: offense_node, graphql_node: node)
|
99
|
+
elsif PARTIAL_TAG.include?(node.type_name)
|
100
|
+
add_partial(path: node.value.template_name_expr, node: offense_node)
|
101
|
+
elsif node.type_name == :function
|
102
|
+
add_partial(path: node.value.from, node: offense_node)
|
103
|
+
elsif node.children && !node.children.empty?
|
104
|
+
node.children.each { |c| report_offense_on_graphql(c, offense_node:) }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class UnreachableCode < LiquidCheck
|
5
|
+
severity :error
|
6
|
+
category :liquid
|
7
|
+
doc docs_url(__FILE__)
|
8
|
+
|
9
|
+
FLOW_COMMAND = %i[break continue return]
|
10
|
+
CONDITION_TYPES = Set.new(%i[condition else_condition])
|
11
|
+
INCLUDE_FLOW_COMMAND = %w[break]
|
12
|
+
|
13
|
+
def on_document(node)
|
14
|
+
@processed_files = {}
|
15
|
+
check_unreachable_code(node.children)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def check_unreachable_code(nodes)
|
21
|
+
nodes = sanitize_children(nodes)
|
22
|
+
nodes.each_cons(2) do |node1, _node2|
|
23
|
+
next unless flow_expression?(node1)
|
24
|
+
|
25
|
+
add_offense("Unreachable code after `#{node1.type_name}`", node: node1)
|
26
|
+
end
|
27
|
+
flow_expression?(nodes.last)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sanitize_children(children)
|
31
|
+
children.reject { |c| c.value.is_a?(String) && c.value.strip == '' }
|
32
|
+
end
|
33
|
+
|
34
|
+
def flow_expression?(node)
|
35
|
+
return false if node.nil?
|
36
|
+
return true if flow_command?(node)
|
37
|
+
|
38
|
+
case node.type_name
|
39
|
+
when :if, :unless
|
40
|
+
check_if(node)
|
41
|
+
false
|
42
|
+
when :for
|
43
|
+
check_for(node)
|
44
|
+
false
|
45
|
+
when :case
|
46
|
+
check_case(node)
|
47
|
+
false
|
48
|
+
when :try_rc, :try
|
49
|
+
check_try(node)
|
50
|
+
false
|
51
|
+
when :block_body
|
52
|
+
node.children.any? { |c| flow_expression?(c) }
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_if(node)
|
59
|
+
node.children.each do |condition|
|
60
|
+
check_unreachable_code(condition.children.detect(&:block_body?).children)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_for(node)
|
65
|
+
check_unreachable_code(node.children.detect(&:block_body?).children)
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_case(node)
|
69
|
+
node.children.each do |condition|
|
70
|
+
next unless CONDITION_TYPES.include?(condition.type_name)
|
71
|
+
|
72
|
+
check_unreachable_code(condition.children.detect(&:block_body?).children)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def check_try(node)
|
77
|
+
node.children.each do |block_body|
|
78
|
+
check_unreachable_code(block_body.children)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def flow_command?(node)
|
83
|
+
return true if FLOW_COMMAND.include?(node.type_name)
|
84
|
+
|
85
|
+
return evaluate_include(node.value.template_name_expr) if node.type_name == :include && node.value.template_name_expr.is_a?(String)
|
86
|
+
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
def evaluate_include(path)
|
91
|
+
return false unless path.is_a?(String)
|
92
|
+
|
93
|
+
@processed_files[path] ||= include_node_contains_flow_command?(@platformos_app.grouped_files[PlatformosCheck::PartialFile][path]&.parse&.root)
|
94
|
+
@processed_files[path]
|
95
|
+
end
|
96
|
+
|
97
|
+
def include_node_contains_flow_command?(root)
|
98
|
+
return false if root.nil?
|
99
|
+
|
100
|
+
root.nodelist.any? do |node|
|
101
|
+
if INCLUDE_FLOW_COMMAND.include?(node.respond_to?(:tag_name) && node.tag_name)
|
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)
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -53,6 +53,8 @@ module PlatformosCheck
|
|
53
53
|
register_tag('background', Background)
|
54
54
|
register_tag('content_for', ContentFor)
|
55
55
|
register_tag('session', Session)
|
56
|
+
register_tag('context', Context)
|
57
|
+
register_tag('context_rc', Context)
|
56
58
|
register_tag('sign_in', SignIn)
|
57
59
|
register_tag('yield', Yield)
|
58
60
|
register_tag('graphql', Graphql)
|
data/lib/platformos_check.rb
CHANGED
@@ -47,6 +47,7 @@ require_relative "platformos_check/tags/base"
|
|
47
47
|
require_relative "platformos_check/tags/base_block"
|
48
48
|
require_relative "platformos_check/tags/background"
|
49
49
|
require_relative "platformos_check/tags/cache"
|
50
|
+
require_relative "platformos_check/tags/context"
|
50
51
|
require_relative "platformos_check/tags/export"
|
51
52
|
require_relative "platformos_check/tags/form"
|
52
53
|
require_relative "platformos_check/tags/function"
|
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.8
|
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-
|
13
|
+
date: 2023-12-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: graphql
|
@@ -119,6 +119,7 @@ files:
|
|
119
119
|
- docs/checks/deprecated_filter.md
|
120
120
|
- docs/checks/form_action.md
|
121
121
|
- docs/checks/form_authenticity_token.md
|
122
|
+
- docs/checks/graphql_in_for_loop.md
|
122
123
|
- docs/checks/html_parsing_error.md
|
123
124
|
- docs/checks/img_lazy_loading.md
|
124
125
|
- docs/checks/img_width_and_height.md
|
@@ -134,6 +135,7 @@ files:
|
|
134
135
|
- docs/checks/template_length.md
|
135
136
|
- docs/checks/undefined_object.md
|
136
137
|
- docs/checks/unknown_filter.md
|
138
|
+
- docs/checks/unreachable_code.md
|
137
139
|
- docs/checks/unused_assign.md
|
138
140
|
- docs/checks/unused_partial.md
|
139
141
|
- docs/checks/valid_yaml.md
|
@@ -163,6 +165,7 @@ files:
|
|
163
165
|
- lib/platformos_check/checks/deprecated_filter.rb
|
164
166
|
- lib/platformos_check/checks/form_action.rb
|
165
167
|
- lib/platformos_check/checks/form_authenticity_token.rb
|
168
|
+
- lib/platformos_check/checks/graphql_in_for_loop.rb
|
166
169
|
- lib/platformos_check/checks/html_parsing_error.rb
|
167
170
|
- lib/platformos_check/checks/img_lazy_loading.rb
|
168
171
|
- lib/platformos_check/checks/img_width_and_height.rb
|
@@ -178,6 +181,7 @@ files:
|
|
178
181
|
- lib/platformos_check/checks/template_length.rb
|
179
182
|
- lib/platformos_check/checks/undefined_object.rb
|
180
183
|
- lib/platformos_check/checks/unknown_filter.rb
|
184
|
+
- lib/platformos_check/checks/unreachable_code.rb
|
181
185
|
- lib/platformos_check/checks/unused_assign.rb
|
182
186
|
- lib/platformos_check/checks/unused_partial.rb
|
183
187
|
- lib/platformos_check/checks/valid_yaml.rb
|
@@ -314,6 +318,7 @@ files:
|
|
314
318
|
- lib/platformos_check/tags/base_block.rb
|
315
319
|
- lib/platformos_check/tags/base_tag_methods.rb
|
316
320
|
- lib/platformos_check/tags/cache.rb
|
321
|
+
- lib/platformos_check/tags/context.rb
|
317
322
|
- lib/platformos_check/tags/export.rb
|
318
323
|
- lib/platformos_check/tags/form.rb
|
319
324
|
- lib/platformos_check/tags/function.rb
|