theme-check 1.6.2 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/data/shopify_liquid/filters.yml +1 -0
  4. data/data/shopify_liquid/tags.yml +9 -9
  5. data/docs/checks/TEMPLATE.md.erb +24 -19
  6. data/exe/theme-check-language-server +0 -4
  7. data/lib/theme_check/analyzer.rb +29 -5
  8. data/lib/theme_check/checks/matching_schema_translations.rb +12 -5
  9. data/lib/theme_check/checks/required_layout_theme_object.rb +9 -4
  10. data/lib/theme_check/checks/translation_key_exists.rb +1 -13
  11. data/lib/theme_check/checks/unused_assign.rb +3 -2
  12. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  13. data/lib/theme_check/corrector.rb +40 -3
  14. data/lib/theme_check/exceptions.rb +1 -0
  15. data/lib/theme_check/file_system_storage.rb +4 -0
  16. data/lib/theme_check/language_server/bridge.rb +142 -0
  17. data/lib/theme_check/language_server/channel.rb +69 -0
  18. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +3 -1
  19. data/lib/theme_check/language_server/diagnostics_engine.rb +125 -0
  20. data/lib/theme_check/language_server/handler.rb +24 -118
  21. data/lib/theme_check/language_server/io_messenger.rb +104 -0
  22. data/lib/theme_check/language_server/messenger.rb +27 -0
  23. data/lib/theme_check/language_server/protocol.rb +4 -0
  24. data/lib/theme_check/language_server/server.rb +111 -103
  25. data/lib/theme_check/language_server.rb +6 -1
  26. data/lib/theme_check/liquid_node.rb +33 -0
  27. data/lib/theme_check/locale_diff.rb +36 -10
  28. data/lib/theme_check/position.rb +4 -4
  29. data/lib/theme_check/shopify_liquid/system_translations.rb +35 -0
  30. data/lib/theme_check/shopify_liquid/tag.rb +19 -1
  31. data/lib/theme_check/shopify_liquid.rb +1 -0
  32. data/lib/theme_check/tags.rb +0 -1
  33. data/lib/theme_check/theme_file_rewriter.rb +13 -0
  34. data/lib/theme_check/version.rb +1 -1
  35. data/lib/theme_check.rb +4 -0
  36. metadata +8 -2
@@ -13,141 +13,149 @@ module ThemeCheck
13
13
  attr_reader :should_raise_errors
14
14
 
15
15
  def initialize(
16
- in_stream: STDIN,
17
- out_stream: STDOUT,
18
- err_stream: STDERR,
19
- should_raise_errors: false
16
+ messenger:,
17
+ should_raise_errors: false,
18
+ number_of_threads: 2
20
19
  )
21
- validate!([in_stream, out_stream, err_stream])
20
+ # This is what does the IO
21
+ @messenger = messenger
22
22
 
23
- @handler = Handler.new(self)
24
- @in = in_stream
25
- @out = out_stream
26
- @err = err_stream
23
+ # This is what you use to communicate with the language client
24
+ @bridge = Bridge.new(@messenger)
27
25
 
28
- # Because programming is fun,
29
- #
30
- # Ruby on Windows turns \n into \r\n. Which means that \r\n
31
- # gets turned into \r\r\n. Which means that the protocol
32
- # breaks on windows unless we turn STDOUT into binary mode.
33
- #
34
- # Hours wasted: 9.
35
- @out.binmode
26
+ # The handler handles messages from the language client
27
+ @handler = Handler.new(@bridge)
36
28
 
37
- @out.sync = true # do not buffer
38
- @err.sync = true # do not buffer
29
+ # The queue holds the JSON RPC messages
30
+ @queue = Queue.new
39
31
 
40
- @should_raise_errors = should_raise_errors
41
- end
32
+ # The JSON RPC thread pushes messages onto the queue
33
+ @json_rpc_thread = nil
42
34
 
