theme-check 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/theme-check.yml +11 -3
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +29 -0
  5. data/Gemfile +5 -5
  6. data/LICENSE.md +2 -0
  7. data/README.md +1 -0
  8. data/RELEASING.md +10 -3
  9. data/Rakefile +6 -0
  10. data/config/default.yml +3 -0
  11. data/dev.yml +1 -1
  12. data/docs/checks/remote_asset.md +82 -0
  13. data/exe/theme-check +1 -1
  14. data/lib/theme_check.rb +1 -0
  15. data/lib/theme_check/check.rb +1 -1
  16. data/lib/theme_check/checks/asset_size_css.rb +1 -1
  17. data/lib/theme_check/checks/asset_size_javascript.rb +1 -1
  18. data/lib/theme_check/checks/img_width_and_height.rb +2 -2
  19. data/lib/theme_check/checks/matching_translations.rb +1 -1
  20. data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
  21. data/lib/theme_check/checks/remote_asset.rb +99 -0
  22. data/lib/theme_check/checks/translation_key_exists.rb +1 -4
  23. data/lib/theme_check/checks/undefined_object.rb +1 -1
  24. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  25. data/lib/theme_check/cli.rb +101 -57
  26. data/lib/theme_check/config.rb +6 -2
  27. data/lib/theme_check/disabled_checks.rb +2 -2
  28. data/lib/theme_check/in_memory_storage.rb +13 -16
  29. data/lib/theme_check/language_server/completion_engine.rb +2 -2
  30. data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -1
  31. data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +1 -1
  32. data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +1 -1
  33. data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +2 -2
  34. data/lib/theme_check/language_server/constants.rb +2 -2
  35. data/lib/theme_check/language_server/document_link_engine.rb +4 -3
  36. data/lib/theme_check/language_server/handler.rb +29 -21
  37. data/lib/theme_check/language_server/server.rb +1 -2
  38. data/lib/theme_check/node.rb +1 -2
  39. data/lib/theme_check/offense.rb +3 -1
  40. data/lib/theme_check/packager.rb +1 -1
  41. data/lib/theme_check/parsing_helpers.rb +1 -1
  42. data/lib/theme_check/releaser.rb +39 -0
  43. data/lib/theme_check/shopify_liquid/deprecated_filter.rb +6 -8
  44. data/lib/theme_check/shopify_liquid/filter.rb +3 -5
  45. data/lib/theme_check/shopify_liquid/object.rb +2 -6
  46. data/lib/theme_check/shopify_liquid/tag.rb +1 -3
  47. data/lib/theme_check/storage.rb +3 -3
  48. data/lib/theme_check/string_helpers.rb +47 -0
  49. data/lib/theme_check/tags.rb +1 -2
  50. data/lib/theme_check/theme.rb +1 -1
  51. data/lib/theme_check/version.rb +1 -1
  52. data/packaging/homebrew/theme_check.base.rb +1 -1
  53. data/theme-check.gemspec +3 -2
  54. metadata +9 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93631c68de37297060f57799731a0ef4adae05aa9d89f8a019d28b0afe65533a
4
- data.tar.gz: 86efb9c580a6206974bec0c876fc1feca80c5dad3c7d500e4528a537f01c9613
3
+ metadata.gz: 299afef3e63667dca21495d3a3e6246d378b4be98c6b26a7a5f46405e6db80b8
4
+ data.tar.gz: b4d57dd51b0bd8960fcd42314fea731c1ae0aaac1ca29bbd2c591b3a6a9f4781
5
5
  SHA512:
6
- metadata.gz: 712d137ecb52bc6af0b83db9ed391252f78d5a0ad2a3b4c3d82d6b1a59e927da167b2bdea40a7f07f45d77cb16c72ca2e49243b78bb6c52296ed84a30c5cd83f
7
- data.tar.gz: 3e6505817fc868979c2ead8f99133d9c24754b905b3211774c74e9a1261a9f962fd9660c94aed70838a2c0dcb086e353a7ce5a739d9ada8456f7ba0ff61ee077
6
+ metadata.gz: ea83f67457a564676ca6e32f7b6f601f54c2d6c259cc1a8af705d673640bb9a3e11881f54ca9cc571faf75cb36af93b51c5f03c96547d2cb24eb19ff69d59795
7
+ data.tar.gz: 2694a73abcb292ebcc6d1f142a9255f9b5126d4748c64ece82c0081acb65d12c194acbd6232f24b50d0ddf982d076f49b342ef833883a09ab9a287712b631af5
@@ -8,15 +8,23 @@ on:
8
8
 
9
9
  jobs:
10
10
  test:
11
+ runs-on: macos-latest
11
12
 
12
- runs-on: ubuntu-20.04
13
+ strategy:
14
+ matrix:
15
+ version:
16
+ - 3.0.0
17
+ - 2.7.1
18
+ - 2.6.6
19
+
20
+ name: Ruby ${{ matrix.version }}
13
21
 
14
22
  steps:
15
23
  - uses: actions/checkout@v2
16
- - name: Set up Ruby
24
+ - name: Set up Ruby ${{ matrix.version }}
17
25
  uses: ruby/setup-ruby@v1
18
26
  with:
19
- ruby-version: 2.7
27
+ ruby-version: ${{ matrix.version }}
20
28
  - uses: actions/cache@v1
21
29
  with:
22
30
  path: vendor/bundle
data/.rubocop.yml CHANGED
@@ -7,7 +7,7 @@ require:
7
7
  - rubocop-rake
8
8
 
9
9
  AllCops:
10
- TargetRubyVersion: 2.7
10
+ TargetRubyVersion: 2.6
11
11
  Exclude:
12
12
  - 'vendor/bundle/**/*'
13
13
  - 'packaging/builds/**/*'
data/CHANGELOG.md CHANGED
@@ -1,4 +1,33 @@
1
1
 
2
+ 0.8.0 / 2021-04-13
3
+ ==================
4
+
5
+ * Set minimal Ruby version to 2.6
6
+
7
+ 0.7.3 / 2021-04-13
8
+ ==================
9
+
10
+ * Refactor CLI option parsing
11
+
12
+ 0.7.2 / 2021-04-12
13
+ ==================
14
+
15
+ * Fixup bug in RemoteAsset causing Language Server to break
16
+
17
+ 0.7.1 / 2021-04-12
18
+ ==================
19
+
20
+ * Fix High CPU Bug in RemoteAsset check
21
+ * Fix document link for render tags that trim whitespace.
22
+
23
+ 0.7.0 / 2021-04-08
24
+ ==================
25
+
26
+ * Add [RemoteAsset Check](/docs/checks/remote_asset.md)
27
+ * Fixes:
28
+ * Don't hang on self closing img tags ([#247](https://github.com/shopify/theme-check/issues/247))
29
+ * Fix document links from different root
30
+
2
31
  0.6.0 / 2021-03-23
3
32
  ==================
4
33
 
data/Gemfile CHANGED
@@ -21,8 +21,8 @@ group :development do
21
21
  gem 'guard-minitest'
22
22
  end
23
23
 
24
- gem 'rubocop', require: false
25
- gem 'rubocop-performance', require: false
26
- gem 'rubocop-shopify', require: false
27
- gem 'rubocop-minitest', require: false
28
- gem 'rubocop-rake', require: false
24
+ gem 'rubocop', '~> 1.12.0', require: false
25
+ gem 'rubocop-performance', '~> 1.10.2', require: false
26
+ gem 'rubocop-shopify', '~> 1.0.7', require: false
27
+ gem 'rubocop-minitest', '~> 0.11.0', require: false
28
+ gem 'rubocop-rake', '~> 0.5.1', require: false
data/LICENSE.md CHANGED
@@ -1,6 +1,8 @@
1
1
 
2
2
  Copyright 2020-present, Shopify Inc.
3
3
 
4
+ Contains code from activesupport Copyright (c) 2005-2019 David Heinemeier Hansson
5
+
4
6
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5
7
 
