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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 737ae774f598b10b46e556298d68e7eafe711c00ef6463c30ba8f8736c6cf42a
4
- data.tar.gz: 443998e3955d4032ab41b2d5189ff42f5a736781b05cf0bdfa2a8dfe1b4fe629
3
+ metadata.gz: 579d15b95d0e00e65eb562778f0870bc28698bfe2f6c97ca66e7ca81a18b855c
4
+ data.tar.gz: 2f6afe88f067cccc169d62a9744399b5b11294537dd8c7653ca9d5ed9165f1f3
5
5
  SHA512:
6
- metadata.gz: 5049fd3616a7721b1d23920c87436ed19a525d96454ef18a93cb22ba210e733a5984c154f3a79778f047769fa059052fc65b3ca56cc02d70e4ee5e108c568b29
7
- data.tar.gz: c7dcafbce9b30fc5fd2b954a080e4f167f24c7fcfbc8ea48b6131b54eb33eb71657f45068075c1ea2f9f6520682a33f94eb207e0a68341e10f9abcda4939b8db
6
+ metadata.gz: 2fa86abf31229208eaff48e7f95b212977682bd560234d99d2fa9b3162a33ca7139b8faad2960152951295b056ef41b834e9faa756b0627f02a14034f6d4e468
7
+ data.tar.gz: 18fa38e2613faf4e31dfdf0a432e4fbe3a8ced0b18f8f194698bf73606affd770cf2589dadb852c26c19f5ec31146535e95e863d7c31ab9c8977feb67fa99a07
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ v0.4.8 / 2023-12-20
2
+ ==================
3
+
4
+ * Add GraphqlInForLoop check
5
+
6
+ v0.4.7 / 2023-12-27
7
+ ==================
8
+
9
+ * Add UnreachableCode check
10
+
1
11
  v0.4.6 / 2023-12-19
2
12
  ==================
3
13
 
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: []
@@ -45,7 +45,7 @@ There should be no cases where disabling this rule is needed.
45
45
 
46
46
  ## Version
47
47
 
48
- This check has been introduced in PlatformOS Check 0.45.0.
48
+ This check has been introduced in PlatformOS Check 0.4.5.
49
49
 
50
50
  ## Resources
51
51
 
@@ -39,7 +39,7 @@ There should be no cases where disabling this rule is needed.
39
39
 
40
40
  ## Version
41
41
 
42
- This check has been introduced in PlatformOS Check 0.46.0.
42
+ This check has been introduced in PlatformOS Check 0.4.6.
43
43
 
44
44
  ## Resources
45
45
 
@@ -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
@@ -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.6"
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.6
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-19 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
@@ -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