43
- def listen
44
- loop do
45
- process_request
46
-
47
- # support ctrl+c and stuff
48
- rescue SignalException, DoneStreaming
49
- cleanup
50
- return 0
51
-
52
- rescue Exception => e # rubocop:disable Lint/RescueException
53
- raise e if should_raise_errors
54
- log(e)
55
- log(e.backtrace)
56
- return 1
57
- end
58
- end
35
+ # The handler threads read messages from the queue
36
+ @number_of_threads = number_of_threads
37
+ @handlers = []
59
38
 
60
- def send_response(response)
61
- response_body = JSON.dump(response)
62
- log(JSON.pretty_generate(response)) if $DEBUG
39
+ # The error queue holds blocks the main thread. When filled, we exit the program.
40
+ @error = SizedQueue.new(number_of_threads)
63
41
 
64
- @out.write("Content-Length: #{response_body.bytesize}\r\n")
65
- @out.write("\r\n")
66
- @out.write(response_body)
67
- @out.flush
42
+ @should_raise_errors = should_raise_errors
68
43
  end
69
44
 
70
- def log(message)
71
- @err.puts(message)
72
- @err.flush
45
+ def listen
46
+ start_handler_threads
47
+ start_json_rpc_thread
48
+ status_code = status_code_from_error(@error.pop)
49
+ cleanup(status_code)
50
+ rescue SignalException
51
+ 0
73
52
  end
74
53
 
75
- private
76
-
77
- def supported_io_classes
78
- [IO, StringIO]
54
+ def start_json_rpc_thread
55
+ @json_rpc_thread = Thread.new do
56
+ loop do
57
+ message = @bridge.read_message
58
+ if message['method'] == 'initialize'
59
+ handle_message(message)
60
+ elsif message.key?('result')
61
+ # Responses are handled on the main thread to prevent
62
+ # a potential deadlock caused by all handlers waiting
63
+ # for a responses.
64
+ handle_response(message)
65
+ else
66
+ @queue << message
67
+ end
68
+ rescue Exception => e # rubocop:disable Lint/RescueException
69
+ break @error << e
70
+ end
71
+ end
79
72
  end
80
73
 
81
- def validate!(streams = [])
82
- streams.each do |stream|
83
- unless supported_io_classes.find { |klass| stream.is_a?(klass) }
84
- raise IncompatibleStream, incompatible_stream_message
74
+ def start_handler_threads
75
+ @number_of_threads.times do
76
+ @handlers << Thread.new do
77
+ loop do
78
+ message = @queue.pop
79
+ break if @queue.closed? && @queue.empty?
80
+ handle_message(message)
81
+ rescue Exception => e # rubocop:disable Lint/RescueException
82
+ break @error << e
83
+ end
85
84
  end
86
85
  end
87
86
  end
88
87
 
89
- def incompatible_stream_message
90
- 'if provided, in_stream, out_stream, and err_stream must be a kind of '\
91
- "one of the following: #{supported_io_classes.join(', ')}"
88
+ def status_code_from_error(e)
89
+ raise e
90
+
91
+ # support ctrl+c and stuff
92
+ rescue SignalException, DoneStreaming
93
+ 0
94
+
95
+ rescue Exception => e # rubocop:disable Lint/RescueException
96
+ raise e if should_raise_errors
97
+ @bridge.log("#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}")
98
+ 2
92
99
  end
93
100
 
94
- def process_request
95
- request_body = read_new_content
96
- request_json = JSON.parse(request_body)
97
- log(JSON.pretty_generate(request_json)) if $DEBUG
101
+ private
98
102
 
99
- id = request_json['id']
100
- method_name = request_json['method']
101
- params = request_json['params']
102
- method_name = "on_#{to_snake_case(method_name)}"
103
+ def handle_message(message)
104
+ id = message['id']
105
+ method_name = message['method']
106
+ method_name &&= "on_#{to_snake_case(method_name)}"
107
+ params = message['params']
103
108
 
104
109
  if @handler.respond_to?(method_name)
105
110
  @handler.send(method_name, id, params)
106
111
  end
107
- end
108
112
 
