decidim-dev 0.31.3 → 0.32.0.rc1
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/README.md +4 -12
- data/app/models/decidim/dev/dummy_resource.rb +4 -1
- data/app/models/decidim/dev/nested_dummy_resource.rb +1 -0
- data/config/locales/ar.yml +1 -2
- data/config/locales/bg.yml +1 -2
- data/config/locales/ca-IT.yml +1 -2
- data/config/locales/ca.yml +1 -2
- data/config/locales/cs.yml +1 -2
- data/config/locales/de.yml +1 -2
- data/config/locales/el.yml +1 -2
- data/config/locales/es-MX.yml +1 -2
- data/config/locales/es-PY.yml +1 -2
- data/config/locales/es.yml +1 -2
- data/config/locales/eu.yml +1 -2
- data/config/locales/fi-plain.yml +1 -2
- data/config/locales/fi.yml +1 -2
- data/config/locales/fr-CA.yml +1 -2
- data/config/locales/fr.yml +1 -2
- data/config/locales/ga-IE.yml +0 -1
- data/config/locales/gl.yml +1 -2
- data/config/locales/hu.yml +1 -2
- data/config/locales/id-ID.yml +1 -2
- data/config/locales/is-IS.yml +1 -2
- data/config/locales/it.yml +1 -2
- data/config/locales/ja.yml +1 -2
- data/config/locales/lt.yml +1 -2
- data/config/locales/lv.yml +1 -2
- data/config/locales/nl.yml +1 -2
- data/config/locales/no.yml +1 -2
- data/config/locales/pl.yml +1 -2
- data/config/locales/pt-BR.yml +1 -2
- data/config/locales/pt.yml +1 -2
- data/config/locales/ro-RO.yml +1 -2
- data/config/locales/ru.yml +1 -2
- data/config/locales/sk.yml +1 -2
- data/config/locales/sl.yml +0 -1
- data/config/locales/sv.yml +1 -2
- data/config/locales/tr-TR.yml +1 -2
- data/config/locales/uk.yml +0 -1
- data/config/locales/zh-CN.yml +1 -2
- data/config/locales/zh-TW.yml +1 -2
- data/config/rubocop/decidim-linters/configuration.yml +8 -0
- data/config/rubocop/rails/disabled.yml +6 -0
- data/config/rubocop/rspec/configuration.yml +5 -0
- data/config/rubocop/ruby/configuration.yml +2 -3
- data/config/rubocop/ruby/disabled.yml +9 -0
- data/config/rubocop/yard/configuration.yml +1 -0
- data/decidim-dev.gemspec +21 -23
- data/lib/decidim/dev/assets/assemblies.json +1 -21
- data/lib/decidim/dev/assets/assemblies_with_null.json +1 -2
- data/lib/decidim/dev/assets/participatory_processes.json +1 -4
- data/lib/decidim/dev/assets/participatory_processes_with_null.json +1 -1
- data/lib/decidim/dev/dummy_translator.rb +1 -1
- data/lib/decidim/dev/rubocop/cop/decidim/message_antipattern.rb +76 -0
- data/lib/decidim/dev/test/rspec_support/active_job.rb +5 -0
- data/lib/decidim/dev/test/rspec_support/component_context.rb +1 -1
- data/lib/decidim/dev/test/rspec_support/helpers.rb +9 -3
- data/lib/decidim/dev/test/rspec_support/tom_select.rb +7 -4
- data/lib/decidim/dev/version.rb +1 -1
- data/lib/decidim/dev.rb +0 -1
- data/lib/erb_lint/linters/admin_page_title_linter.rb +46 -0
- data/lib/erb_lint/linters/partial_path_linter.rb +101 -0
- data/rubocop-decidim.yml +2 -0
- metadata +134 -64
- /data/lib/decidim/dev/assets/{import_participatory_space_private_users.csv → import_members.csv} +0 -0
- /data/lib/decidim/dev/assets/{import_participatory_space_private_users_invalid_col_sep.csv → import_members_invalid_col_sep.csv} +0 -0
- /data/lib/decidim/dev/assets/{import_participatory_space_private_users_iso8859-1.csv → import_members_iso8859-1.csv} +0 -0
- /data/lib/decidim/dev/assets/{import_participatory_space_private_users_nok.csv → import_members_nok.csv} +0 -0
- /data/lib/decidim/dev/assets/{import_participatory_space_private_users_with_bom.csv → import_members_with_bom.csv} +0 -0
data/decidim-dev.gemspec
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
$LOAD_PATH.push File.expand_path("lib", __dir__)
|
|
4
4
|
|
|
5
|
-
# Maintain your gem's version:
|
|
6
|
-
require "decidim/dev/version"
|
|
7
|
-
|
|
8
|
-
# Describe your gem and declare its dependencies:
|
|
9
5
|
Gem::Specification.new do |s|
|
|
10
|
-
|
|
6
|
+
version = "0.32.0.rc1"
|
|
7
|
+
s.version = version
|
|
11
8
|
s.authors = ["Josep Jaume Rey Peroy", "Marc Riera Casals", "Oriol Gual Oliva"]
|
|
12
9
|
s.email = ["josepjaume@gmail.com", "mrc2407@gmail.com", "oriolgual@gmail.com"]
|
|
13
10
|
s.license = "AGPL-3.0-or-later"
|
|
@@ -19,7 +16,7 @@ Gem::Specification.new do |s|
|
|
|
19
16
|
"homepage_uri" => "https://decidim.org",
|
|
20
17
|
"source_code_uri" => "https://github.com/decidim/decidim"
|
|
21
18
|
}
|
|
22
|
-
s.required_ruby_version = "~> 3.
|
|
19
|
+
s.required_ruby_version = "~> 3.4.0"
|
|
23
20
|
|
|
24
21
|
s.name = "decidim-dev"
|
|
25
22
|
s.summary = "Decidim dev tools"
|
|
@@ -33,42 +30,43 @@ Gem::Specification.new do |s|
|
|
|
33
30
|
end
|
|
34
31
|
|
|
35
32
|
s.add_dependency "capybara", "~> 3.39"
|
|
36
|
-
s.add_dependency "decidim-admin",
|
|
37
|
-
s.add_dependency "decidim-api",
|
|
38
|
-
s.add_dependency "decidim-comments",
|
|
39
|
-
s.add_dependency "decidim-core",
|
|
40
|
-
s.add_dependency "decidim-generators",
|
|
41
|
-
s.add_dependency "decidim-verifications",
|
|
33
|
+
s.add_dependency "decidim-admin", version
|
|
34
|
+
s.add_dependency "decidim-api", version
|
|
35
|
+
s.add_dependency "decidim-comments", version
|
|
36
|
+
s.add_dependency "decidim-core", version
|
|
37
|
+
s.add_dependency "decidim-generators", version
|
|
38
|
+
s.add_dependency "decidim-verifications", version
|
|
42
39
|
s.add_dependency "factory_bot_rails", "~> 6.2"
|
|
43
40
|
s.add_dependency "faker", "~> 3.2"
|
|
44
41
|
|
|
45
|
-
s.add_dependency "bullet", "~> 8.
|
|
46
|
-
s.add_dependency "byebug", "
|
|
47
|
-
s.add_dependency "erb_lint", "
|
|
42
|
+
s.add_dependency "bullet", "~> 8.1.0"
|
|
43
|
+
s.add_dependency "byebug", ">= 11", "< 14"
|
|
44
|
+
s.add_dependency "erb_lint", ">= 0.8", "< 0.10"
|
|
48
45
|
s.add_dependency "i18n-tasks", "~> 1.0"
|
|
49
46
|
s.add_dependency "nokogiri", "~> 1.16", ">= 1.16.2"
|
|
50
|
-
s.add_dependency "parallel_tests", "
|
|
51
|
-
s.add_dependency "puma", "
|
|
47
|
+
s.add_dependency "parallel_tests", ">= 4.2", "< 6.0"
|
|
48
|
+
s.add_dependency "puma", ">= 6.5", "< 8.0"
|
|
52
49
|
s.add_dependency "rails-controller-testing", "~> 1.0"
|
|
53
50
|
s.add_dependency "rspec", "~> 3.12"
|
|
54
51
|
s.add_dependency "rspec-cells", "~> 0.3.7"
|
|
55
52
|
s.add_dependency "rspec-html-matchers", "~> 0.10"
|
|
56
53
|
s.add_dependency "rspec_junit_formatter", "~> 0.6.0"
|
|
57
|
-
s.add_dependency "rspec-rails", "
|
|
54
|
+
s.add_dependency "rspec-rails", ">= 6", "< 9"
|
|
58
55
|
s.add_dependency "rspec-retry", "~> 0.6.2"
|
|
59
|
-
s.add_dependency "rubocop", "
|
|
56
|
+
s.add_dependency "rubocop", ">= 1.78", "< 1.87"
|
|
60
57
|
s.add_dependency "rubocop-capybara", "~> 2.22.0", ">= 2.22.1"
|
|
61
|
-
s.add_dependency "rubocop-factory_bot", "
|
|
58
|
+
s.add_dependency "rubocop-factory_bot", ">= 2.27", "< 2.29"
|
|
62
59
|
s.add_dependency "rubocop-faker", "~> 1.3", ">= 1.3.0"
|
|
63
60
|
s.add_dependency "rubocop-graphql", "~> 1.5", ">= 1.5.6"
|
|
64
61
|
s.add_dependency "rubocop-performance", "~> 1.25", ">= 1.25.0"
|
|
65
|
-
s.add_dependency "rubocop-rails", "
|
|
62
|
+
s.add_dependency "rubocop-rails", ">= 2.32", "< 2.35"
|
|
66
63
|
s.add_dependency "rubocop-rspec", "~> 3.0", ">= 3.6.0"
|
|
67
|
-
s.add_dependency "rubocop-rspec_rails", "
|
|
64
|
+
s.add_dependency "rubocop-rspec_rails", ">= 2.31", "< 2.33"
|
|
68
65
|
s.add_dependency "rubocop-rubycw", "~> 0.2.0"
|
|
66
|
+
s.add_dependency "rubocop-yard", ">= 1.0", "< 1.2"
|
|
69
67
|
s.add_dependency "selenium-webdriver", "~> 4.9"
|
|
70
68
|
s.add_dependency "simplecov", "~> 0.22.0"
|
|
71
|
-
s.add_dependency "simplecov-cobertura", "~>
|
|
69
|
+
s.add_dependency "simplecov-cobertura", "~> 3.1.0"
|
|
72
70
|
s.add_dependency "spring", "~> 4.0"
|
|
73
71
|
s.add_dependency "spring-watcher-listen", "~> 2.0"
|
|
74
72
|
s.add_dependency "w3c_rspec_validators", "~> 0.3.0"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"es": "Incidunt provident doloremque."
|
|
48
48
|
},
|
|
49
49
|
"decidim_scope_id": null,
|
|
50
|
+
"access_mode": "open",
|
|
50
51
|
"paticipatory_scope": {
|
|
51
52
|
"ca": "Assumenda.",
|
|
52
53
|
"en": "Quidem.",
|
|
@@ -58,7 +59,6 @@
|
|
|
58
59
|
"es": "Et molestiae."
|
|
59
60
|
},
|
|
60
61
|
"scopes_enabled": true,
|
|
61
|
-
"private_space": false,
|
|
62
62
|
"reference": "PhD-ASSE-2020-01-1",
|
|
63
63
|
"purpose_of_action": {
|
|
64
64
|
"ca": "<p>Velit a qui. Debitis rem occaecati. Id minus est.</p>",
|
|
@@ -90,7 +90,6 @@
|
|
|
90
90
|
"en": "<p>Optio ducimus velit. Facilis omnis asperiores. Corporis ipsa non.</p>",
|
|
91
91
|
"es": "<p>Est dolorum et. Quo sed ut. Corporis quos sit.</p>"
|
|
92
92
|
},
|
|
93
|
-
"is_transparent": true,
|
|
94
93
|
"special_features": {
|
|
95
94
|
"ca": "<p>Dolor et reprehenderit. Unde soluta eum. Omnis occaecati omnis.</p>",
|
|
96
95
|
"en": "<p>Et atque perspiciatis. Quas impedit corporis. Nesciunt blanditiis ipsa.</p>",
|
|
@@ -106,7 +105,6 @@
|
|
|
106
105
|
"en": "ut",
|
|
107
106
|
"es": "rerum"
|
|
108
107
|
},
|
|
109
|
-
"decidim_assemblies_type_id": 1,
|
|
110
108
|
"area": {
|
|
111
109
|
"id": null,
|
|
112
110
|
"name": {
|
|
@@ -514,7 +512,6 @@
|
|
|
514
512
|
"geocoding_enabled": false,
|
|
515
513
|
"attachments_allowed": false,
|
|
516
514
|
"resources_permissions_enabled": true,
|
|
517
|
-
"collaborative_drafts_enabled": true,
|
|
518
515
|
"participatory_texts_enabled": false,
|
|
519
516
|
"amendments_enabled": false,
|
|
520
517
|
"amendments_wizard_help_text": {
|
|
@@ -578,23 +575,6 @@
|
|
|
578
575
|
"permissions": null,
|
|
579
576
|
"published_at": "2020-01-22 07:55:05 UTC"
|
|
580
577
|
},
|
|
581
|
-
{
|
|
582
|
-
"manifest_name": "sortitions",
|
|
583
|
-
"id": 26,
|
|
584
|
-
"name": {
|
|
585
|
-
"ca": "Sortejos",
|
|
586
|
-
"en": "Sortitions",
|
|
587
|
-
"es": "Sorteos"
|
|
588
|
-
},
|
|
589
|
-
"participatory_space_id": 1,
|
|
590
|
-
"participatory_space_type": "Decidim::Assembly",
|
|
591
|
-
"settings": {
|
|
592
|
-
"comments_enabled": true
|
|
593
|
-
},
|
|
594
|
-
"weight": 0,
|
|
595
|
-
"permissions": null,
|
|
596
|
-
"published_at": "2020-01-22 07:55:19 UTC"
|
|
597
|
-
},
|
|
598
578
|
{
|
|
599
579
|
"manifest_name": "surveys",
|
|
600
580
|
"id": 23,
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"es": "Et molestiae."
|
|
59
59
|
},
|
|
60
60
|
"scopes_enabled": true,
|
|
61
|
-
"
|
|
61
|
+
"access_mode": "open",
|
|
62
62
|
"reference": "PhD-ASSE-2020-01-1",
|
|
63
63
|
"purpose_of_action": {
|
|
64
64
|
"ca": "<p>Velit a qui. Debitis rem occaecati. Id minus est.</p>",
|
|
@@ -106,7 +106,6 @@
|
|
|
106
106
|
"en": "ut",
|
|
107
107
|
"es": "rerum"
|
|
108
108
|
},
|
|
109
|
-
"decidim_assemblies_type_id": 1,
|
|
110
109
|
"area": {
|
|
111
110
|
"id": null,
|
|
112
111
|
"name": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"es": "South Carolina"
|
|
88
88
|
}
|
|
89
89
|
},
|
|
90
|
-
"
|
|
90
|
+
"access_mode": "open",
|
|
91
91
|
"promoted": true,
|
|
92
92
|
"scopes_enabled": true,
|
|
93
93
|
"participatory_process_steps": [
|
|
@@ -105,9 +105,6 @@
|
|
|
105
105
|
},
|
|
106
106
|
"start_date": "2019-09-02",
|
|
107
107
|
"end_date": "2019-12-02",
|
|
108
|
-
"cta_path": null,
|
|
109
|
-
"cta_text": {
|
|
110
|
-
},
|
|
111
108
|
"active": true,
|
|
112
109
|
"position": 0
|
|
113
110
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubocop"
|
|
4
|
+
|
|
5
|
+
module RuboCop
|
|
6
|
+
module Cop
|
|
7
|
+
module Decidim
|
|
8
|
+
class MessageAntipattern < RuboCop::Cop::Base
|
|
9
|
+
SINGLE_WORD_ANTI_PATTERNS = %w(
|
|
10
|
+
successfully
|
|
11
|
+
problem
|
|
12
|
+
error
|
|
13
|
+
warning
|
|
14
|
+
done
|
|
15
|
+
complete
|
|
16
|
+
finished
|
|
17
|
+
ok
|
|
18
|
+
okay
|
|
19
|
+
saved
|
|
20
|
+
updated
|
|
21
|
+
created
|
|
22
|
+
deleted
|
|
23
|
+
removed
|
|
24
|
+
published
|
|
25
|
+
unpublished
|
|
26
|
+
).freeze
|
|
27
|
+
|
|
28
|
+
MSG = "Anti-pattern detected: avoid generic single-word text in have_callout/have_admin_callout/have_content. " \
|
|
29
|
+
"Use the full admin flash message, e.g. 'Meeting successfully published'. " \
|
|
30
|
+
"Exception: when used inside `within` blocks (e.g., for checking `.label` elements)."
|
|
31
|
+
|
|
32
|
+
def on_send(node)
|
|
33
|
+
return unless [:have_callout, :have_admin_callout, :have_content].include?(node.method_name)
|
|
34
|
+
return if within_block?(node)
|
|
35
|
+
|
|
36
|
+
first_argument = node.first_argument
|
|
37
|
+
return unless first_argument
|
|
38
|
+
|
|
39
|
+
if first_argument.nil_type?
|
|
40
|
+
add_offense(first_argument, message: MSG)
|
|
41
|
+
return
|
|
42
|
+
end
|
|
43
|
+
return unless first_argument.str_type?
|
|
44
|
+
|
|
45
|
+
text = first_argument.value
|
|
46
|
+
return unless antipattern_text?(text)
|
|
47
|
+
|
|
48
|
+
add_offense(first_argument, message: MSG)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def within_block?(node)
|
|
54
|
+
node.each_ancestor(:block).any? do |ancestor|
|
|
55
|
+
ancestor.send_node&.method_name == :within
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def antipattern_text?(text)
|
|
60
|
+
return true if text.nil?
|
|
61
|
+
return true if text.empty?
|
|
62
|
+
|
|
63
|
+
stripped_text = text.gsub(/[[:punct:]\s]/, "")
|
|
64
|
+
return true if stripped_text.empty?
|
|
65
|
+
|
|
66
|
+
single_word = text.strip !~ /\s/
|
|
67
|
+
return false unless single_word
|
|
68
|
+
|
|
69
|
+
return true if SINGLE_WORD_ANTI_PATTERNS.include?(stripped_text.downcase)
|
|
70
|
+
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -33,8 +33,8 @@ module Decidim
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def within_language_menu(options = {})
|
|
36
|
-
within(options[:admin] ? ".language-choose" : "
|
|
37
|
-
find(options[:admin] ? "#admin-menu-trigger" : "#trigger-dropdown-language-chooser").click
|
|
36
|
+
within(options[:admin] ? ".language-choose" : "header") do
|
|
37
|
+
find(options[:admin] ? "#admin-menu-trigger" : "#trigger-dropdown-menu-language-chooser-desktop").click
|
|
38
38
|
yield
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -53,12 +53,18 @@ module Decidim
|
|
|
53
53
|
expect(page).to have_css(".main-bar #trigger-dropdown-account")
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def have_callout(text)
|
|
57
57
|
within_flash_messages do
|
|
58
58
|
have_content text
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
+
# Fallback for legacy usage of this helper
|
|
63
|
+
# It actually works the same, as the markup is the same too
|
|
64
|
+
def have_admin_callout(text)
|
|
65
|
+
have_callout(text)
|
|
66
|
+
end
|
|
67
|
+
|
|
62
68
|
def stub_get_request_with_format(rq_url, rs_format)
|
|
63
69
|
stub_request(:get, rq_url)
|
|
64
70
|
.with(
|
|
@@ -9,11 +9,14 @@ module Capybara
|
|
|
9
9
|
# a better way, we could try actually interacting with the on-screen tom-select-provided UI,
|
|
10
10
|
# but we are taking the easy way out for now.
|
|
11
11
|
#
|
|
12
|
-
# @param
|
|
13
|
-
#
|
|
12
|
+
# @param select_selector [String] The CSS selector of the select element.
|
|
13
|
+
# @param option_id [String, Array<String>] The `id` value of an option in the select.
|
|
14
|
+
# For selects with the `multiple` attribute, this can be an array of such IDs.
|
|
14
15
|
#
|
|
15
|
-
# @example
|
|
16
|
-
#
|
|
16
|
+
# @example
|
|
17
|
+
# tom_select("#select_id", option_id: "2")
|
|
18
|
+
# @example
|
|
19
|
+
# tom_select("#select_id", option_id: ["2", "10"]) # For a `multiple` select input.
|
|
17
20
|
def tom_select(select_selector, option_id:)
|
|
18
21
|
js_str = %(document.querySelector("#{select_selector}").tomselect.setValue(#{option_id.inspect}))
|
|
19
22
|
execute_script(js_str)
|
data/lib/decidim/dev/version.rb
CHANGED
data/lib/decidim/dev.rb
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb_lint"
|
|
4
|
+
require "erb_lint/linter"
|
|
5
|
+
require "erb_lint/linter_config"
|
|
6
|
+
require "erb_lint/linter_registry"
|
|
7
|
+
require "erb_lint/offense"
|
|
8
|
+
|
|
9
|
+
module ERBLint
|
|
10
|
+
module Linters
|
|
11
|
+
class AdminPageTitleLinter < Linter
|
|
12
|
+
include LinterRegistry
|
|
13
|
+
|
|
14
|
+
TITLE_SNIPPET = '<% add_decidim_page_title(t(".title")) %>'
|
|
15
|
+
TITLE_SNIPPET_REGEX = /\A<%\s*add_decidim_page_title\(t\(".title".*?\)\)\s*%>/
|
|
16
|
+
|
|
17
|
+
def run(processed_source)
|
|
18
|
+
return unless admin_view?(processed_source.filename)
|
|
19
|
+
|
|
20
|
+
first_line = processed_source.file_content.to_s.lines.first
|
|
21
|
+
return if first_line&.match?(TITLE_SNIPPET_REGEX)
|
|
22
|
+
|
|
23
|
+
add_offense(
|
|
24
|
+
processed_source.to_source_range(0...0),
|
|
25
|
+
"Admin views must start with: #{TITLE_SNIPPET}"
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def admin_view?(filename)
|
|
32
|
+
return false unless filename.include?("/app/views/")
|
|
33
|
+
return false unless filename.include?("/admin/")
|
|
34
|
+
return false if filename.include?("/layouts/")
|
|
35
|
+
return false if mailer_view?(filename)
|
|
36
|
+
return false unless filename.end_with?(".html.erb")
|
|
37
|
+
|
|
38
|
+
File.basename(filename).start_with?("_") == false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def mailer_view?(filename)
|
|
42
|
+
filename.include?("/mailer/") || filename =~ %r{/\w+_mailer/}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb_lint"
|
|
4
|
+
require "erb_lint/linter"
|
|
5
|
+
require "erb_lint/linter_config"
|
|
6
|
+
require "erb_lint/linter_registry"
|
|
7
|
+
require "erb_lint/offense"
|
|
8
|
+
|
|
9
|
+
module ERBLint
|
|
10
|
+
module Linters
|
|
11
|
+
# Lint ERB partial paths.
|
|
12
|
+
#
|
|
13
|
+
# This linter ensures that partials are rendered with their full path
|
|
14
|
+
# relative to the views directory. For example, in app/views/posts/index.html.erb,
|
|
15
|
+
# it will flag `render "post"` and suggest `render "posts/post"`.
|
|
16
|
+
#
|
|
17
|
+
# It allows configuring prefixes that are exempt from this rule via
|
|
18
|
+
# the `allowed_prefixes` configuration option.
|
|
19
|
+
class PartialPath < Linter
|
|
20
|
+
include LinterRegistry
|
|
21
|
+
|
|
22
|
+
# Configuration schema for the PartialPath linter.
|
|
23
|
+
#
|
|
24
|
+
# @!attribute allowed_prefixes
|
|
25
|
+
# @return [Array<String>] List of prefixes that are allowed to be used without full path
|
|
26
|
+
class ConfigSchema < LinterConfig
|
|
27
|
+
property :allowed_prefixes, accepts: Array
|
|
28
|
+
end
|
|
29
|
+
self.config_schema = ConfigSchema
|
|
30
|
+
|
|
31
|
+
# Runs the linter on the given processed source.
|
|
32
|
+
#
|
|
33
|
+
# This method scans the source code for ERB render calls and checks if they
|
|
34
|
+
# are using the full path for partials. If not, it adds an offense.
|
|
35
|
+
#
|
|
36
|
+
# @param processed_source [ERBLint::ProcessedSource] The processed source to lint
|
|
37
|
+
# @return [void]
|
|
38
|
+
def run(processed_source)
|
|
39
|
+
file_path = processed_source.filename
|
|
40
|
+
|
|
41
|
+
return unless file_path =~ %r{app/views/(.+?)/_?([^/]+)\.html\.erb$}
|
|
42
|
+
return if file_path.include?("/cells/")
|
|
43
|
+
|
|
44
|
+
current_directory = Regexp.last_match(1)
|
|
45
|
+
source = processed_source.file_content
|
|
46
|
+
|
|
47
|
+
source.scan(/<%=\s*render\s+"([^"]+)"[^%]*%>/) do
|
|
48
|
+
partial_path = Regexp.last_match(1)
|
|
49
|
+
start_pos = Regexp.last_match.begin(1)
|
|
50
|
+
|
|
51
|
+
next if partial_path.start_with?("layouts/")
|
|
52
|
+
next if partial_path.start_with?("/")
|
|
53
|
+
next if partial_path.include?("/")
|
|
54
|
+
|
|
55
|
+
next if allowed_prefix?(partial_path)
|
|
56
|
+
|
|
57
|
+
full_path = "#{current_directory}/#{partial_path}"
|
|
58
|
+
range = processed_source.to_source_range(
|
|
59
|
+
start_pos...(start_pos + partial_path.length)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
add_offense(
|
|
63
|
+
range,
|
|
64
|
+
"Use the full path for partials. Replace `render \"#{partial_path}\"` with `render \"#{full_path}\"`"
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Checks if the given path starts with any of the allowed prefixes.
|
|
70
|
+
#
|
|
71
|
+
# @param path [String] The partial path to check
|
|
72
|
+
# @return [Boolean] true if the path starts with an allowed prefix, false otherwise
|
|
73
|
+
def allowed_prefix?(path)
|
|
74
|
+
return false unless @config.allowed_prefixes
|
|
75
|
+
|
|
76
|
+
@config.allowed_prefixes.any? { |prefix| path.start_with?(prefix) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Generates the autocorrection lambda for the given offense.
|
|
80
|
+
#
|
|
81
|
+
# This method creates a lambda that will replace the partial path with its
|
|
82
|
+
# full path when the linter's autocorrect feature is used.
|
|
83
|
+
#
|
|
84
|
+
# @param processed_source [ERBLint::ProcessedSource] The processed source containing the offense
|
|
85
|
+
# @param offense [ERBLint::Offense] The offense to generate a correction for
|
|
86
|
+
# @return [Proc, nil] A lambda that performs the correction, or nil if correction is not possible
|
|
87
|
+
def autocorrect(processed_source, offense)
|
|
88
|
+
return unless processed_source.filename =~ %r{app/views/(.+?)/_?([^/]+)\.html\.erb$}
|
|
89
|
+
return if processed_source.filename.include?("/cells/")
|
|
90
|
+
|
|
91
|
+
current_directory = Regexp.last_match(1)
|
|
92
|
+
|
|
93
|
+
lambda do |corrector|
|
|
94
|
+
partial_path = offense.source_range.source
|
|
95
|
+
full_path = "#{current_directory}/#{partial_path}"
|
|
96
|
+
corrector.replace(offense.source_range, full_path)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
data/rubocop-decidim.yml
CHANGED
|
@@ -25,4 +25,6 @@ inherit_from:
|
|
|
25
25
|
- config/rubocop/graphql/disabled.yml
|
|
26
26
|
- config/rubocop/factory_bot/configuration.yml
|
|
27
27
|
- config/rubocop/factory_bot/disabled.yml
|
|
28
|
+
- config/rubocop/yard/configuration.yml
|
|
28
29
|
- config/rubocop/disabled.yml
|
|
30
|
+
- config/rubocop/decidim-linters/configuration.yml
|