platformos-check 0.4.8 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|