109
- def to_snake_case(method_name)
110
- StringHelpers.underscore(method_name.gsub(/[^\w]/, '_'))
113
+ rescue DoneStreaming => e
114
+ raise e
115
+ rescue StandardError => e
116
+ is_request = id
117
+ raise e unless is_request
118
+ # Errors obtained in request handlers should be sent
119
+ # back as internal errors instead of closing the program.
120
+ @bridge.send_internal_error(id, e)
111
121
  end
112
122
 
113
- def initial_line
114
- # Scanning for lines that fit the protocol.
115
- while true
116
- initial_line = @in.gets
117
- # gets returning nil means the stream was closed.
118
- raise DoneStreaming if initial_line.nil?
119
-
120
- if initial_line.match(/Content-Length: (\d+)/)
121
- break
122
- end
123
- end
124
- initial_line
123
+ def handle_response(message)
124
+ id = message['id']
125
+ result = message['result']
126
+ @bridge.receive_response(id, result)
125
127
  end
126
128
 
127
- def read_new_content
128
- length = initial_line.match(/Content-Length: (\d+)/)[1].to_i
129
- content = ''
130
- while content.length < length + 2
131
- begin
132
- # Why + 2? Because \r\n
133
- content += @in.read(length + 2)
134
- rescue => e
135
- log(e)
136
- log(e.backtrace)
137
- # We have almost certainly been disconnected from the server
138
- cleanup
139
- raise DoneStreaming
140
- end
141
- end
142
-
143
- content
129
+ def to_snake_case(method_name)
130
+ StringHelpers.underscore(method_name.gsub(/[^\w]/, '_'))
144
131
  end
145
132
 
146
- def cleanup
147
- @err.close
148
- @out.close
149
- rescue
150
- # I did my best
133
+ def cleanup(status_code)
134
+ # Stop listenting to RPC calls
135
+ @messenger.close_input
136
+ # Wait for rpc loop to close
137
+ @json_rpc_thread&.join if @json_rpc_thread&.alive?
138
+ # Close the queue
139
+ @queue.close unless @queue.closed?
140
+ # Give 10 seconds for the handlers to wrap up what they were
141
+ # doing/emptying the queue. 👀 unit tests.
142
+ @handlers.each { |thread| thread.join(10) if thread.alive? }
143
+
144
+ # Hijack the status_code if an error occurred while cleaning up.
145
+ # 👀 unit tests.
146
+ until @error.empty?
147
+ code = status_code_from_error(@error.pop)
148
+ # Promote the status_code to ERROR if one of the threads
149
+ # resulted in an error, otherwise leave the status_code as
150
+ # is. That's because one thread could end successfully in a
151
+ # DoneStreaming error while the other failed with an
152
+ # internal error. If we had an internal error, we should
153
+ # return with a status_code that fits.
154
+ status_code = code if code > status_code
155
+ end
156
+ status_code
157
+ ensure
158
+ @messenger.close_output
151
159
  end
152
160
  end
153
161
  end
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  require_relative "language_server/protocol"
3
3
  require_relative "language_server/constants"
4
+ require_relative "language_server/channel"
5
+ require_relative "language_server/messenger"
6
+ require_relative "language_server/io_messenger"
7
+ require_relative "language_server/bridge"
4
8
  require_relative "language_server/uri_helper"
5
9
  require_relative "language_server/handler"
6
10
  require_relative "language_server/server"
@@ -12,6 +16,7 @@ require_relative "language_server/completion_engine"
12
16
  require_relative "language_server/document_link_provider"
13
17
  require_relative "language_server/document_link_engine"
14
18
  require_relative "language_server/diagnostics_tracker"
19
+ require_relative "language_server/diagnostics_engine"
15
20
 
16
21
  Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file|
17
22
  require file
@@ -24,7 +29,7 @@ end
24
29
  module ThemeCheck
25
30
  module LanguageServer
26
31
  def self.start
27
- Server.new.listen
32
+ Server.new(messenger: IOMessenger.new).listen
28
33
  end
29
34
  end
30
35
  end
@@ -74,6 +74,34 @@ module ThemeCheck
74
74
  position.end_index
75
75
  end
76
76
 
