theme-check 1.8.0 → 1.9.0

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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +21 -0
  4. data/README.md +10 -0
  5. data/RELEASING.md +13 -0
  6. data/config/default.yml +5 -0
  7. data/data/shopify_liquid/deprecated_filters.yml +4 -0
  8. data/data/shopify_liquid/filters.yml +2 -1
  9. data/docs/checks/schema_json_format.md +76 -0
  10. data/docs/language_server/code-action-command-palette.png +0 -0
  11. data/docs/language_server/code-action-flow.png +0 -0
  12. data/docs/language_server/code-action-keyboard.png +0 -0
  13. data/docs/language_server/code-action-light-bulb.png +0 -0
  14. data/docs/language_server/code-action-problem.png +0 -0
  15. data/docs/language_server/code-action-quickfix.png +0 -0
  16. data/docs/language_server/how_to_correct_code_with_code_actions_and_execute_command.md +197 -0
  17. data/lib/theme_check/checks/asset_size_app_block_css.rb +2 -3
  18. data/lib/theme_check/checks/asset_size_app_block_javascript.rb +2 -3
  19. data/lib/theme_check/checks/asset_url_filters.rb +2 -0
  20. data/lib/theme_check/checks/default_locale.rb +1 -1
  21. data/lib/theme_check/checks/deprecated_filter.rb +79 -4
  22. data/lib/theme_check/checks/deprecated_global_app_block_type.rb +2 -3
  23. data/lib/theme_check/checks/matching_schema_translations.rb +4 -6
  24. data/lib/theme_check/checks/matching_translations.rb +1 -0
  25. data/lib/theme_check/checks/missing_required_template_files.rb +3 -3
  26. data/lib/theme_check/checks/missing_template.rb +1 -1
  27. data/lib/theme_check/checks/pagination_size.rb +2 -3
  28. data/lib/theme_check/checks/remote_asset.rb +5 -0
  29. data/lib/theme_check/checks/required_directories.rb +1 -1
  30. data/lib/theme_check/checks/schema_json_format.rb +29 -0
  31. data/lib/theme_check/checks/space_inside_braces.rb +132 -87
  32. data/lib/theme_check/checks/translation_key_exists.rb +33 -13
  33. data/lib/theme_check/checks/unused_snippet.rb +1 -1
  34. data/lib/theme_check/checks/valid_html_translation.rb +1 -1
  35. data/lib/theme_check/checks/valid_schema.rb +2 -2
  36. data/lib/theme_check/corrector.rb +28 -54
  37. data/lib/theme_check/file_system_storage.rb +4 -3
  38. data/lib/theme_check/html_node.rb +99 -6
  39. data/lib/theme_check/html_visitor.rb +1 -32
  40. data/lib/theme_check/in_memory_storage.rb +9 -0
  41. data/lib/theme_check/json_helpers.rb +14 -0
  42. data/lib/theme_check/language_server/bridge.rb +1 -1
  43. data/lib/theme_check/language_server/client_capabilities.rb +27 -0
  44. data/lib/theme_check/language_server/code_action_engine.rb +32 -0
  45. data/lib/theme_check/language_server/code_action_provider.rb +42 -0
  46. data/lib/theme_check/language_server/code_action_providers/quickfix_code_action_provider.rb +83 -0
  47. data/lib/theme_check/language_server/code_action_providers/source_fix_all_code_action_provider.rb +40 -0
  48. data/lib/theme_check/language_server/configuration.rb +69 -0
  49. data/lib/theme_check/language_server/diagnostic.rb +124 -0
  50. data/lib/theme_check/language_server/diagnostics_engine.rb +15 -60
  51. data/lib/theme_check/language_server/diagnostics_manager.rb +136 -0
  52. data/lib/theme_check/language_server/document_change_corrector.rb +267 -0
  53. data/lib/theme_check/language_server/document_link_provider.rb +6 -6
  54. data/lib/theme_check/language_server/execute_command_engine.rb +19 -0
  55. data/lib/theme_check/language_server/execute_command_provider.rb +30 -0
  56. data/lib/theme_check/language_server/execute_command_providers/correction_execute_command_provider.rb +48 -0
  57. data/lib/theme_check/language_server/execute_command_providers/run_checks_execute_command_provider.rb +22 -0
  58. data/lib/theme_check/language_server/handler.rb +79 -28
  59. data/lib/theme_check/language_server/io_messenger.rb +9 -1
  60. data/lib/theme_check/language_server/server.rb +8 -7
  61. data/lib/theme_check/language_server/uri_helper.rb +1 -0
  62. data/lib/theme_check/language_server/versioned_in_memory_storage.rb +69 -0
  63. data/lib/theme_check/language_server.rb +23 -5
  64. data/lib/theme_check/liquid_node.rb +249 -39
  65. data/lib/theme_check/locale_diff.rb +16 -4
  66. data/lib/theme_check/node.rb +16 -0
  67. data/lib/theme_check/offense.rb +27 -23
  68. data/lib/theme_check/regex_helpers.rb +1 -1
  69. data/lib/theme_check/schema_helper.rb +70 -0
  70. data/lib/theme_check/storage.rb +4 -0
  71. data/lib/theme_check/theme.rb +1 -1
  72. data/lib/theme_check/theme_file.rb +8 -1
  73. data/lib/theme_check/theme_file_rewriter.rb +18 -9
  74. data/lib/theme_check/version.rb +1 -1
  75. data/lib/theme_check.rb +7 -2
  76. metadata +26 -3
  77. data/lib/theme_check/language_server/diagnostics_tracker.rb +0 -66
