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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aebbd738b1045c874460b7f1de2cf2774297f36136fce816521dda29ce371652
4
- data.tar.gz: c348e3afc68f2a664fe1ad313d41497ad7c977f8de72f3b82df9dda297926c38
3
+ metadata.gz: 579d15b95d0e00e65eb562778f0870bc28698bfe2f6c97ca66e7ca81a18b855c
4
+ data.tar.gz: 2f6afe88f067cccc169d62a9744399b5b11294537dd8c7653ca9d5ed9165f1f3
5
5
  SHA512:
6
- metadata.gz: 368d39315a05ab34eff3797012af56775ca9a5ff4d8209ed0c44aa2916f97fe5740afa859bbfbbdf34409aa40967efe4236b2394eafcdecfc313c97f62a4dba7
7
- data.tar.gz: f912bda1de7e0be84e887ab2f19cde9072e03468ea52265f910e20ff5a7317b7f7f4e6fe83aef690350f21543ffbd495e01c72eddca5c393acfc5500055d6939
6
+ metadata.gz: 2fa86abf31229208eaff48e7f95b212977682bd560234d99d2fa9b3162a33ca7139b8faad2960152951295b056ef41b834e9faa756b0627f02a14034f6d4e468
7
+ data.tar.gz: 18fa38e2613faf4e31dfdf0a432e4fbe3a8ced0b18f8f194698bf73606affd770cf2589dadb852c26c19f5ec31146535e95e863d7c31ab9c8977feb67fa99a07
data/CHANGELOG.md CHANGED
@@ -1,4 +1,9 @@
1
- v0.4.7 / 2023-12-22
1
+ v0.4.8 / 2023-12-20
2
+ ==================
3
+
4
+ * Add GraphqlInForLoop check
5
+
6
+ v0.4.7 / 2023-12-27
2
7
  ==================
3
8
 
4
9
  * Add UnreachableCode check
data/config/default.yml CHANGED
@@ -99,6 +99,10 @@ FormAuthenticityToken:
99
99
  enabled: true
100
100
  ignore: []
101
101
 
102
+ GraphqlInForLoop:
103
+ enabled: true
104
+ ignore: []
105
+
102
106
  HtmlParsingError:
103
107
  enabled: true
104
108
  ignore: []
@@ -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
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlatformosCheck
4
- # Recommends using {% liquid ... %} if 5 or more consecutive {% ... %} are found.
5
4
  class UnreachableCode < LiquidCheck
6
5
  severity :error
7
6
  category :liquid
@@ -68,7 +68,7 @@ module PlatformosCheck
68
68
  end
69
69
 
70
70
  def warnings
71
- @ast.warnings
71
+ parse&.warnings
72
72
  end
73
73
 
74
74
  def root
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PlatformosCheck
4
+ module Tags
5
+ class Context < Base
6
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
7
+ def children
8
+ @node.attributes_expr.values
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PlatformosCheck
4
- VERSION = "0.4.7"
4
+ VERSION = "0.4.8"
5
5
  end
@@ -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.7
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-27 00:00:00.000000000 Z
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