77
+ def start_token_index
78
+ return position.start_index if inside_liquid_tag?
79
+ position.start_index - (start_token.length + 1)
80
+ end
81
+
82
+ def end_token_index
83
+ return position.end_index if inside_liquid_tag?
84
+ position.end_index + end_token.length
85
+ end
86
+
87
+ def render_start_tag
88
+ "#{start_token} #{@value.raw}#{end_token}"
89
+ end
90
+
91
+ def render_end_tag
92
+ "#{start_token} #{@value.block_delimiter} #{end_token}"
93
+ end
94
+
95
+ def block_body_start_index
96
+ return unless block_tag?
97
+ block_regex.begin(:body)
98
+ end
99
+
100
+ def block_body_end_index
101
+ return unless block_tag?
102
+ block_regex.end(:body)
103
+ end
104
+
77
105
  # Literals are hard-coded values in the liquid file.
78
106
  def literal?
79
107
  @value.is_a?(String) || @value.is_a?(Integer)
@@ -184,6 +212,11 @@ module ThemeCheck
184
212
 
185
213
  private
186
214
 
215
+ def block_regex
216
+ return unless block_tag?
217
+ /(?<start_token>#{render_start_tag})(?<body>.*)(?<end_token>#{render_end_tag})/m.match(source)
218
+ end
219
+
187
220
  def position
188
221
  @position ||= Position.new(
189
222
  markup,
@@ -14,24 +14,43 @@ module ThemeCheck
14
14
  visit_object(@default, @other, [])
15
15
  end
16
16
 
17
- def add_as_offenses(check, key_prefix: [], node: nil, theme_file: nil)
17
+ def add_as_offenses(check, key_prefix: [], node: nil, theme_file: nil, schema: {})
18
18
  if extra_keys.any?
19
- add_keys_offense(check, "Extra translation keys", extra_keys,
20
- key_prefix: key_prefix, node: node, theme_file: theme_file)
19
+ remove_extra_keys_offense(check, "Extra translation keys", extra_keys,
20
+ key_prefix: key_prefix, node: node, theme_file: theme_file, schema: schema)
21
21
  end
22
22
 
23
23
  if missing_keys.any?
24
- add_keys_offense(check, "Missing translation keys", missing_keys,
25
- key_prefix: key_prefix, node: node, theme_file: theme_file)
24
+ add_missing_keys_offense(check, "Missing translation keys", missing_keys,
25
+ key_prefix: key_prefix, node: node, theme_file: theme_file, schema: schema)
26
26
  end
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def add_keys_offense(check, cause, keys, key_prefix:, node: nil, theme_file: nil)
32
- message = "#{cause}: #{format_keys(key_prefix, keys)}"
31
+ def remove_extra_keys_offense(check, cause, extra_keys, key_prefix:, node: nil, theme_file: nil, schema: {})
32
+ message = "#{cause}: #{format_keys(key_prefix, extra_keys)}"
33
33
  if node
34
- check.add_offense(message, node: node)
34
+ check.add_offense(message, node: node) do |corrector|
35
+ extra_keys.each do |k|
36
+ corrector.remove_key(schema, key_prefix + k)
37
+ end
38
+ corrector.replace_block_body(node, schema)
39
+ end
40
+ else
41
+ check.add_offense(message, theme_file: theme_file)
42
+ end
43
+ end
44
+
45
+ def add_missing_keys_offense(check, cause, missing_keys, key_prefix:, node: nil, theme_file: nil, schema: {})
46
+ message = "#{cause}: #{format_keys(key_prefix, missing_keys)}"
47
+ if node
48
+ check.add_offense(message, node: node) do |corrector|
49
+ missing_keys.each do |k|
50
+ corrector.add_key(schema, key_prefix + k, "TODO")
51
+ end
52
+ corrector.replace_block_body(node, schema)
53
+ end
35
54
  else
36
55
  check.add_offense(message, theme_file: theme_file)
37
56
  end
@@ -46,10 +65,12 @@ module ThemeCheck
46
65
  other = {} unless other.is_a?(Hash)
47
66
  return if pluralization?(default) && pluralization?(other)
48
67
 
49
- @extra_keys += (other.keys - default.keys).map { |key| path + [key] }
68
+ shopify_translations = system_translations(path)
69
+
70
+ @extra_keys += (other.keys - default.keys - shopify_translations.keys).map { |key| path + [key] }
50
71
 
51
72
  default.each do |key, default_value|
52
- translated_value = other[key]
73
+ translated_value = other[key] || shopify_translations[key]
53
74
  new_path = path + [key]
54
75
 
55
76
  if translated_value.nil?
@@ -65,5 +86,10 @@ module ThemeCheck
65
86
  PLURALIZATION_KEYS.include?(key) && !value.is_a?(Hash)
66
87
  end
67
88
  end
89
+
90
+ def system_translations(path)
91
+ return ShopifyLiquid::SystemTranslations.translations_hash if path.empty?
92
+ ShopifyLiquid::SystemTranslations.translations_hash.dig(*path) || {}
93
+ end
68
94
  end
69
95
  end
@@ -64,6 +64,10 @@ module ThemeCheck
64
64
  strict_position.end_column
65
65
  end
66
66
 
67
+ def content_line_count
68
+ @content_line_count ||= contents.count("\n")
69
+ end
70
+
67
71
  private
68
72
 
69
73
  def compute_start_offset
@@ -78,10 +82,6 @@ module ThemeCheck
78
82
  @contents
79
83
  end
80
84
 
81
- def content_line_count
82
- @content_line_count ||= contents.count("\n")
83
- end
84
-
85
85
  def line_number
86
86
  return 0 if @line_number_1_indexed.nil?
87
87
  bounded(0, @line_number_1_indexed - 1, content_line_count)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ module ShopifyLiquid
4
+ module SystemTranslations
5
+ extend self
6
+
7
+ def translations
8
+ @translations ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_translation_keys.yml")).to_set
9
+ end
10
+
11
+ def translations_hash
12
+ @translations_hash ||= translations.reduce({}) do |acc, k|
13
+ dig_set(acc, k.split('.'), "")
14
+ end
15
+ end
16
+
17
+ def include?(key)
18
+ translations.include?(key)
19
+ end
20
+
21
+ private
22
+
23
+ def dig_set(obj, keys, value)
24
+ key = keys.first
25
+ if keys.length == 1
26
+ obj[key] = value
27
+ else
28
+ obj[key] = {} unless obj[key]
29
+ dig_set(obj[key], keys.slice(1..-1), value)
30
+ end
31
+ obj
32
+ end
33
+ end
34
+ end
35
+ end
@@ -7,10 +7,17 @@ module ThemeCheck
7
7
  extend self
8
8
 
9
9
  def labels
10
- @tags ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/tags.yml"))
10
+ @labels ||= tags_file_contents
11
+ .map { |x| to_label(x) }
11
12
  .to_set
12
13
  end
13
14
 
15
+ def end_labels
16
+ @end_labels ||= tags_file_contents
17
+ .select { |x| x.is_a?(Hash) }
18
+ .map { |x| x.values[0] }
19
+ end
20
+
14
21
  def tag_regex(tag)
15
22
  return unless labels.include?(tag)
16
23
  @tag_regexes ||= {}
@@ -22,6 +29,17 @@ module ThemeCheck
22
29
  @tag_liquid_regexes ||= {}
23
30
  @tag_liquid_regexes[tag] ||= /^\s*#{tag}/m
24
31
  end
32
+
33
+ private
34
+
35
+ def to_label(label)
36
+ return label if label.is_a?(String)
37
+ label.keys[0]
38
+ end
39
+
40
+ def tags_file_contents
41
+ @tags_file_contents ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_liquid/tags.yml"))
42
+ end
25
43
  end
26
44
  end
27
45
  end
@@ -3,3 +3,4 @@ require_relative 'shopify_liquid/deprecated_filter'
3
3
  require_relative 'shopify_liquid/filter'
4
4
  require_relative 'shopify_liquid/object'
5
5
  require_relative 'shopify_liquid/tag'
6
+ require_relative 'shopify_liquid/system_translations'
@@ -66,7 +66,6 @@ module ThemeCheck
66
66
 
67
67
  def initialize(tag_name, markup, options)
68
68
  super
69
-
70
69
  if (matches = markup.match(SYNTAX))
71
70
  @liquid_variable_name = matches[:liquid_variable_name]
72
71
  @page_size = parse_expression(matches[:page_size])
@@ -25,6 +25,12 @@ module ThemeCheck
25
25
  )
26
26
  end
27
27
 
28
+ def remove(node)
29
+ @rewriter.remove(
30
+ range(node.start_token_index, node.end_token_index)
31
+ )
32
+ end
33
+
28
34
  def replace(node, content)
29
35
  @rewriter.replace(
30
36
  range(node.start_index, node.end_index),
@@ -32,6 +38,13 @@ module ThemeCheck
32
38
  )
33
39
  end
34
40
 
41
+ def replace_body(node, content)
42
+ @rewriter.replace(
43
+ range(node.block_body_start_index, node.block_body_end_index),
44
+ content
45
+ )
46
+ end
47
+
35
48
  def wrap(node, insert_before, insert_after)
36
49
  @rewriter.wrap(
37
50
  range(node.start_index, node.end_index),
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
- VERSION = "1.6.2"
3
+ VERSION = "1.8.0"
4
4
  end
data/lib/theme_check.rb CHANGED
@@ -51,6 +51,10 @@ Encoding.default_external = Encoding::UTF_8
51
51
  Encoding.default_internal = Encoding::UTF_8
52
52
 
53
53
  module ThemeCheck
54
+ def self.debug?
55
+ ENV["THEME_CHECK_DEBUG"] == "true"
56
+ end
57
+
54
58
  def self.with_liquid_c_disabled
55
59
  if defined?(Liquid::C)
56
60
  was_enabled = Liquid::C.enabled
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: theme-check
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc-André Cournoyer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-16 00:00:00.000000000 Z
11
+ date: 2021-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -201,6 +201,8 @@ files:
201
201
  - lib/theme_check/json_helpers.rb
202
202
  - lib/theme_check/json_printer.rb
203
203
  - lib/theme_check/language_server.rb
204
+ - lib/theme_check/language_server/bridge.rb
205
+ - lib/theme_check/language_server/channel.rb
204
206
  - lib/theme_check/language_server/completion_engine.rb
205
207
  - lib/theme_check/language_server/completion_helper.rb
206
208
  - lib/theme_check/language_server/completion_provider.rb
@@ -209,6 +211,7 @@ files:
209
211
  - lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb
210
212
  - lib/theme_check/language_server/completion_providers/tag_completion_provider.rb
211
213
  - lib/theme_check/language_server/constants.rb
214
+ - lib/theme_check/language_server/diagnostics_engine.rb
212
215
  - lib/theme_check/language_server/diagnostics_tracker.rb
213
216
  - lib/theme_check/language_server/document_link_engine.rb
214
217
  - lib/theme_check/language_server/document_link_provider.rb
@@ -217,6 +220,8 @@ files:
217
220
  - lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb
218
221
  - lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb
219
222
  - lib/theme_check/language_server/handler.rb
223
+ - lib/theme_check/language_server/io_messenger.rb
224
+ - lib/theme_check/language_server/messenger.rb
220
225
  - lib/theme_check/language_server/protocol.rb
221
226
  - lib/theme_check/language_server/server.rb
222
227
  - lib/theme_check/language_server/tokens.rb
@@ -241,6 +246,7 @@ files:
241
246
  - lib/theme_check/shopify_liquid/deprecated_filter.rb
242
247
  - lib/theme_check/shopify_liquid/filter.rb
243
248
  - lib/theme_check/shopify_liquid/object.rb
249
+ - lib/theme_check/shopify_liquid/system_translations.rb
244
250
  - lib/theme_check/shopify_liquid/tag.rb
245
251
  - lib/theme_check/storage.rb
246
252
  - lib/theme_check/string_helpers.rb