@@ -22,6 +22,7 @@ module ThemeCheck
22
22
  return if resource_url =~ ABSOLUTE_PATH
23
23
  return if resource_url =~ RELATIVE_PATH
24
24
  return if url_hosted_by_shopify?(resource_url)
25
+ return if url_is_setting_variable?(resource_url)
25
26
 
26
27
  # Ignore non-stylesheet link tags
27
28
  rel = node.attributes["rel"]
@@ -39,5 +40,9 @@ module ThemeCheck
39
40
  url.start_with?(Liquid::VariableStart) &&
40
41
  AssetUrlFilters::ASSET_URL_FILTERS.any? { |filter| url.include?(filter) }
41
42
  end
43
+
44
+ def url_is_setting_variable?(url)
45
+ url.start_with?(Liquid::VariableStart) && url =~ /settings\./
46
+ end
42
47
  end
43
48
  end
@@ -19,7 +19,7 @@ module ThemeCheck
19
19
 
20
20
  def add_missing_directories_offense(directory)
21
21
  add_offense("Theme is missing '#{directory}' directory") do |corrector|
22
- corrector.mkdir(@theme, directory)
22
+ corrector.mkdir(@theme.storage, directory)
23
23
  end
24
24
  end
25
25
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module ThemeCheck
3
+ class SchemaJsonFormat < LiquidCheck
4
+ severity :style
5
+ category :liquid
6
+ doc docs_url(__FILE__)
7
+
8
+ def initialize(start_level: 0, indent: ' ')
9
+ @pretty_json_opts = {
10
+ indent: indent,
11
+ start_level: start_level,
12
+ }
13
+ end
14
+
15
+ def on_schema(node)
16
+ schema = node.inner_json
17
+ return if schema.nil?
18
+ pretty_schema = pretty_json(schema, **@pretty_json_opts)
19
+ if pretty_schema != node.inner_markup
20
+ add_offense(
21
+ "JSON formatting could be improved",
22
+ node: node,
23
+ ) do |corrector|
24
+ corrector.replace_inner_json(node, schema, **@pretty_json_opts)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,115 +6,160 @@ module ThemeCheck
6
6
  category :liquid
7
7
  doc docs_url(__FILE__)
8
8
 
9
- def initialize
10
- @ignore = false
11
- end
12
-
13
9
  def on_node(node)
14
10
  return unless node.markup
15
- return if :assign == node.type_name
11
+ return if node.literal?
12
+ return if node.assigned_or_echoed_variable?
16
13
 
17
14
  outside_of_strings(node.markup) do |chunk, chunk_start|
18
- chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=)( +)/) do |_match|
19
- add_offense(
20
- "Too many spaces after '#{Regexp.last_match(1)}'",
21
- node: node,
22
- markup: Regexp.last_match(2),
23
- node_markup_offset: chunk_start + Regexp.last_match.begin(2)
24
- )
15
+ chunk.scan(/(?<token>[,:|]|==|<>|<=|>=|<|>|!=)(?<offense> +)/) do |_match|
16
+ add_too_many_spaces_after_offense(Regexp.last_match, node, chunk_start)
25
17
  end
