platformos-check 0.4.7 → 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 +6 -1
- data/config/default.yml +4 -0
- data/docs/checks/graphql_in_for_loop.md +56 -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 +0 -1
- 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 +5 -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
@@ -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
|
@@ -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
|
@@ -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
|
@@ -164,6 +165,7 @@ files:
|
|
164
165
|
- lib/platformos_check/checks/deprecated_filter.rb
|
165
166
|
- lib/platformos_check/checks/form_action.rb
|
166
167
|
- lib/platformos_check/checks/form_authenticity_token.rb
|
168
|
+
- lib/platformos_check/checks/graphql_in_for_loop.rb
|
167
169
|
- lib/platformos_check/checks/html_parsing_error.rb
|
168
170
|
- lib/platformos_check/checks/img_lazy_loading.rb
|
169
171
|
- lib/platformos_check/checks/img_width_and_height.rb
|
@@ -316,6 +318,7 @@ files:
|
|
316
318
|
- lib/platformos_check/tags/base_block.rb
|
317
319
|
- lib/platformos_check/tags/base_tag_methods.rb
|
318
320
|
- lib/platformos_check/tags/cache.rb
|
321
|
+
- lib/platformos_check/tags/context.rb
|
319
322
|
- lib/platformos_check/tags/export.rb
|
320
323
|
- lib/platformos_check/tags/form.rb
|
321
324
|
- lib/platformos_check/tags/function.rb
|