theme-check 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.md +18 -0
- data/CONTRIBUTING.md +82 -0
- data/README.md +4 -0
- data/Rakefile +7 -0
- data/TROUBLESHOOTING.md +65 -0
- data/data/shopify_liquid/built_in_liquid_objects.json +60 -0
- data/data/shopify_liquid/deprecated_filters.json +22 -0
- data/data/shopify_liquid/documentation/filters.json +5528 -0
- data/data/shopify_liquid/documentation/latest.json +1 -0
- data/data/shopify_liquid/documentation/objects.json +19272 -0
- data/data/shopify_liquid/documentation/tags.json +1252 -0
- data/data/shopify_liquid/plus_labels.json +15 -0
- data/data/shopify_liquid/theme_app_extension_labels.json +3 -0
- data/lib/theme_check/checks/undefined_object.rb +4 -0
- data/lib/theme_check/language_server/completion_context.rb +52 -0
- data/lib/theme_check/language_server/completion_engine.rb +15 -21
- data/lib/theme_check/language_server/completion_provider.rb +26 -1
- data/lib/theme_check/language_server/completion_providers/assignments_completion_provider.rb +40 -0
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +49 -6
- data/lib/theme_check/language_server/completion_providers/object_attribute_completion_provider.rb +48 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +15 -10
- data/lib/theme_check/language_server/completion_providers/render_snippet_completion_provider.rb +5 -1
- data/lib/theme_check/language_server/completion_providers/tag_completion_provider.rb +8 -1
- data/lib/theme_check/language_server/handler.rb +3 -1
- data/lib/theme_check/language_server/protocol.rb +9 -0
- data/lib/theme_check/language_server/type_helper.rb +22 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/node_handler.rb +63 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope.rb +60 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder/scope_visitor.rb +44 -0
- data/lib/theme_check/language_server/variable_lookup_finder/assignments_finder.rb +76 -0
- data/lib/theme_check/language_server/variable_lookup_finder/constants.rb +44 -0
- data/lib/theme_check/language_server/variable_lookup_finder/liquid_fixer.rb +103 -0
- data/lib/theme_check/language_server/variable_lookup_finder/potential_lookup.rb +10 -0
- data/lib/theme_check/language_server/variable_lookup_finder/tolerant_parser.rb +114 -0
- data/lib/theme_check/language_server/variable_lookup_finder.rb +67 -100
- data/lib/theme_check/language_server/variable_lookup_traverser.rb +70 -0
- data/lib/theme_check/language_server.rb +12 -0
- data/lib/theme_check/remote_asset_file.rb +13 -7
- data/lib/theme_check/shopify_liquid/deprecated_filter.rb +1 -1
- data/lib/theme_check/shopify_liquid/documentation/markdown_template.rb +51 -0
- data/lib/theme_check/shopify_liquid/documentation.rb +44 -0
- data/lib/theme_check/shopify_liquid/filter.rb +35 -3
- data/lib/theme_check/shopify_liquid/object.rb +8 -3
- data/lib/theme_check/shopify_liquid/source_index/base_entry.rb +60 -0
- data/lib/theme_check/shopify_liquid/source_index/base_state.rb +23 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_entry.rb +18 -0
- data/lib/theme_check/shopify_liquid/source_index/filter_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/object_entry.rb +14 -0
- data/lib/theme_check/shopify_liquid/source_index/object_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index/parameter_entry.rb +21 -0
- data/lib/theme_check/shopify_liquid/source_index/property_entry.rb +9 -0
- data/lib/theme_check/shopify_liquid/source_index/return_type_entry.rb +37 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_entry.rb +20 -0
- data/lib/theme_check/shopify_liquid/source_index/tag_state.rb +11 -0
- data/lib/theme_check/shopify_liquid/source_index.rb +76 -0
- data/lib/theme_check/shopify_liquid/source_manager.rb +111 -0
- data/lib/theme_check/shopify_liquid/tag.rb +11 -1
- data/lib/theme_check/shopify_liquid.rb +17 -1
- data/lib/theme_check/version.rb +1 -1
- data/shipit.rubygems.yml +3 -0
- data/theme-check.gemspec +3 -1
- metadata +40 -8
- data/data/shopify_liquid/deprecated_filters.yml +0 -14
- data/data/shopify_liquid/filters.yml +0 -211
- data/data/shopify_liquid/objects.yml +0 -84
- data/data/shopify_liquid/plus_objects.yml +0 -15
- data/data/shopify_liquid/tags.yml +0 -30
- data/data/shopify_liquid/theme_app_extension_objects.yml +0 -2
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "language_server/protocol"
|
3
4
|
require_relative "language_server/constants"
|
4
5
|
require_relative "language_server/configuration"
|
@@ -7,9 +8,19 @@ require_relative "language_server/messenger"
|
|
7
8
|
require_relative "language_server/io_messenger"
|
8
9
|
require_relative "language_server/bridge"
|
9
10
|
require_relative "language_server/uri_helper"
|
11
|
+
require_relative "language_server/type_helper"
|
10
12
|
require_relative "language_server/server"
|
11
13
|
require_relative "language_server/tokens"
|
14
|
+
require_relative "language_server/variable_lookup_finder/potential_lookup"
|
15
|
+
require_relative "language_server/variable_lookup_finder/tolerant_parser"
|
16
|
+
require_relative "language_server/variable_lookup_finder/assignments_finder/node_handler"
|
17
|
+
require_relative "language_server/variable_lookup_finder/assignments_finder/scope_visitor"
|
18
|
+
require_relative "language_server/variable_lookup_finder/assignments_finder/scope"
|
19
|
+
require_relative "language_server/variable_lookup_finder/assignments_finder"
|
20
|
+
require_relative "language_server/variable_lookup_finder/constants"
|
21
|
+
require_relative "language_server/variable_lookup_finder/liquid_fixer"
|
12
22
|
require_relative "language_server/variable_lookup_finder"
|
23
|
+
require_relative "language_server/variable_lookup_traverser"
|
13
24
|
require_relative "language_server/diagnostic"
|
14
25
|
require_relative "language_server/diagnostics_manager"
|
15
26
|
require_relative "language_server/diagnostics_engine"
|
@@ -17,6 +28,7 @@ require_relative "language_server/document_change_corrector"
|
|
17
28
|
require_relative "language_server/versioned_in_memory_storage"
|
18
29
|
require_relative "language_server/client_capabilities"
|
19
30
|
|
31
|
+
require_relative "language_server/completion_context"
|
20
32
|
require_relative "language_server/completion_helper"
|
21
33
|
require_relative "language_server/completion_provider"
|
22
34
|
require_relative "language_server/completion_engine"
|
@@ -31,13 +31,7 @@ module ThemeCheck
|
|
31
31
|
return if @uri.nil?
|
32
32
|
return @content unless @content.nil?
|
33
33
|
|
34
|
-
|
35
|
-
req = Net::HTTP::Get.new(@uri)
|
36
|
-
req['Accept-Encoding'] = 'gzip, deflate, br'
|
37
|
-
http.request(req)
|
38
|
-
end
|
39
|
-
|
40
|
-
@content = res.body
|
34
|
+
@content = request(@uri)
|
41
35
|
|
42
36
|
rescue OpenSSL::SSL::SSLError, Zlib::StreamError, *NET_HTTP_EXCEPTIONS
|
43
37
|
@contents = ''
|
@@ -47,5 +41,17 @@ module ThemeCheck
|
|
47
41
|
return if @uri.nil?
|
48
42
|
@gzipped_size ||= content.bytesize
|
49
43
|
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def request(uri)
|
48
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
49
|
+
req = Net::HTTP::Get.new(uri)
|
50
|
+
req['Accept-Encoding'] = 'gzip, deflate, br'
|
51
|
+
http.request(req)
|
52
|
+
end
|
53
|
+
|
54
|
+
res.body
|
55
|
+
end
|
50
56
|
end
|
51
57
|
end
|
@@ -17,7 +17,7 @@ module ThemeCheck
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def all
|
20
|
-
@all ||=
|
20
|
+
@all ||= SourceIndex.deprecated_filters
|
21
21
|
.values
|
22
22
|
.each_with_object({}) do |filters, acc|
|
23
23
|
filters.each do |(filter, alternatives)|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class Documentation
|
6
|
+
class MarkdownTemplate
|
7
|
+
MARKDOWN_RELATIVE_LINK = %r{(\[([^\[]+)\]\((/[^\)]+)\))*}
|
8
|
+
|
9
|
+
def render(entry)
|
10
|
+
[
|
11
|
+
title(entry),
|
12
|
+
body(entry),
|
13
|
+
].reject(&:empty?).join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def title(entry)
|
19
|
+
"### #{entry.name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def body(entry)
|
23
|
+
[entry.deprecation_reason, entry.summary, entry.description]
|
24
|
+
.reject(&:nil?)
|
25
|
+
.reject(&:empty?)
|
26
|
+
.join(horizontal_rule)
|
27
|
+
.tap { |body| break(patch_urls!(body)) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def horizontal_rule
|
31
|
+
"\n\n---\n\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
def patch_urls!(body)
|
35
|
+
body.gsub(MARKDOWN_RELATIVE_LINK) do |original_link|
|
36
|
+
match = Regexp.last_match
|
37
|
+
|
38
|
+
text = match[2]
|
39
|
+
path = match[3]
|
40
|
+
|
41
|
+
if text && path
|
42
|
+
"[#{text}](https://shopify.dev#{path})"
|
43
|
+
else
|
44
|
+
original_link
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'documentation/markdown_template'
|
4
|
+
|
5
|
+
module ThemeCheck
|
6
|
+
module ShopifyLiquid
|
7
|
+
class Documentation
|
8
|
+
class << self
|
9
|
+
def filter_doc(filter_name)
|
10
|
+
render_doc(SourceIndex.filters.find { |entry| entry.name == filter_name })
|
11
|
+
end
|
12
|
+
|
13
|
+
def object_doc(object_name)
|
14
|
+
render_doc(SourceIndex.objects.find { |entry| entry.name == object_name })
|
15
|
+
end
|
16
|
+
|
17
|
+
def tag_doc(tag_name)
|
18
|
+
render_doc(SourceIndex.tags.find { |entry| entry.name == tag_name })
|
19
|
+
end
|
20
|
+
|
21
|
+
def object_property_doc(object_name, property_name)
|
22
|
+
property_entry = SourceIndex
|
23
|
+
.objects
|
24
|
+
.find { |entry| entry.name == object_name }
|
25
|
+
&.properties
|
26
|
+
&.find { |prop| prop.name == property_name }
|
27
|
+
|
28
|
+
render_doc(property_entry)
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_doc(entry)
|
32
|
+
return nil unless entry
|
33
|
+
markdown_template.render(entry)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def markdown_template
|
39
|
+
@markdown_template ||= MarkdownTemplate.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -6,10 +6,42 @@ module ThemeCheck
|
|
6
6
|
module Filter
|
7
7
|
extend self
|
8
8
|
|
9
|
+
LABELS_NOT_IN_SOURCE_INDEX = [
|
10
|
+
"h",
|
11
|
+
"installments_pricing",
|
12
|
+
"sentence",
|
13
|
+
"t",
|
14
|
+
"app_block_path_for",
|
15
|
+
"dev_shop?",
|
16
|
+
"app_extension_path_for",
|
17
|
+
"global_block_type?",
|
18
|
+
"app_block_path?",
|
19
|
+
"app_extension_path?",
|
20
|
+
"app_snippet_path?",
|
21
|
+
"registration_uuid_from",
|
22
|
+
"handle_from",
|
23
|
+
"camelcase",
|
24
|
+
"format_code",
|
25
|
+
"handle",
|
26
|
+
"encode_url_component",
|
27
|
+
"recover_password_link",
|
28
|
+
"delete_customer_address_link",
|
29
|
+
"edit_customer_address_link",
|
30
|
+
"cancel_customer_order_link",
|
31
|
+
"unit",
|
32
|
+
"weight",
|
33
|
+
"paragraphize",
|
34
|
+
"excerpt",
|
35
|
+
"pad_spaces",
|
36
|
+
"distance_from",
|
37
|
+
"theme_url",
|
38
|
+
"link_to_theme",
|
39
|
+
"_online_store_editor_live_setting",
|
40
|
+
"debug",
|
41
|
+
]
|
42
|
+
|
9
43
|
def labels
|
10
|
-
@labels ||=
|
11
|
-
.values
|
12
|
-
.flatten
|
44
|
+
@labels ||= SourceIndex.filters.map(&:name) + LABELS_NOT_IN_SOURCE_INDEX
|
13
45
|
end
|
14
46
|
end
|
15
47
|
end
|
@@ -6,16 +6,21 @@ module ThemeCheck
|
|
6
6
|
module Object
|
7
7
|
extend self
|
8
8
|
|
9
|
+
LABELS_NOT_IN_SOURCE_INDEX = [
|
10
|
+
"customer_address",
|
11
|
+
"product_variant",
|
12
|
+
].freeze
|
13
|
+
|
9
14
|
def labels
|
10
|
-
@labels ||=
|
15
|
+
@labels ||= SourceIndex.objects.map(&:name) + LABELS_NOT_IN_SOURCE_INDEX
|
11
16
|
end
|
12
17
|
|
13
18
|
def plus_labels
|
14
|
-
@plus_labels ||=
|
19
|
+
@plus_labels ||= SourceIndex.plus_labels
|
15
20
|
end
|
16
21
|
|
17
22
|
def theme_app_extension_labels
|
18
|
-
@theme_app_extension_labels ||=
|
23
|
+
@theme_app_extension_labels ||= SourceIndex.theme_app_extension_labels
|
19
24
|
end
|
20
25
|
end
|
21
26
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module ThemeCheck
|
6
|
+
module ShopifyLiquid
|
7
|
+
class SourceIndex
|
8
|
+
class BaseEntry
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_reader :hash
|
12
|
+
|
13
|
+
def_delegators :return_type_instance, :generic_type?, :array_type?, :array_type, :to_s
|
14
|
+
|
15
|
+
def initialize(hash = {})
|
16
|
+
@hash = hash || {}
|
17
|
+
@return_type = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
hash['name']
|
22
|
+
end
|
23
|
+
|
24
|
+
def summary
|
25
|
+
hash['summary'] || ''
|
26
|
+
end
|
27
|
+
|
28
|
+
def description
|
29
|
+
hash['description'] || ''
|
30
|
+
end
|
31
|
+
|
32
|
+
def deprecated?
|
33
|
+
hash['deprecated']
|
34
|
+
end
|
35
|
+
|
36
|
+
def deprecation_reason
|
37
|
+
return nil unless deprecated?
|
38
|
+
|
39
|
+
hash['deprecation_reason'] || nil
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_writer :return_type
|
43
|
+
|
44
|
+
def return_type
|
45
|
+
@return_type || to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
def return_type_instance
|
49
|
+
ReturnTypeEntry.new(return_type_hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def return_type_hash
|
55
|
+
hash['return_type']&.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class SourceIndex
|
6
|
+
class BaseState
|
7
|
+
class << self
|
8
|
+
def mark_outdated
|
9
|
+
@up_to_date = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def mark_up_to_date
|
13
|
+
@up_to_date = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def outdated?
|
17
|
+
@up_to_date == false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class SourceIndex
|
6
|
+
class FilterEntry < BaseEntry
|
7
|
+
def parameters
|
8
|
+
(hash['parameters'] || [])
|
9
|
+
.map { |hash| ParameterEntry.new(hash) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def input_type
|
13
|
+
hash['syntax'].split(' | ')[0]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class SourceIndex
|
6
|
+
class ParameterEntry < BaseEntry
|
7
|
+
def summary
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def return_type_hash
|
14
|
+
{
|
15
|
+
'type' => (hash['types'] || ['untyped']).first,
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class SourceIndex
|
6
|
+
class ReturnTypeEntry < BaseEntry
|
7
|
+
def summary
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
hash['type']
|
13
|
+
end
|
14
|
+
|
15
|
+
def generic_type?
|
16
|
+
hash['type'] == 'generic'
|
17
|
+
end
|
18
|
+
|
19
|
+
def array_type?
|
20
|
+
!array_type.nil? && !array_type.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def array_type
|
24
|
+
hash['array_value']
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def return_type_hash
|
30
|
+
{
|
31
|
+
'type' => "type<#{self}>",
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ThemeCheck
|
4
|
+
module ShopifyLiquid
|
5
|
+
class SourceIndex
|
6
|
+
class TagEntry < BaseEntry
|
7
|
+
def parameters
|
8
|
+
(hash['parameters'] || [])
|
9
|
+
.map { |hash| ParameterEntry.new(hash) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def return_type_hash
|
13
|
+
{
|
14
|
+
'type' => "tag<#{name}>",
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module ThemeCheck
|
7
|
+
module ShopifyLiquid
|
8
|
+
class SourceIndex
|
9
|
+
class << self
|
10
|
+
def filters
|
11
|
+
@filters = nil if FilterState.outdated?
|
12
|
+
|
13
|
+
@filters ||= FilterState.mark_up_to_date &&
|
14
|
+
load_file(:filters)
|
15
|
+
.map { |hash| FilterEntry.new(hash) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def objects
|
19
|
+
@objects = nil if ObjectState.outdated?
|
20
|
+
|
21
|
+
@objects ||= ObjectState.mark_up_to_date &&
|
22
|
+
load_file(:objects)
|
23
|
+
.concat(built_in_objects)
|
24
|
+
.filter_map do |hash|
|
25
|
+
next if (theme_app_extension_labels + labels_only_exposed_in_certain_contexts).include?(hash['name'])
|
26
|
+
|
27
|
+
ObjectEntry.new(hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def tags
|
32
|
+
@tags = nil if TagState.outdated?
|
33
|
+
|
34
|
+
@tags ||= TagState.mark_up_to_date &&
|
35
|
+
load_file(:tags)
|
36
|
+
.map { |hash| TagEntry.new(hash) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def plus_labels
|
40
|
+
@plus_objects ||= load_file("../plus_labels")
|
41
|
+
end
|
42
|
+
|
43
|
+
def theme_app_extension_labels
|
44
|
+
@theme_app_extension_labels ||= load_file("../theme_app_extension_labels")
|
45
|
+
end
|
46
|
+
|
47
|
+
def labels_only_exposed_in_certain_contexts
|
48
|
+
['robots'].freeze
|
49
|
+
end
|
50
|
+
|
51
|
+
def deprecated_filters
|
52
|
+
@deprecated_filters ||= load_file("../deprecated_filters")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def load_file(file_name)
|
58
|
+
read_json(local_path!(file_name))
|
59
|
+
end
|
60
|
+
|
61
|
+
def local_path!(file_name)
|
62
|
+
SourceManager.download unless SourceManager.has_required_files?
|
63
|
+
SourceManager.local_path(file_name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_json(path)
|
67
|
+
JSON.parse(path.read)
|
68
|
+
end
|
69
|
+
|
70
|
+
def built_in_objects
|
71
|
+
load_file('../built_in_liquid_objects')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'pathname'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
module ThemeCheck
|
8
|
+
module ShopifyLiquid
|
9
|
+
class SourceManager
|
10
|
+
REQUIRED_FILE_NAMES = [:filters, :objects, :tags, :latest].freeze
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def download_or_refresh_files(destination = default_destination)
|
14
|
+
if has_required_files?(destination)
|
15
|
+
refresh(destination)
|
16
|
+
else
|
17
|
+
download(destination)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def download(destination = default_destination)
|
22
|
+
Dir.mkdir(destination) unless destination.exist?
|
23
|
+
|
24
|
+
REQUIRED_FILE_NAMES.each do |file_name|
|
25
|
+
download_file(local_path(file_name, destination), remote_path(file_name))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def refresh(destination = default_destination)
|
30
|
+
refresh_threads << Thread.new { refresh_thread(destination) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def local_path(file_name, destination = default_destination)
|
34
|
+
destination + "#{file_name}.json"
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_required_files?(destination = default_destination)
|
38
|
+
REQUIRED_FILE_NAMES.all? { |file_name| local_path(file_name, destination).exist? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_downloads
|
42
|
+
refresh_threads.each(&:join)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def refresh_thread(destination)
|
48
|
+
return unless refresh_needed?(destination)
|
49
|
+
|
50
|
+
Dir.mktmpdir do |tmp_dir|
|
51
|
+
download(Pathname.new(tmp_dir))
|
52
|
+
|
53
|
+
FileUtils.cp_r("#{tmp_dir}/.", destination)
|
54
|
+
|
55
|
+
mark_all_indexes_outdated
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def refresh_needed?(destination)
|
60
|
+
local_latest_content = local_path(:latest, destination).read
|
61
|
+
remote_latest_content = open_uri(remote_path(:latest))
|
62
|
+
|
63
|
+
revision(local_latest_content) != revision(remote_latest_content)
|
64
|
+
end
|
65
|
+
|
66
|
+
def revision(json_content)
|
67
|
+
# Raise an error if revision isn't found to avoid returning nil
|
68
|
+
JSON.parse(json_content).fetch('revision')
|
69
|
+
end
|
70
|
+
|
71
|
+
def remote_path(file_name)
|
72
|
+
"https://raw.githubusercontent.com/Shopify/theme-liquid-docs/main/data/#{file_name}.json"
|
73
|
+
end
|
74
|
+
|
75
|
+
def download_file(local_path, remote_uri)
|
76
|
+
File.open(local_path, "wb") do |file|
|
77
|
+
content = open_uri(remote_uri)
|
78
|
+
file.write(content)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def mark_all_indexes_outdated
|
83
|
+
SourceIndex::FilterState.mark_outdated
|
84
|
+
SourceIndex::ObjectState.mark_outdated
|
85
|
+
SourceIndex::TagState.mark_outdated
|
86
|
+
end
|
87
|
+
|
88
|
+
# State
|
89
|
+
|
90
|
+
def default_destination
|
91
|
+
@default_destination ||= Pathname.new("#{__dir__}/../../../data/shopify_liquid/documentation")
|
92
|
+
end
|
93
|
+
|
94
|
+
def refresh_threads
|
95
|
+
@refresh_threads ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
def open_uri(uri_str)
|
99
|
+
uri = URI.parse(uri_str)
|
100
|
+
|
101
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
102
|
+
req = Net::HTTP::Get.new(uri)
|
103
|
+
http.request(req)
|
104
|
+
end
|
105
|
+
|
106
|
+
res.body
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|