26
- chunk.scan(/([,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z)/) do |_match|
27
- add_offense(
28
- "Space missing after '#{Regexp.last_match(1)}'",
29
- node: node,
30
- markup: Regexp.last_match(1),
31
- node_markup_offset: chunk_start + Regexp.last_match.begin(0),
32
- )
18
+ chunk.scan(/(?<offense>(?<token>[,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z))/) do |_match|
19
+ add_space_missing_after_offense(Regexp.last_match, node, chunk_start)
33
20
  end
34
- chunk.scan(/( +)(\||==|<>|<=|>=|<|>|!=)+/) do |_match|
35
- add_offense(
36
- "Too many spaces before '#{Regexp.last_match(2)}'",
37
- node: node,
38
- markup: Regexp.last_match(1),
39
- node_markup_offset: chunk_start + Regexp.last_match.begin(1)
40
- )
21
+ chunk.scan(/(?<offense>\s{2,})(?<token>\||==|<>|<=|>=|<|>|!=)+/) do |_match|
22
+ unless Regexp.last_match(:offense).include?("\n")
23
+ add_too_many_spaces_before_offense(Regexp.last_match, node, chunk_start)
24
+ end
41
25
  end
42
- chunk.scan(/(\A|\S)(?<match>\||==|<>|<=|>=|<|\b>|!=)/) do |_match|
43
- add_offense(
44
- "Space missing before '#{Regexp.last_match(1)}'",
45
- node: node,
46
- markup: Regexp.last_match(:match),
47
- node_markup_offset: chunk_start + Regexp.last_match.begin(:match)
48
- )
26
+ chunk.scan(/(\A|\S)(?<offense>(?<token>\||==|<>|<=|>=|<|\b>|!=))/) do |_match|
27
+ add_space_missing_before_offense(Regexp.last_match, node, chunk_start)
49
28
  end
50
29
  end
51
30
  end
52
31
 
32
+ BlockMarkup = Struct.new(:markup, :node_markup_offset)
33
+
53
34
  def on_tag(node)
54
- unless node.inside_liquid_tag?
55
- if node.markup[-1] != " " && node.markup[-1] != "\n"
56
- add_offense(
57
- "Space missing before '#{node.end_token}'",
58
- node: node,
59
- markup: node.markup[-1],
60
- node_markup_offset: node.markup.size - 1,
61
- )
62
- elsif node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
63
- add_offense(
64
- "Too many spaces before '#{node.end_token}'",
65
- node: node,
66
- markup: Regexp.last_match(2),
67
- node_markup_offset: node.markup.size - Regexp.last_match(2).size
68
- )
35
+ return if node.inside_liquid_tag?
36
+
37
+ # Both the start and end tags
38
+ blocks = [
39
+ BlockMarkup.new(node.block_start_markup, node.block_start_start_index - node.start_index),
40
+ BlockMarkup.new(node.block_end_markup, node.block_end_start_index - node.start_index),
41
+ ]
42
+
43
+ blocks.each do |block|
44
+ # Looking at spaces after the start token
45
+ if block.markup =~ /^(?<token>{%-?)(?<offense>[^ \n\t-])/
46
+ add_space_missing_after_offense(Regexp.last_match, node, block.node_markup_offset)
47
+ end
48
+
49
+ if block.markup =~ /^(?<token>{%-?)(?<offense> {2,})\S/
50
+ add_too_many_spaces_after_offense(Regexp.last_match, node, block.node_markup_offset)
51
+ end
52
+
53
+ # Looking at spaces before the end token
54
+ if block.markup =~ /(?<offense>[^ \n\t-])(?<token>-?%})$/
55
+ add_space_missing_before_offense(Regexp.last_match, node, block.node_markup_offset)
69
56
  end
57
+
58
+ if block.markup =~ /\S(?<offense> {2,})(?<token>-?%})$/
59
+ add_too_many_spaces_before_offense(Regexp.last_match, node, block.node_markup_offset)
60
+ end
61
+
62
+ next
70
63
  end
71
- @ignore = true
72
64
  end
73
65
 
74
- def after_tag(_node)
75
- @ignore = false
66
+ def on_variable(node)
67
+ return if node.markup.empty?
68
+ return if node.assigned_or_echoed_variable?
69
+
70
+ block_start_offset = node.block_start_start_index - node.start_index
71
+
72
+ # Looking at spaces after the start token
73
+ if node.block_start_markup =~ /^(?<token>{{-?)(?<offense>[^ \n\t-])/
74
+ add_space_missing_after_offense(Regexp.last_match, node, block_start_offset)
75
+ end
76
+
77
+ if node.block_start_markup =~ /^(?<token>{{-?)(?<offense> {2,})\S/
78
+ add_too_many_spaces_after_offense(Regexp.last_match, node, block_start_offset)
79
+ end
80
+
81
+ # Looking at spaces before the end token
82
+ if node.block_start_markup =~ /(?<offense>[^ \n\t-])(?<token>-?}})$/
83
+ add_space_missing_before_offense(Regexp.last_match, node, block_start_offset)
84
+ end
85
+
86
+ if node.block_start_markup =~ /\S(?<offense> {2,})(?<token>-?}})$/
87
+ add_too_many_spaces_before_offense(Regexp.last_match, node, block_start_offset)
88
+ end
76
89
  end
77
90
 
78
- def on_variable(node)
79
- return if @ignore || node.markup.empty?
80
- if node.markup[0] != " "
81
- add_offense(
82
- "Space missing after '#{node.start_token}'",
83
- node: node,
84
- markup: node.markup[0]
85
- ) do |corrector|
86
- corrector.insert_before(node, " ")
87
- end
91
+ def add_space_missing_after_offense(match, node, source_offset)
92
+ add_offense_for_match(
93
+ "Space missing after '#{match[:token]}'",
94
+ match,
95
+ node,
96
+ source_offset
97
+ ) do |corrector|
98
+ corrector.insert_after(
99
+ node,
100
+ ' ',
101
+ (node.start_index + source_offset + match.begin(:token))...
102
+ (node.start_index + source_offset + match.end(:token))
103
+ )
88
104
  end
89
- if node.markup[-1] != " " && node.markup[-1] != "\n"
90
- add_offense(
91
- "Space missing before '#{node.end_token}'",
92
- node: node,
93
- markup: node.markup[-1],
94
- node_markup_offset: node.markup.size - 1,
95
- ) do |corrector|
96
- corrector.insert_after(node, " ")
97
- end
105
+ end
106
+
107
+ def add_too_many_spaces_after_offense(match, node, source_offset)
108
+ add_offense_for_match(
109
+ "Too many spaces after '#{match[:token]}'",
110
+ match,
111
+ node,
112
+ source_offset
113
+ ) do |corrector|
114
+ corrector.replace(
115
+ node,
116
+ ' ',
117
+ (node.start_index + source_offset + match.begin(:offense))...
118
+ (node.start_index + source_offset + match.end(:offense))
119
+ )
98
120
  end
99
- if node.markup =~ /\A( +)/m
100
- add_offense(
101
- "Too many spaces after '#{node.start_token}'",
102
- node: node,
103
- markup: Regexp.last_match(1),
104
- ) do |corrector|
105
- corrector.replace(node, " #{node.markup.lstrip}")
106
- end
121
+ end
122
+
123
+ def add_space_missing_before_offense(match, node, source_offset)
124
+ add_offense_for_match(
125
+ "Space missing before '#{match[:token]}'",
126
+ match,
127
+ node,
128
+ source_offset
129
+ ) do |corrector|
130
+ corrector.insert_before(
131
+ node,
132
+ ' ',
133
+ (node.start_index + source_offset + match.begin(:token))...
134
+ (node.start_index + source_offset + match.end(:token))
135
+ )
107
136
  end
108
- if node.markup =~ /(\n?)( +)\z/m && Regexp.last_match(1) != "\n"
109
- add_offense(
110
- "Too many spaces before '#{node.end_token}'",
111
- node: node,
112
- markup: Regexp.last_match(2),
113
- node_markup_offset: node.markup.size - Regexp.last_match(2).size
114
- ) do |corrector|
115
- corrector.replace(node, "#{node.markup.rstrip} ")
116
- end
137
+ end
138
+
139
+ def add_too_many_spaces_before_offense(match, node, source_offset)
140
+ add_offense_for_match(
141
+ "Too many spaces before '#{match[:token]}'",
142
+ match,
143
+ node,
144
+ source_offset
145
+ ) do |corrector|
146
+ corrector.replace(
147
+ node,
148
+ ' ',
149
+ (node.start_index + source_offset + match.begin(:offense))...
150
+ (node.start_index + source_offset + match.end(:offense))
151
+ )
117
152
  end
118
153
  end
154
+
155
+ def add_offense_for_match(message, match, node, source_offset, &block)
156
+ add_offense(
157
+ message,
158
+ node: node,
159
+ markup: match[:offense],
160
+ node_markup_offset: source_offset + match.begin(:offense),
161
+ &block
162
+ )
163
+ end
119
164
  end
120
165
  end
@@ -5,28 +5,48 @@ module ThemeCheck
5
5
  category :translation
6
6
  doc docs_url(__FILE__)
7
7
 
8
+ def initialize
9
+ @schema_locales = {}
10
+ @nodes = {}
11
+ end
12
+
13
+ def on_document(node)
14
+ @nodes[node.theme_file.name] = []
15
+ end
16
+
8
17
  def on_variable(node)
9
18
  return unless @theme.default_locale_json&.content&.is_a?(Hash)
10
-
11
19
  return unless node.value.filters.any? { |name, _| name == "t" || name == "translate" }
12
- return unless (key_node = node.children.first)
13
- return unless key_node.value.is_a?(String)
14
-
15
- unless key_exists?(key_node.value) || ShopifyLiquid::SystemTranslations.include?(key_node.value)
16
- add_offense(
17
- "'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}'",
18
- node: node,
19
- markup: key_node.value,
20
- ) do |corrector|
21
- corrector.add_default_translation_key(@theme.default_locale_json, key_node.value.split("."), "TODO")
20
+
21
+ @nodes[node.theme_file.name] << node
22
+ end
23
+
24
+ def on_schema(node)
25
+ if (schema_locales = node.inner_json&.dig("locales", @theme.default_locale))
26
+ @schema_locales = schema_locales
27
+ end
28
+ end
29
+
30
+ def on_end
31
+ @nodes.each_pair do |_file_name, file_nodes|
32
+ file_nodes.each do |node|
33
+ next unless (key_node = node.children.first)
34
+ next unless key_node.value.is_a?(String)
35
+ next if key_exists?(key_node.value, @theme.default_locale_json.content) || key_exists?(key_node.value, @schema_locales) || ShopifyLiquid::SystemTranslations.include?(key_node.value)
36
+ add_offense(
37
+ @schema_locales.empty? ? "'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}'" : "'#{key_node.value}' does not have a matching entry in '#{@theme.default_locale_json.relative_path}' or '#{node.theme_file.relative_path}'",
38
+ node: node,
39
+ markup: key_node.value
40
+ ) do |corrector|
41
+ corrector.add_translation(@theme.default_locale_json, key_node.value.split("."), "TODO")
42
+ end
22
43
  end
23
44
  end
24
45
  end
25
46
 
26
47
  private
27
48
 
28
- def key_exists?(key)
29
- pointer = @theme.default_locale_json.content
49
+ def key_exists?(key, pointer)
30
50
  key.split(".").each do |token|
31
51
  return false unless pointer.key?(token)
32
52
  pointer = pointer[token]
@@ -25,7 +25,7 @@ module ThemeCheck
25
25
  def on_end
26
26
  missing_snippets.each do |theme_file|
27
27
  add_offense("This snippet is not used", theme_file: theme_file) do |corrector|
28
- corrector.remove_file(@theme, theme_file.relative_path.to_s)
28
+ corrector.remove_file(@theme.storage, theme_file.relative_path.to_s)
29
29
  end
30
30
  end
31
31
  end
@@ -19,7 +19,7 @@ module ThemeCheck
19
19
 
20
20
  def html_key?(keys)
21
21
  pluralized_key = keys[-2] if keys.length > 1
22
- keys[-1].end_with?('_html') || pluralized_key.end_with?('_html')
22
+ keys[-1].end_with?('_html') || pluralized_key&.end_with?('_html')
23
23
  end
24
24
 
25
25
  def parse_and_add_offense(key, value)
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
  module ThemeCheck
3
3
  class ValidSchema < LiquidCheck
4
- severity :suggestion
4
+ severity :error
5
5
  category :json
6
6
  doc docs_url(__FILE__)
7
7
 
8
8
  def on_schema(node)
9
- JSON.parse(node.value.nodelist.join)
9
+ JSON.parse(node.inner_markup)
10
10
  rescue JSON::ParserError => e
11
11
  add_offense(format_json_parse_error(e), node: node)
12
12
  end
@@ -2,89 +2,63 @@
2
2
 
3
3
  module ThemeCheck
4
4
  class Corrector
5
+ include JsonHelpers
6
+
5
7
  def initialize(theme_file:)
6
8
  @theme_file = theme_file
7
9
  end
8
10
 
9
- def insert_after(node, content)
10
- @theme_file.rewriter.insert_after(node, content)
11
+ def insert_after(node, content, character_range = nil)
12
+ @theme_file.rewriter.insert_after(node, content, character_range)
11
13
  end
12
14
 
13
- def insert_before(node, content)
14
- @theme_file.rewriter.insert_before(node, content)
15
+ def insert_before(node, content, character_range = nil)
16
+ @theme_file.rewriter.insert_before(node, content, character_range)
15
17
  end
16
18
 
17
19
  def remove(node)
18
20
  @theme_file.rewriter.remove(node)
19
21
  end
20
22
 
21
- def replace(node, content)
22
- @theme_file.rewriter.replace(node, content)
23
+ def replace(node, content, character_range = nil)
24
+ @theme_file.rewriter.replace(node, content, character_range)
23
25
  node.markup = content
24
26
  end
25
27
 
26
- def replace_block_body(node, content)
27
- content = "\n #{JSON.pretty_generate(content, array_nl: "\n ", object_nl: "\n ")}\n" if content.is_a?(Hash)
28
- @theme_file.rewriter.replace_body(node, content)
29
- end
30
-
31
- def wrap(node, insert_before, insert_after)
32
- @theme_file.rewriter.wrap(node, insert_before, insert_after)
33
- end
34
-
35
- def create(theme, relative_path, content)
36
- theme.storage.write(relative_path, content)
28
+ def replace_inner_markup(node, content)
29
+ @theme_file.rewriter.replace_inner_markup(node, content)
37
30
  end
38
31
 
39
- def create_default_locale_json(theme)
40
- create(theme, "locales/#{theme.default_locale}.default.json", {})
41
- theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
32
+ def replace_inner_json(node, json, **pretty_json_opts)
33
+ replace_inner_markup(node, pretty_json(json, **pretty_json_opts))
42
34
  end
43
35
 
44
- def remove_file(theme, relative_path)
45
- theme.storage.remove(relative_path)
36
+ def wrap(node, insert_before, insert_after)
37
+ @theme_file.rewriter.wrap(node, insert_before, insert_after)
46
38
  end
47
39
 
48
- def mkdir(theme, relative_path)
49
- theme.storage.mkdir(relative_path)
40
+ def create_file(storage, relative_path, content)
41
+ storage.write(relative_path, content)
50
42
  end
51
43
 
52
- def add_default_translation_key(file, key, value)
53
- hash = file.content
54
- add_key(hash, key, value)
55
- file.update_contents(hash)
44
+ def remove_file(storage, relative_path)
45
+ storage.remove(relative_path)
56
46
  end
57
47
 
58
- def remove_key(hash, key)
59
- key.reduce(hash) do |pointer, token|
60
- return pointer.delete(token) if token == key.last
61
- pointer[token]
62
- end
48
+ def mkdir(storage, relative_path)
49
+ storage.mkdir(relative_path)
63
50
  end
64
51
 
65
- def add_key(hash, key, value)
66
- key.reduce(hash) do |pointer, token|
67
- return pointer[token] = value if token == key.last
68
- pointer[token] = {} unless pointer.key?(token)
69
- pointer[token]
70
- end
52
+ def add_translation(json_file, path, value)
53
+ hash = json_file.content
54
+ SchemaHelper.set(hash, path, value)
55
+ json_file.update_contents(hash)
71
56
  end
72
57
 
73
- def schema_corrector(schema, key, value)
74
- return unless schema.is_a?(Hash)
75
- key.reduce(schema) do |pointer, token|
76
- case pointer
77
- when Array
78
- pointer.each do |item|
79
- schema_corrector(item, key.drop(1), value)
80
- end
81
-
82
- when Hash
83
- return pointer[token] = value if token == key.last
84
- pointer[token] = {} unless pointer.key?(token) || pointer.key?("id")
85
- pointer[token].nil? && pointer["id"] == token ? pointer : pointer[token]
86
- end
87
- end
58
+ def remove_translation(json_file, path)
59
+ hash = json_file.content
60
+ SchemaHelper.delete(hash, path)
61
+ json_file.update_contents(hash)
88
62
  end
89
63
  end
90
64
  end
@@ -36,13 +36,14 @@ module ThemeCheck
36
36
  end
37
37
 
38
38
  def mkdir(relative_path)
39
- reset_memoizers unless file_exists?(relative_path)
40
-
41
- file(relative_path).mkpath unless file(relative_path).directory?
39
+ return if file_exists?(relative_path)
40
+ reset_memoizers
41
+ file(relative_path).mkpath
42
42
  end
43
43
 
44
44
  def files
45
45
  @file_array ||= glob("**/*")
46
+ .reject { |path| File.directory?(path) }
46
47
  .map { |path| path.relative_path_from(@root).to_s }
47
48
  end
48
49