6
8
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
data/README.md CHANGED
@@ -35,6 +35,7 @@ Theme Check currently checks for the following:
35
35
  As well as checks that prevent easy to spot performance problems:
36
36
 
37
37
  ✅ Use of [parser-blocking](/docs/checks/parser_blocking_javascript.md) JavaScript
38
+ ✅ [Use of non-Shopify domains for assets](/docs/checks/remote_asset.md)
38
39
  ✅ [Missing width and height attributes on `img` tags](/docs/checks/img_width_and_height.md)
39
40
  ✅ [Too much JavaScript](/docs/checks/asset_size_javascript.md)
40
41
  ✅ [Too much CSS](/docs/checks/asset_size_css.md)
data/RELEASING.md CHANGED
@@ -2,11 +2,18 @@
2
2
 
3
3
  1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
4
4
 
5
- 2. Create a PR to update the version in `lib/theme_check/version.rb` and replace the `THEME_CHECK_VERSION` placeholder in the documentation for new rules.
5
+ 2. Run the following command to update the version in `lib/theme_check/version.rb` and replace the `THEME_CHECK_VERSION` placeholder in the documentation for new rules:
6
6
 
7
- 3. Merge your PR to master
7
+ ```bash
8
+ VERSION="X.X.X"
9
+ rake prerelease[$VERSION]
10
+ ```
11
+
12
+ 3. Commit your changes and make a PR.
13
+
14
+ 4. Merge your PR to master.
8
15
 
