platformos-check 0.4.8 → 0.4.10
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/CHANGELOG.md +27 -0
- data/README.md +28 -19
- data/config/default.yml +13 -1
- data/docs/checks/form_action.md +6 -0
- data/docs/checks/form_authenticity_token.md +22 -3
- data/docs/checks/include_in_render.md +62 -0
- data/docs/checks/translation_files_match.md +70 -0
- data/docs/checks/translation_key_exists.md +44 -0
- data/docs/platformos-check.jpg +0 -0
- data/lib/platformos_check/app.rb +13 -0
- data/lib/platformos_check/app_file.rb +10 -3
- data/lib/platformos_check/checks/convert_include_to_render.rb +41 -2
- data/lib/platformos_check/checks/form_action.rb +3 -1
- data/lib/platformos_check/checks/form_authenticity_token.rb +20 -0
- data/lib/platformos_check/checks/img_lazy_loading.rb +6 -2
- data/lib/platformos_check/checks/include_in_render.rb +45 -0
- data/lib/platformos_check/checks/invalid_args.rb +4 -1
- data/lib/platformos_check/checks/translation_files_match.rb +83 -0
- data/lib/platformos_check/checks/translation_key_exists.rb +48 -0
- data/lib/platformos_check/checks/undefined_object.rb +55 -26
- data/lib/platformos_check/checks/unreachable_code.rb +9 -9
- data/lib/platformos_check/checks/unused_assign.rb +33 -24
- data/lib/platformos_check/cli.rb +1 -1
- data/lib/platformos_check/ext/hash.rb +19 -0
- data/lib/platformos_check/graphql_file.rb +4 -0
- data/lib/platformos_check/language_server/constants.rb +18 -2
- data/lib/platformos_check/language_server/document_link_provider.rb +67 -10
- data/lib/platformos_check/language_server/document_link_providers/localize_document_link_provider.rb +38 -0
- data/lib/platformos_check/language_server/document_link_providers/theme_render_document_link_provider.rb +2 -1
- data/lib/platformos_check/language_server/document_link_providers/translation_document_link_provider.rb +36 -0
- data/lib/platformos_check/tags/graphql.rb +3 -0
- data/lib/platformos_check/translation_file.rb +40 -0
- data/lib/platformos_check/version.rb +1 -1
- data/lib/platformos_check/yaml_file.rb +7 -2
- data/lib/platformos_check.rb +1 -0
- metadata +13 -4
- data/docs/preview.png +0 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
class TranslationFilesMatch < YamlCheck
|
5
|
+
severity :error
|
6
|
+
category :translation
|
7
|
+
doc docs_url(__FILE__)
|
8
|
+
|
9
|
+
PLURALIZATION_KEYS = Set.new(%w[zero one two few many other])
|
10
|
+
|
11
|
+
def on_file(file)
|
12
|
+
return unless file.translation?
|
13
|
+
return if file.parse_error
|
14
|
+
return add_offense_wrong_language_in_file(file) if file.language != file.language_from_path
|
15
|
+
return check_if_file_exists_for_all_other_languages(file) if file.language == @platformos_app.default_language
|
16
|
+
|
17
|
+
default_language_file = @platformos_app.grouped_files[PlatformosCheck::TranslationFile][file.name.sub(file.language, @platformos_app.default_language)]
|
18
|
+
|
19
|
+
return add_offense_missing_file(file) if default_language_file.nil?
|
20
|
+
|
21
|
+
add_offense_different_structure(file, default_language_file) unless same_structure?(default_language_file.content[@platformos_app.default_language], file.content[file.language])
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def add_offense_wrong_language_in_file(file)
|
27
|
+
add_offense("Mismatch detected - file inside #{file.language_from_path} directory defines translations for `#{file.language}`", app_file: file) do |_corrector|
|
28
|
+
file.update_contents(file.content[file.language_from_path] = file.content.delete(file.content[file.language]))
|
29
|
+
file.write
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_offense_missing_file(file)
|
34
|
+
add_offense("Mismatch detected - missing `#{file.relative_path.to_s.sub(file.language, @platformos_app.default_language)}` to define translations the default language", app_file: file)
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_if_file_exists_for_all_other_languages(file)
|
38
|
+
@platformos_app.translations_hash.each_key do |lang|
|
39
|
+
next if lang == @platformos_app.default_language
|
40
|
+
|
41
|
+
language_file = @platformos_app.grouped_files[PlatformosCheck::TranslationFile][file.name.sub(file.language, lang)]
|
42
|
+
add_offense_missing_translation_file(file, lang) if language_file.nil?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_offense_missing_translation_file(file, lang)
|
47
|
+
missing_file_path = file.relative_path.to_s.sub(file.language, lang)
|
48
|
+
add_offense("Mismatch detected - missing `#{missing_file_path}` file to define translations for `#{lang}`", app_file: file) do |corrector|
|
49
|
+
missing_file_content = file.content.clone
|
50
|
+
missing_file_content[lang] = missing_file_content.delete(file.language)
|
51
|
+
corrector.create_file(@platformos_app.storage, missing_file_path, YAML.dump(missing_file_content))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def same_structure?(hash1, hash2)
|
56
|
+
if !hash1.is_a?(Hash) && !hash2.is_a?(Hash)
|
57
|
+
true
|
58
|
+
elsif (hash1.is_a?(Hash) && !hash2.is_a?(Hash)) || (!hash1.is_a?(Hash) && hash2.is_a?(Hash))
|
59
|
+
false
|
60
|
+
elsif pluralization?(hash1) && pluralization?(hash2)
|
61
|
+
true
|
62
|
+
elsif hash1.keys.map(&:to_s).sort != hash2.keys.map(&:to_s).sort
|
63
|
+
false
|
64
|
+
else
|
65
|
+
hash1.keys.all? { |key| same_structure?(hash1[key], hash2[key]) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_offense_different_structure(file, default_language_file)
|
70
|
+
add_offense("Mismatch detected - structure differs from the default language file #{default_language_file.relative_path}", app_file: file) do |_corrector|
|
71
|
+
file.content[file.language].transform_values! { |v| v.nil? ? {} : v }
|
72
|
+
file.content[file.language] = default_language_file.content[default_language_file.language].deep_merge(file.content[file.language])
|
73
|
+
file.write
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def pluralization?(hash)
|
78
|
+
hash.all? do |key, value|
|
79
|
+
PLURALIZATION_KEYS.include?(key) && !value.is_a?(Hash)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
# Recommends using {% liquid ... %} if 5 or more consecutive {% ... %} are found.
|
5
|
+
class TranslationKeyExists < LiquidCheck
|
6
|
+
severity :error
|
7
|
+
categories :translation, :liquid
|
8
|
+
doc docs_url(__FILE__)
|
9
|
+
|
10
|
+
def on_variable(node)
|
11
|
+
return unless node.value.name.is_a?(String)
|
12
|
+
return unless node.filters.size == 1
|
13
|
+
|
14
|
+
translation_filter = node.filters.detect { |f| TranslationFile::TRANSLATION_FILTERS.include?(f[0]) }
|
15
|
+
return unless translation_filter
|
16
|
+
return unless translation_filter
|
17
|
+
|
18
|
+
filter_attributes = translation_filter[2] || {}
|
19
|
+
|
20
|
+
return unless filter_attributes['default'].nil?
|
21
|
+
return if !filter_attributes['scope'].nil? && !filter_attributes['scope'].is_a?(String)
|
22
|
+
|
23
|
+
lang = filter_attributes['language'].is_a?(String) ? filter_attributes['language'] : @platformos_app.default_language
|
24
|
+
translation_components = node.value.name.split('.')
|
25
|
+
|
26
|
+
translation_components = filter_attributes['scope'].split('.') + translation_components if filter_attributes['scope']
|
27
|
+
|
28
|
+
return add_translation_offense(node:, lang:) if @platformos_app.translations_hash.empty?
|
29
|
+
|
30
|
+
hash = @platformos_app.translations_hash[lang] || {}
|
31
|
+
index = 0
|
32
|
+
while translation_components[index]
|
33
|
+
hash = hash[translation_components[index]]
|
34
|
+
if hash.nil?
|
35
|
+
add_translation_offense(node:, lang:)
|
36
|
+
break
|
37
|
+
end
|
38
|
+
index += 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def add_translation_offense(node:, lang:)
|
45
|
+
add_offense("Translation `#{lang}.#{node.value.name}` does not exists", node:)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -6,6 +6,9 @@ module PlatformosCheck
|
|
6
6
|
doc docs_url(__FILE__)
|
7
7
|
severity :error
|
8
8
|
|
9
|
+
NOTIFICATION_GLOBAL_OBJECTS = %w[data response form].freeze
|
10
|
+
FORM_GLOBAL_OBJECTS = %w[form form_builder].freeze
|
11
|
+
|
9
12
|
class TemplateInfo
|
10
13
|
def initialize(app_file: nil)
|
11
14
|
@all_variable_lookups = {}
|
@@ -19,7 +22,8 @@ module PlatformosCheck
|
|
19
22
|
attr_reader :all_assigns, :all_captures, :all_forloops, :app_file, :all_renders
|
20
23
|
|
21
24
|
def add_render(name:, node:)
|
22
|
-
@all_renders[name]
|
25
|
+
@all_renders[name] ||= []
|
26
|
+
@all_renders[name] << node
|
23
27
|
end
|
24
28
|
|
25
29
|
def add_variable_lookup(name:, node:)
|
@@ -39,8 +43,10 @@ module PlatformosCheck
|
|
39
43
|
end
|
40
44
|
|
41
45
|
def each_partial
|
42
|
-
@all_renders.each do |(name,
|
43
|
-
|
46
|
+
@all_renders.each do |(name, nodes)|
|
47
|
+
nodes.each do |node|
|
48
|
+
yield [name, node]
|
49
|
+
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
@@ -56,14 +62,17 @@ module PlatformosCheck
|
|
56
62
|
yield [key, info]
|
57
63
|
end
|
58
64
|
end
|
65
|
+
|
66
|
+
def first_declaration(name)
|
67
|
+
[all_assigns[name], all_captures[name]].compact.min_by(&:line_number)
|
68
|
+
end
|
59
69
|
end
|
60
70
|
|
61
71
|
def self.single_file(**_args)
|
62
72
|
true
|
63
73
|
end
|
64
74
|
|
65
|
-
def initialize
|
66
|
-
@config_type = config_type
|
75
|
+
def initialize
|
67
76
|
@files = {}
|
68
77
|
end
|
69
78
|
|
@@ -72,15 +81,15 @@ module PlatformosCheck
|
|
72
81
|
end
|
73
82
|
|
74
83
|
def on_assign(node)
|
75
|
-
@files[node.app_file.name].all_assigns[node.value.to]
|
84
|
+
@files[node.app_file.name].all_assigns[node.value.to] ||= node
|
76
85
|
end
|
77
86
|
|
78
87
|
def on_capture(node)
|
79
|
-
@files[node.app_file.name].all_captures[node.value.instance_variable_get(:@to)]
|
88
|
+
@files[node.app_file.name].all_captures[node.value.instance_variable_get(:@to)] ||= node
|
80
89
|
end
|
81
90
|
|
82
91
|
def on_parse_json(node)
|
83
|
-
@files[node.app_file.name].all_captures[node.value.to]
|
92
|
+
@files[node.app_file.name].all_captures[node.value.to] ||= node
|
84
93
|
end
|
85
94
|
|
86
95
|
def on_for(node)
|
@@ -102,7 +111,7 @@ module PlatformosCheck
|
|
102
111
|
end
|
103
112
|
|
104
113
|
def on_function(node)
|
105
|
-
@files[node.app_file.name].all_assigns[node.value.to]
|
114
|
+
@files[node.app_file.name].all_assigns[node.value.to] ||= node
|
106
115
|
|
107
116
|
return unless node.value.from.is_a?(String)
|
108
117
|
|
@@ -113,13 +122,13 @@ module PlatformosCheck
|
|
113
122
|
end
|
114
123
|
|
115
124
|
def on_graphql(node)
|
116
|
-
@files[node.app_file.name].all_assigns[node.value.to]
|
125
|
+
@files[node.app_file.name].all_assigns[node.value.to] ||= node
|
117
126
|
end
|
118
127
|
|
119
128
|
def on_background(node)
|
120
129
|
return unless node.value.partial_syntax
|
121
130
|
|
122
|
-
@files[node.app_file.name].all_assigns[node.value.to]
|
131
|
+
@files[node.app_file.name].all_assigns[node.value.to] ||= node
|
123
132
|
|
124
133
|
return unless node.value.partial_name.is_a?(String)
|
125
134
|
|
@@ -155,10 +164,10 @@ module PlatformosCheck
|
|
155
164
|
each_template do |(_name, info)|
|
156
165
|
if info.app_file.notification?
|
157
166
|
# NOTE: `data` comes from graphql for notifications
|
158
|
-
check_object(info, all_global_objects +
|
167
|
+
check_object(info, all_global_objects + NOTIFICATION_GLOBAL_OBJECTS)
|
159
168
|
elsif info.app_file.form?
|
160
169
|
# NOTE: `data` comes from graphql for notifications
|
161
|
-
check_object(info, all_global_objects +
|
170
|
+
check_object(info, all_global_objects + FORM_GLOBAL_OBJECTS)
|
162
171
|
else
|
163
172
|
check_object(info, all_global_objects)
|
164
173
|
end
|
@@ -167,42 +176,37 @@ module PlatformosCheck
|
|
167
176
|
|
168
177
|
private
|
169
178
|
|
170
|
-
attr_reader :config_type
|
171
|
-
|
172
179
|
def each_template
|
173
180
|
@files.each do |(name, info)|
|
174
181
|
yield [name, info]
|
175
182
|
end
|
176
183
|
end
|
177
184
|
|
178
|
-
def check_object(info, all_global_objects, render_node = nil,
|
185
|
+
def check_object(info, all_global_objects, render_node = nil, level = 0)
|
179
186
|
return if level > 1
|
180
187
|
|
181
188
|
check_undefined(info, all_global_objects, render_node) unless info.app_file.partial? && render_node.nil? # ||
|
182
189
|
|
183
190
|
info.each_partial do |(partial_name, node)|
|
184
|
-
next
|
191
|
+
next unless @files[partial_name] # NOTE: undefined partial
|
185
192
|
|
186
193
|
partial_info = @files[partial_name]
|
187
|
-
|
188
|
-
next unless partial_info # NOTE: undefined partial
|
189
|
-
|
190
194
|
partial_variables = node.value.attributes.keys +
|
191
195
|
[node.value.instance_variable_get(:@alias_name)]
|
192
|
-
|
193
|
-
check_object(partial_info, all_global_objects + partial_variables, node,
|
196
|
+
|
197
|
+
check_object(partial_info, all_global_objects + partial_variables, node, level + 1)
|
194
198
|
end
|
195
199
|
end
|
196
200
|
|
197
201
|
def check_undefined(info, all_global_objects, render_node)
|
198
|
-
all_variables = info.all_variables
|
199
202
|
potentially_unused_variables = render_node.value.attributes.keys if render_node
|
203
|
+
missing_arguments = []
|
200
204
|
info.each_variable_lookup(!!render_node) do |(key, node)|
|
201
205
|
name, line_number = key
|
202
206
|
|
203
207
|
potentially_unused_variables&.delete(name)
|
204
208
|
|
205
|
-
next if all_variables.include?(name)
|
209
|
+
next if info.all_variables.include?(name) && variable_declared_before_used?(name, info, line_number)
|
206
210
|
next if all_global_objects.include?(name)
|
207
211
|
|
208
212
|
node = node.parent
|
@@ -211,7 +215,7 @@ module PlatformosCheck
|
|
211
215
|
next if node.variable? && node.filters.any? { |(filter_name)| filter_name == "default" }
|
212
216
|
|
213
217
|
if render_node
|
214
|
-
|
218
|
+
missing_arguments << name
|
215
219
|
elsif !info.app_file.partial?
|
216
220
|
add_offense("Undefined object `#{name}`", node:, line_number:)
|
217
221
|
end
|
@@ -219,8 +223,33 @@ module PlatformosCheck
|
|
219
223
|
|
220
224
|
potentially_unused_variables -= render_node.value.internal_attributes if render_node && render_node.value.respond_to?(:internal_attributes)
|
221
225
|
potentially_unused_variables&.each do |name|
|
222
|
-
add_offense("Unused argument `#{name}`", node: render_node)
|
226
|
+
add_offense("Unused argument `#{name}`", node: render_node) do |corrector|
|
227
|
+
match = render_node.markup.match(/(?<attribute>,?\s*#{name}\s*:\s*#{Liquid::QuotedFragment})\s*/)
|
228
|
+
|
229
|
+
corrector.replace(render_node, render_node.markup.sub(match[:attribute], ''), render_node.start_index...render_node.end_index)
|
230
|
+
end
|
223
231
|
end
|
232
|
+
|
233
|
+
return if missing_arguments.empty?
|
234
|
+
|
235
|
+
add_offense("Missing arguments: #{missing_arguments.map { |name| "`#{name}`" }.join(', ')}", node: render_node) do |corrector|
|
236
|
+
new_attributes = ''
|
237
|
+
missing_arguments.each do |name|
|
238
|
+
new_attributes += ", #{name}: "
|
239
|
+
new_attributes += @files[render_node.app_file.name].all_assigns.key?(name) ? name : 'null'
|
240
|
+
end
|
241
|
+
|
242
|
+
start_pos = render_node.end_index
|
243
|
+
start_pos -= 1 while start_pos > 0 && render_node.source[start_pos - 1] == ' '
|
244
|
+
corrector.replace(render_node, new_attributes, start_pos...start_pos)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def variable_declared_before_used?(name, info, line_number_when_used)
|
249
|
+
declaration = info.first_declaration(name)
|
250
|
+
return true if declaration.nil?
|
251
|
+
|
252
|
+
declaration.line_number <= line_number_when_used
|
224
253
|
end
|
225
254
|
end
|
226
255
|
end
|
@@ -8,7 +8,7 @@ module PlatformosCheck
|
|
8
8
|
|
9
9
|
FLOW_COMMAND = %i[break continue return]
|
10
10
|
CONDITION_TYPES = Set.new(%i[condition else_condition])
|
11
|
-
INCLUDE_FLOW_COMMAND = %w[break]
|
11
|
+
INCLUDE_FLOW_COMMAND = %w[break].freeze
|
12
12
|
|
13
13
|
def on_document(node)
|
14
14
|
@processed_files = {}
|
@@ -94,16 +94,16 @@ module PlatformosCheck
|
|
94
94
|
@processed_files[path]
|
95
95
|
end
|
96
96
|
|
97
|
-
def include_node_contains_flow_command?(
|
98
|
-
return false if
|
97
|
+
def include_node_contains_flow_command?(node)
|
98
|
+
return false if node.nil?
|
99
99
|
|
100
|
-
|
101
|
-
if INCLUDE_FLOW_COMMAND.include?(
|
100
|
+
node.nodelist.any? do |n|
|
101
|
+
if INCLUDE_FLOW_COMMAND.include?(n.respond_to?(:tag_name) && n.tag_name)
|
102
102
|
true
|
103
|
-
elsif
|
104
|
-
include_node_contains_flow_command?(
|
105
|
-
elsif
|
106
|
-
evaluate_include(
|
103
|
+
elsif n.respond_to?(:nodelist) && n.nodelist
|
104
|
+
include_node_contains_flow_command?(n)
|
105
|
+
elsif n.respond_to?(:tag_name) && n.tag_name == 'include' && n.template_name_expr.is_a?(String)
|
106
|
+
evaluate_include(n.template_name_expr)
|
107
107
|
else
|
108
108
|
false
|
109
109
|
end
|
@@ -7,6 +7,13 @@ module PlatformosCheck
|
|
7
7
|
category :liquid
|
8
8
|
doc docs_url(__FILE__)
|
9
9
|
|
10
|
+
TAGS_FOR_AUTO_VARIABLE_PREPEND = Set.new(%i[graphql function background]).freeze
|
11
|
+
FILTERS_THAT_MODIFY_OBJECT = Set.new(%w[array_add add_to_array
|
12
|
+
prepend_to_array array_prepend
|
13
|
+
assign_to_hash_key hash_add_key add_hash_key
|
14
|
+
remove_hash_key hash_delete_key delete_hash_key]).freeze
|
15
|
+
PREPEND_CHARACTER = '_'
|
16
|
+
|
10
17
|
class TemplateInfo < Struct.new(:used_assigns, :assign_nodes, :includes)
|
11
18
|
def collect_used_assigns(templates, visited = Set.new)
|
12
19
|
collected = used_assigns
|
@@ -34,7 +41,8 @@ module PlatformosCheck
|
|
34
41
|
end
|
35
42
|
|
36
43
|
def on_assign(node)
|
37
|
-
return if
|
44
|
+
return if ignore_prepended?(node)
|
45
|
+
return if node.value.from.filters.any? { |filter_name, *_arguments| FILTERS_THAT_MODIFY_OBJECT.include?(filter_name) }
|
38
46
|
|
39
47
|
@templates[node.app_file.name].assign_nodes[node.value.to] = node
|
40
48
|
end
|
@@ -44,13 +52,21 @@ module PlatformosCheck
|
|
44
52
|
end
|
45
53
|
|
46
54
|
def on_function(node)
|
47
|
-
return if
|
55
|
+
return if ignore_prepended?(node)
|
48
56
|
|
49
57
|
@templates[node.app_file.name].assign_nodes[node.value.to] = node
|
50
58
|
end
|
51
59
|
|
52
60
|
def on_graphql(node)
|
53
|
-
return if
|
61
|
+
return if node.value.to.nil?
|
62
|
+
return if ignore_prepended?(node)
|
63
|
+
|
64
|
+
@templates[node.app_file.name].assign_nodes[node.value.to] = node
|
65
|
+
end
|
66
|
+
|
67
|
+
def on_background(node)
|
68
|
+
return if node.value.to.nil?
|
69
|
+
return if ignore_prepended?(node)
|
54
70
|
|
55
71
|
@templates[node.app_file.name].assign_nodes[node.value.to] = node
|
56
72
|
end
|
@@ -77,25 +93,8 @@ module PlatformosCheck
|
|
77
93
|
next if used.include?(name)
|
78
94
|
|
79
95
|
add_offense("`#{name}` is never used", node:) do |corrector|
|
80
|
-
|
81
|
-
|
82
|
-
offset = node.markup.match(/^graphql\s+/)[0].size
|
83
|
-
|
84
|
-
corrector.insert_before(
|
85
|
-
node,
|
86
|
-
'_',
|
87
|
-
(node.start_index + offset)...(node.start_index + offset)
|
88
|
-
)
|
89
|
-
when :function
|
90
|
-
offset = node.markup.match(/^function\s+/)[0].size
|
91
|
-
|
92
|
-
corrector.insert_before(
|
93
|
-
node,
|
94
|
-
'_',
|
95
|
-
(node.start_index + offset)...(node.start_index + offset)
|
96
|
-
)
|
97
|
-
when :parse_json
|
98
|
-
# noop
|
96
|
+
if TAGS_FOR_AUTO_VARIABLE_PREPEND.include?(node.type_name)
|
97
|
+
prepend_variable(node, corrector)
|
99
98
|
else
|
100
99
|
corrector.remove(node)
|
101
100
|
end
|
@@ -106,8 +105,18 @@ module PlatformosCheck
|
|
106
105
|
|
107
106
|
private
|
108
107
|
|
109
|
-
def
|
110
|
-
node.value.to.start_with?(
|
108
|
+
def ignore_prepended?(node)
|
109
|
+
node.value.to.start_with?(PREPEND_CHARACTER)
|
110
|
+
end
|
111
|
+
|
112
|
+
def prepend_variable(node, corrector)
|
113
|
+
offset = node.markup.match(/^#{node.type_name}\s+/)[0].size
|
114
|
+
|
115
|
+
corrector.insert_before(
|
116
|
+
node,
|
117
|
+
PREPEND_CHARACTER,
|
118
|
+
(node.start_index + offset)...(node.start_index + offset)
|
119
|
+
)
|
111
120
|
end
|
112
121
|
end
|
113
122
|
end
|
data/lib/platformos_check/cli.rb
CHANGED
@@ -188,7 +188,7 @@ module PlatformosCheck
|
|
188
188
|
def check(out_stream = STDOUT)
|
189
189
|
update_docs
|
190
190
|
|
191
|
-
warn "Checking #{@config.root}
|
191
|
+
warn "Checking #{@config.root}:"
|
192
192
|
storage = PlatformosCheck::FileSystemStorage.new(@config.root, ignored_patterns: @config.ignored_patterns)
|
193
193
|
raise Abort, "No platformos_app files found." if storage.platformos_app.all.empty?
|
194
194
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def deep_merge!(other_hash, &block)
|
5
|
+
merge!(other_hash) do |key, this_val, other_val|
|
6
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
7
|
+
this_val.deep_merge(other_val, &block)
|
8
|
+
elsif block
|
9
|
+
yield(key, this_val, other_val)
|
10
|
+
else
|
11
|
+
other_val
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def deep_merge(other_hash, &)
|
17
|
+
dup.deep_merge!(other_hash, &)
|
18
|
+
end
|
19
|
+
end
|
@@ -32,13 +32,29 @@ module PlatformosCheck
|
|
32
32
|
PARTIAL_GRAPHQL = partial_tag_with_result('graphql')
|
33
33
|
PARTIAL_BACKGROUND = partial_tag_with_result('background')
|
34
34
|
|
35
|
+
TAGS_FOR_FILTERS = 'echo|print|log|hash_assign|assign'
|
36
|
+
TRANSLATION_FILTERS_NAMES = 'translate|t_escape|translate_escape|t[^\\w]'
|
37
|
+
OPTIONAL_SCOPE_ARGUMENT = %((:?([\\w:'"\\s]*)\\s*(scope:\\s*['"](?<scope>[^'"]*)['"]))?)
|
38
|
+
|
39
|
+
LOCALIZE_FILTERS_NAMES = ''
|
40
|
+
|
35
41
|
ASSET_INCLUDE = /
|
36
42
|
\{\{-?\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
|
37
43
|
\{\{-?\s*"(?<partial>[^"]*)"\s*\|\s*asset_url|
|
38
44
|
|
39
45
|
# in liquid tags the whole line is white space until the asset partial
|
40
|
-
^\s*(
|
41
|
-
^\s*(
|
46
|
+
^\s*(?:#{TAGS_FOR_FILTERS}[^=]*=)\s*'(?<partial>[^']*)'\s*\|\s*asset_url|
|
47
|
+
^\s*(?:#{TAGS_FOR_FILTERS}[^=]*=)\s*"(?<partial>[^"]*)"\s*\|\s*asset_url
|
48
|
+
/mix
|
49
|
+
|
50
|
+
TRANSLATION_FILTER = /
|
51
|
+
'(?<key>[^']*)'\s*\|\s*(#{TRANSLATION_FILTERS_NAMES})#{OPTIONAL_SCOPE_ARGUMENT}|
|
52
|
+
"(?<key>[^"]*)"\s*\|\s*(#{TRANSLATION_FILTERS_NAMES})#{OPTIONAL_SCOPE_ARGUMENT}
|
53
|
+
/mix
|
54
|
+
|
55
|
+
LOCALIZE_FILTER = /
|
56
|
+
[\s\w'"-:.]+\|\s*(localize|l):\s*'(?<key>[^']*)'|
|
57
|
+
[\s\w'"-:.]+\|\s*(localize|l):\s*"(?<key>[^"]*)"
|
42
58
|
/mix
|
43
59
|
end
|
44
60
|
end
|
@@ -7,6 +7,16 @@ module PlatformosCheck
|
|
7
7
|
include PositionHelper
|
8
8
|
include URIHelper
|
9
9
|
|
10
|
+
class DefaultTranslationFile
|
11
|
+
def initialize(default_language)
|
12
|
+
@default_language = default_language
|
13
|
+
end
|
14
|
+
|
15
|
+
def relative_path
|
16
|
+
Pathname.new(@default_language, "#{@default_language}.yml")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
10
20
|
class << self
|
11
21
|
attr_accessor :partial_regexp, :app_file_type, :default_dir, :default_extension
|
12
22
|
|
@@ -41,18 +51,12 @@ module PlatformosCheck
|
|
41
51
|
|
42
52
|
def document_links(buffer, platformos_app)
|
43
53
|
matches(buffer, partial_regexp).map do |match|
|
44
|
-
start_row, start_column =
|
45
|
-
buffer,
|
46
|
-
match.begin(:partial)
|
47
|
-
)
|
54
|
+
start_row, start_column = start_coordinates(buffer, match)
|
48
55
|
|
49
|
-
end_row, end_column =
|
50
|
-
buffer,
|
51
|
-
match.end(:partial)
|
52
|
-
)
|
56
|
+
end_row, end_column = end_coordinates(buffer, match)
|
53
57
|
|
54
58
|
{
|
55
|
-
target: file_link(match
|
59
|
+
target: file_link(match, platformos_app),
|
56
60
|
range: {
|
57
61
|
start: {
|
58
62
|
line: start_row,
|
@@ -67,13 +71,66 @@ module PlatformosCheck
|
|
67
71
|
end
|
68
72
|
end
|
69
73
|
|
70
|
-
def
|
74
|
+
def start_coordinates(buffer, match)
|
75
|
+
from_index_to_row_column(
|
76
|
+
buffer,
|
77
|
+
match.begin(:partial)
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def end_coordinates(buffer, match)
|
82
|
+
from_index_to_row_column(
|
83
|
+
buffer,
|
84
|
+
match.end(:partial)
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def file_link(match, platformos_app)
|
89
|
+
partial = match[:partial]
|
71
90
|
relative_path = platformos_app.send(app_file_type).detect { |f| f.name == partial }&.relative_path
|
72
91
|
relative_path ||= default_relative_path(partial)
|
73
92
|
|
74
93
|
file_uri(@storage.path(relative_path))
|
75
94
|
end
|
76
95
|
|
96
|
+
def translation_file_link(match, platformos_app)
|
97
|
+
@current_best_fit = platformos_app.translations.first || DefaultTranslationFile.new(platformos_app.default_language)
|
98
|
+
@current_best_fit_level = 0
|
99
|
+
array_of_translation_components = translation_components_for_match(match)
|
100
|
+
platformos_app.translations.each do |translation_file|
|
101
|
+
array_of_translation_components.each do |translation_components|
|
102
|
+
exact_match_level = translation_components.size
|
103
|
+
component_result = translation_file.content[platformos_app.default_language]
|
104
|
+
next if component_result.nil?
|
105
|
+
|
106
|
+
i = 0
|
107
|
+
while i < exact_match_level
|
108
|
+
component_result = yaml(component_result, translation_components[i])
|
109
|
+
|
110
|
+
break if component_result.nil?
|
111
|
+
|
112
|
+
i += 1
|
113
|
+
if i > @current_best_fit_level
|
114
|
+
@current_best_fit = translation_file
|
115
|
+
@current_best_fit_level = i
|
116
|
+
end
|
117
|
+
|
118
|
+
break unless component_result.is_a?(Hash)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
file_uri(@storage.path(@current_best_fit&.relative_path))
|
124
|
+
end
|
125
|
+
|
126
|
+
def translation_components_for_match(match)
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
129
|
+
|
130
|
+
def yaml(component_result, component)
|
131
|
+
component_result[component]
|
132
|
+
end
|
133
|
+
|
77
134
|
def default_relative_path(partial)
|
78
135
|
return Pathname.new("app/#{default_dir}/#{partial}#{default_extension}") unless partial.start_with?('modules/')
|
79
136
|
|
data/lib/platformos_check/language_server/document_link_providers/localize_document_link_provider.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PlatformosCheck
|
4
|
+
module LanguageServer
|
5
|
+
class LocalizeDocumentLinkProvider < DocumentLinkProvider
|
6
|
+
@partial_regexp = LOCALIZE_FILTER
|
7
|
+
@app_file_type = :translations
|
8
|
+
@default_dir = 'translations'
|
9
|
+
@default_extension = '.yml'
|
10
|
+
|
11
|
+
def file_link(match, platformos_app)
|
12
|
+
translation_file_link(match, platformos_app)
|
13
|
+
end
|
14
|
+
|
15
|
+
def translation_components_for_match(match)
|
16
|
+
key = match[:key].split('.')
|
17
|
+
[
|
18
|
+
%w[time formats] + key,
|
19
|
+
%w[date formats] + key
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
def start_coordinates(buffer, match)
|
24
|
+
from_index_to_row_column(
|
25
|
+
buffer,
|
26
|
+
match.begin(:key)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def end_coordinates(buffer, match)
|
31
|
+
from_index_to_row_column(
|
32
|
+
buffer,
|
33
|
+
match.end(:key)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|