9
- 4. On [Shipit](https://shipit.shopify.io/shopify/theme-check/rubygems), deploy your commit.
16
+ 5. On [Shipit](https://shipit.shopify.io/shopify/theme-check/rubygems), deploy your commit.
10
17
 
11
18
  ## Homebrew Release Process
12
19
 
data/Rakefile CHANGED
@@ -46,3 +46,9 @@ end
46
46
 
47
47
  desc("Builds all distribution packages of the CLI")
48
48
  task(package: 'package:all')
49
+
50
+ desc("Update files in the repo to match new version")
51
+ task :prerelease, [:version] do |_t, args|
52
+ require 'theme_check/releaser'
53
+ ThemeCheck::Releaser.new.release(args.version)
54
+ end
data/config/default.yml CHANGED
@@ -93,3 +93,6 @@ AssetSizeCSS:
93
93
 
94
94
  ImgWidthAndHeight:
95
95
  enabled: true
96
+
97
+ RemoteAsset:
98
+ enabled: true
data/dev.yml CHANGED
@@ -3,7 +3,7 @@ name: theme-check
3
3
  type: ruby
4
4
 
5
5
  up:
6
- - ruby: 2.7.1
6
+ - ruby: 2.6.6
7
7
  - bundler
8
8
 
9
9
  commands:
@@ -0,0 +1,82 @@
1
+ # Discourage use of third party domains for hosting assets (`RemoteAsset`)
2
+
3
+ Years ago, loading jQuery from a common CDN was good for performance because the browser cache could be reused across website. This is no longer true because browsers now include the domain from which the request was made in the cache key.
4
+
5
+ Therefore, this technique now makes things worse. Here's why:
6
+
7
+ * **The benefits of HTTP/2 prioritization are lost.** HTTP/2 prioritization is a mechanism used by servers. If different servers are used to deliver assets, there's no way to prioritize.
8
+ * **A new connection dance (DNS, TCP, TLS) must be done to start downloading the resource.** With HTTPS, this takes 5 round trips to achieve. The farther away the buyer is from that domain, the longer it takes.
9
+ * **The [slow start][slowstart] part of the Internet's TCP congestion control strategy must happen on every connection.** This means that the download "acceleration" we commonly observe must be repeated many times over.
10
+
11
+ The fix? Deliver as much as you can from a small number of connections. In a Shopify context, this is done by leveraging the `assets/` folder and the [URL filters][url_filters].
12
+
13
+ ## Check Details
14
+
15
+ This check is aimed at eliminating unnecessary HTTP connections.
16
+
17
+ :-1: Examples of **incorrect** code for this check:
18
+
19
+ ```liquid
20
+ <!-- Using multiple CDNs -->
21
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" defer></script>
22
+ {{ "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" | stylesheet_tag }}
23
+ <img src="https://example.com/heart.png" ...>
24
+
25
+ <!-- Missing img_url filter -->
26
+ <img src="{{ image }}" ...>
27
+ ```
28
+
29
+ In the examples above, multiple connections are competing for resources, are accelerating download independently and are improperly prioritized.
30
+
31
+ :+1: Examples of **correct** code for this check:
32
+
33
+ ```liquid
34
+ <!-- Good -->
35
+ <script src="{{ 'jquery.min.js' | asset_url }}" defer></script>
36
+ {{ 'bootstrap.min.css' | asset_url | stylesheet_tag }}
37
+
38
+ <!-- Better -->
39
+ <script src="{{ 'theme.js' | asset_url }}" defer></script>
40
+ {{ 'theme.css' | asset_url | stylesheet_tag }}
41
+
42
+ <!-- Images -->
43
+ <img src="{{ image | img_url }}" ...>
44
+ ```
45
+
46
+ In the above, the JavaScript, CSS and images are all loading from the same connection. Making it so the browser and CDN can properly prioritize which assets are downloaded first while maintaining a "hot" connection that downloads fast.
47
+
48
+ This can be done by downloading the files from those CDNs directly into your theme's `assets/` folder.
49
+
50
+ Use the [`img_url` filter][img_url] for images.
51
+
52
+ ## Check Options
53
+
54
+ The default configuration for this check is the following:
55
+
56
+ ```yaml
57
+ RemoteAsset:
58
+ enabled: true
59
+ ```
60
+
61
+ ## When Not To Use It
62
+
63
+ When the remote content is highly dynamic.
64
+
65
+ ## Version
66
+
67
+ This check has been introduced in Theme Check 0.7.0.
68
+
69
+ ## Resources
70
+
71
+ - [Announcement by Google][googleprivacy]
72
+ - [HTTP Cache Partioning Explainer](https://github.com/shivanigithub/http-cache-partitioning)
73
+ - [Slow Start][slowstart]
74
+ - [Rule Source][codesource]
75
+ - [Documentation Source][docsource]
76
+
77
+ [googleprivacy]: https://developers.google.com/web/updates/2020/10/http-cache-partitioning#resources
78
+ [codesource]: /lib/theme_check/checks/remote_asset.rb
79
+ [docsource]: /docs/checks/remote_asset.md
80
+ [slowstart]: https://en.wikipedia.org/wiki/TCP_congestion_control#Slow_start
81
+ [url_filters]: https://shopify.dev/docs/themes/liquid/reference/filters/url-filters
82
+ [img_url]: https://shopify.dev/docs/themes/liquid/reference/filters/url-filters#img_url
data/exe/theme-check CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  require "theme_check"
5
5
 
6
- ThemeCheck::Cli.new.run!(ARGV)
6
+ ThemeCheck::Cli.parse_and_run(ARGV)
data/lib/theme_check.rb CHANGED
@@ -22,6 +22,7 @@ require_relative "theme_check/offense"
22
22
  require_relative "theme_check/printer"
23
23
  require_relative "theme_check/shopify_liquid"
24
24
  require_relative "theme_check/storage"
25
+ require_relative "theme_check/string_helpers"
25
26
  require_relative "theme_check/file_system_storage"
26
27
  require_relative "theme_check/in_memory_storage"
27
28
  require_relative "theme_check/tags"
@@ -82,7 +82,7 @@ module ThemeCheck
82
82
  end
83
83
 
84
84
  def code_name
85
- self.class.name.demodulize
85
+ StringHelpers.demodulize(self.class.name)
86
86
  end
87
87
 
88
88
  def ignore!
@@ -75,7 +75,7 @@ module ThemeCheck
75
75
  # asset_url (+ optional stylesheet_tag) variables
76
76
  if href =~ /^#{VARIABLE}$/o && href =~ /asset_url/ && href =~ Liquid::QuotedString
77
77
  asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
78
- asset = @theme.assets.find { |a| a.name.ends_with?("/" + asset_id) }
78
+ asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
79
79
  return if asset.nil?
80
80
  asset.gzipped_size
81
81
 
@@ -56,7 +56,7 @@ module ThemeCheck
56
56
  # More complicated liquid statements are not in scope.
57
57
  if src =~ /^#{VARIABLE}$/o && src =~ /asset_url/ && src =~ Liquid::QuotedString
58
58
  asset_id = Regexp.last_match(0).gsub(START_OR_END_QUOTE, "")
59
- asset = @theme.assets.find { |a| a.name.ends_with?("/" + asset_id) }
59
+ asset = @theme.assets.find { |a| a.name.end_with?("/" + asset_id) }
60
60
  return if asset.nil?
61
61
  asset.gzipped_size
62
62
  elsif src =~ %r{^(https?:)?//}
@@ -8,7 +8,7 @@ module ThemeCheck
8
8
  doc docs_url(__FILE__)
9
9
 
10
10
  # Not implemented with lookbehinds and lookaheads because performance was shit!
11
- IMG_TAG = /<img#{HTML_ATTRIBUTES}>/oxim
11
+ IMG_TAG = %r{<img#{HTML_ATTRIBUTES}/?>}oxim
12
12
  SRC_ATTRIBUTE = /\s(src)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
13
13
  WIDTH_ATTRIBUTE = /\s(width)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
14
14
  HEIGHT_ATTRIBUTE = /\s(height)=(#{QUOTED_LIQUID_ATTRIBUTE})/oxim
@@ -35,7 +35,7 @@ module ThemeCheck
35
35
  def record_missing_field_offenses(img_match)
36
36
  width = WIDTH_ATTRIBUTE.match(img_match[0])
37
37
  height = HEIGHT_ATTRIBUTE.match(img_match[0])
38
- return if width.present? && height.present?
38
+ return if width && height
39
39
  missing_width = width.nil?
40
40
  missing_height = height.nil?
41
41
  error_message = if missing_width && missing_height
@@ -11,7 +11,7 @@ module ThemeCheck
11
11
  end
12
12
 
13
13
  def on_file(file)
14
- return unless file.name.starts_with?("locales/")
14
+ return unless file.name.start_with?("locales/")
15
15
  return unless file.content.is_a?(Hash)
16
16
  return if file.name == @theme.default_locale_json&.name
17
17
 
@@ -11,7 +11,7 @@ module ThemeCheck
11
11
  <script # Find the start of a script tag
12
12
  (?=[^>]+?src=) # Make sure src= is in the script with a lookahead
13
13
  (?:(?!defer|async|type=["']module['"]).)*? # Find tags that don't have defer|async|type="module"
14
- >
14
+ /?>
15
15
  }xim
16
16
  SCRIPT_TAG_FILTER = /\{\{[^}]+script_tag\s+\}\}/
17
17
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ class RemoteAsset < LiquidCheck
4
+ include RegexHelpers
5
+ severity :suggestion
6
+ categories :liquid, :performance
7
+ doc docs_url(__FILE__)
8
+
9
+ OFFENSE_MESSAGE = "Asset should be served by the Shopify CDN for better performance."
10
+
11
+ HTML_FILTERS = [
12
+ 'stylesheet_tag',
13
+ 'script_tag',
14
+ 'img_tag',
15
+ ]
16
+ ASSET_URL_FILTERS = [
17
+ 'asset_url',
18
+ 'asset_img_url',
19
+ 'file_img_url',
20
+ 'file_url',
21
+ 'global_asset_url',
22
+ 'img_url',
23
+ 'payment_type_img_url',
24
+ 'shopify_asset_url',
25
+ ]
26
+
27
+ RESOURCE_TAG = %r{<(?<tag_name>img|script|link|source)#{HTML_ATTRIBUTES}/?>}oim
28
+ RESOURCE_URL = /\s(?:src|href)=(?<resource_url>#{QUOTED_LIQUID_ATTRIBUTE})/oim
29
+ ASSET_URL_FILTER = /[\|\s]*(#{ASSET_URL_FILTERS.join('|')})/omi
30
+ PROTOCOL = %r{(https?:)?//}
31
+ ABSOLUTE_PATH = %r{\A/[^/]}im
32
+ RELATIVE_PATH = %r{\A(?!#{PROTOCOL})[^/\{]}oim
33
+ REL = /\srel=(?<rel>#{QUOTED_LIQUID_ATTRIBUTE})/oim
34
+
35
+ def on_variable(node)
36
+ record_variable_offense(node)
37
+ end
38
+
39
+ def on_document(node)
40
+ source = node.template.source
41
+ record_html_offenses(node, source)
42
+ end
43
+
44
+ private
45
+
46
+ def record_variable_offense(variable_node)
47
+ # We flag HTML tags with URLs not hosted by Shopify
48
+ return if !html_resource_drop?(variable_node) || variable_hosted_by_shopify?(variable_node)
49
+ add_offense(OFFENSE_MESSAGE, node: variable_node)
50
+ end
51
+
52
+ def html_resource_drop?(variable_node)
53
+ variable_node.value.filters
54
+ .any? { |(filter_name, *_filter_args)| HTML_FILTERS.include?(filter_name) }
55
+ end
56
+
57
+ def variable_hosted_by_shopify?(variable_node)
58
+ variable_node.value.filters
59
+ .any? { |(filter_name, *_filter_args)| ASSET_URL_FILTERS.include?(filter_name) }
60
+ end
61
+
62
+ # This part is slightly more complicated because we don't have an
63
+ # HTML AST. We have to resort to looking at the HTML with regexes
64
+ # to figure out if we have a resource (stylesheet, script, or media)
65
+ # that points to a remote domain.
66
+ def record_html_offenses(node, source)
67
+ matches(source, RESOURCE_TAG).each do |match|
68
+ tag = match[0]
69
+
70
+ # We don't flag stuff without URLs
71
+ next unless tag =~ RESOURCE_URL
72
+ resource_match = Regexp.last_match
73
+ resource_url = resource_match[:resource_url].gsub(START_OR_END_QUOTE, '')
74
+
75
+ next if non_stylesheet_link?(tag)
76
+ next if url_hosted_by_shopify?(resource_url)
77
+ next if resource_url =~ ABSOLUTE_PATH
78
+ next if resource_url =~ RELATIVE_PATH
79
+ next if resource_url.empty?
80
+
81
+ start = match.begin(0) + resource_match.begin(:resource_url)
82
+ add_offense(
83
+ OFFENSE_MESSAGE,
84
+ node: node,
85
+ markup: resource_url,
86
+ line_number: source[0...start].count("\n") + 1,
87
+ )
88
+ end
89
+ end
90
+
91
+ def non_stylesheet_link?(tag)
92
+ tag =~ REL && !(Regexp.last_match[:rel] =~ /\A['"]stylesheet['"]\Z/)
93
+ end
94
+
95
+ def url_hosted_by_shopify?(url)
96
+ url =~ /\A#{VARIABLE}\Z/oim && url =~ ASSET_URL_FILTER
97
+ end
98
+ end
99
+ end
@@ -4,10 +4,7 @@ module ThemeCheck
4
4
  extend self
5
5
 
6
6
  def translations
7
- @translations ||= begin
8
- # loaded as a Set because the include? lookup will be much faster.
9
- YAML.load(File.read("#{__dir__}/../../../data/shopify_translation_keys.yml")).to_set
10
- end
7
+ @translations ||= YAML.load(File.read("#{__dir__}/../../../data/shopify_translation_keys.yml")).to_set
11
8
  end
12
9
 
13
10
  def include?(key)