rubocop-openproject 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/CHANGELOG.md +20 -1
- data/README.md +10 -7
- data/config/default.yml +10 -0
- data/lib/rubocop/cop/open_project/use_effective_type_for_configuration.rb +84 -0
- data/lib/rubocop/cop/open_project/use_render_mode_instead_of_primitives.rb +115 -0
- data/lib/rubocop/cop/open_project_cops.rb +2 -0
- data/lib/rubocop/open_project/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b6399b37d75f31159de26fc061439ab6107f3621da14770ab2e3f4a263e4748e
|
|
4
|
+
data.tar.gz: c0bc045863a214c19a10fe29e4b23edd6d60f3598158f665fdfb4fefba851946
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5089a09294d79954f16b4fe511fd6b3aabe57b9272ebf41fa4a376feb7a6fcf8c2872b072fd6e3c7eb8493484e0e5b3eb2dc88f2bbfb964fa5fb67f0cdd8e5d8
|
|
7
|
+
data.tar.gz: 46759bc28c398aeefb4fb18305dd9494056a5296a859bb7789d4c37548b785a935a254f0cfcc98442cccceeb70fc5a788113316b3c58707dc0172506ab6462e0
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
4.0.6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
# rubocop-openproject
|
|
2
|
+
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ad3ff98: Add `OpenProject/UseEffectiveTypeForConfiguration` cop to catch reading a type's
|
|
8
|
+
configuration off `#type` instead of `#effective_type`. A work package stores the root of its
|
|
9
|
+
type family while its project may resolve that family to a variant configured differently, so
|
|
10
|
+
reading an aspect (form configuration, workflows, custom fields, defaults, export templates)
|
|
11
|
+
off the stored type silently answers with the root's configuration. The failure is quiet — a
|
|
12
|
+
project running the root behaves correctly, so a spec written without a variant passes while
|
|
13
|
+
the feature is broken wherever a variant is resolved. Inherited core settings (`name`,
|
|
14
|
+
`color`, `is_milestone`, `is_in_roadmap`) are read from the root by design and are not flagged.
|
|
15
|
+
|
|
16
|
+
## 0.6.0
|
|
17
|
+
|
|
18
|
+
### Minor Changes
|
|
19
|
+
|
|
20
|
+
- 6e7b812: Add `OpenProject/UseRenderModeInsteadOfPrimitives` cop to flag `format_text` calls that pass the external-rendering primitive flags (`static_html: true`, `plain_text: true`, `only_path: false`) instead of the canonical `render_mode:` API or the `format_mail_html` / `format_mail_text` mailer view helpers.
|
|
2
21
|
|
|
3
22
|
## [0.5.0] - 2026-05-05
|
|
4
23
|
|
data/README.md
CHANGED
|
@@ -24,16 +24,19 @@ require:
|
|
|
24
24
|
|
|
25
25
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
26
26
|
|
|
27
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
27
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
28
|
+
|
|
29
|
+
When working on a change, add a changeset describing it: run `npx changeset`
|
|
30
|
+
(or `./node_modules/.bin/changeset` after `npm install`), pick the bump
|
|
31
|
+
(`patch` / `minor` / `major`), and write a short summary. This adds a file under
|
|
32
|
+
`.changeset/`; commit it with your PR. The release automation turns accumulated
|
|
33
|
+
changesets into the next version bump and `CHANGELOG.md` entry.
|
|
28
34
|
|
|
29
35
|
## Releasing
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
4. Run `rake` to check everything is fine and fix any issues
|
|
35
|
-
5. Commit your changes (`git commit -am 'Prepare release X.Y.Z'`)
|
|
36
|
-
6. Run `bundle exec rake release`
|
|
37
|
+
Releases are driven by [changesets](https://github.com/changesets/changesets) and
|
|
38
|
+
GitHub Actions, so no RubyGems credentials are needed on your machine. See
|
|
39
|
+
[.github/RELEASING.md](.github/RELEASING.md) for the flow and one-time setup.
|
|
37
40
|
|
|
38
41
|
## Contributing
|
|
39
42
|
|
data/config/default.yml
CHANGED
|
@@ -23,6 +23,16 @@ OpenProject/NoSleepInFeatureSpecs:
|
|
|
23
23
|
Enabled: true
|
|
24
24
|
VersionAdded: '0.1.0'
|
|
25
25
|
|
|
26
|
+
OpenProject/UseEffectiveTypeForConfiguration:
|
|
27
|
+
Description: 'Read a type''s configuration through `effective_type`, not `type`, so the project''s variant is honoured.'
|
|
28
|
+
Enabled: true
|
|
29
|
+
VersionAdded: '0.7.0'
|
|
30
|
+
|
|
31
|
+
OpenProject/UseRenderModeInsteadOfPrimitives:
|
|
32
|
+
Description: 'Use `render_mode:` instead of `static_html:`/`plain_text:`/`only_path:` on `format_text`.'
|
|
33
|
+
Enabled: true
|
|
34
|
+
VersionAdded: '0.6.0'
|
|
35
|
+
|
|
26
36
|
OpenProject/UseServiceResultFactoryMethods:
|
|
27
37
|
Description: 'Use ServiceResult factory methods instead of ServiceResult.new.'
|
|
28
38
|
Enabled: true
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module OpenProject
|
|
6
|
+
# Flags reading a type's configuration off `#type` rather than `#effective_type`.
|
|
7
|
+
#
|
|
8
|
+
# A work package stores the root of its type family, while the project it lives in may
|
|
9
|
+
# resolve that family to a variant configured differently — a different form
|
|
10
|
+
# configuration, workflow, set of custom fields, defaults or export templates. Reading a
|
|
11
|
+
# configuration aspect off the stored type therefore answers with the root's
|
|
12
|
+
# configuration and silently ignores the variant.
|
|
13
|
+
#
|
|
14
|
+
# The failure mode is quiet: a project running the root behaves correctly, so a spec
|
|
15
|
+
# written without a variant passes while the feature is broken for every project that
|
|
16
|
+
# resolves one. That is what this cop is here to catch.
|
|
17
|
+
#
|
|
18
|
+
# Only configuration aspects are flagged. `name`, `color`, `is_milestone` and
|
|
19
|
+
# `is_in_roadmap` are inherited from the root by design, so reading those off `#type` is
|
|
20
|
+
# correct and left alone.
|
|
21
|
+
#
|
|
22
|
+
# Deliberately narrow: it fires when the receiver is literally a `type` call, which is
|
|
23
|
+
# the shape the mistake takes in practice. Administration code holding an explicit type
|
|
24
|
+
# (`@type.attribute_groups`) is reading that member's own configuration on purpose and is
|
|
25
|
+
# not flagged, and neither is a type held in a local variable.
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# # bad
|
|
29
|
+
# work_package.type.attribute_groups
|
|
30
|
+
#
|
|
31
|
+
# # bad
|
|
32
|
+
# work_package.type.custom_fields
|
|
33
|
+
#
|
|
34
|
+
# # bad — inside WorkPackage itself
|
|
35
|
+
# type.statuses(include_default: true)
|
|
36
|
+
#
|
|
37
|
+
# # good
|
|
38
|
+
# work_package.effective_type.attribute_groups
|
|
39
|
+
#
|
|
40
|
+
# # good — when there is no work package to ask
|
|
41
|
+
# project.effective_type(type).attribute_groups
|
|
42
|
+
#
|
|
43
|
+
# # good — inherited from the root by design
|
|
44
|
+
# work_package.type.name
|
|
45
|
+
# work_package.type.is_milestone?
|
|
46
|
+
#
|
|
47
|
+
# # good — administration reads a specific member's own configuration
|
|
48
|
+
# @type.attribute_groups
|
|
49
|
+
class UseEffectiveTypeForConfiguration < Base
|
|
50
|
+
MSG = "Read configuration through `effective_type`, not `type` — the stored type is " \
|
|
51
|
+
"the family's root, so this ignores the variant the project resolves to."
|
|
52
|
+
|
|
53
|
+
# Configuration aspects, per Type::ConfigurationLink::ASPECTS and the readers
|
|
54
|
+
# Type::ConfigurationLinkable resolves through the link chain.
|
|
55
|
+
CONFIGURATION_METHODS = %i[
|
|
56
|
+
artefact_export_enabled?
|
|
57
|
+
artefact_export_mode
|
|
58
|
+
attribute_groups
|
|
59
|
+
custom_field_ids
|
|
60
|
+
custom_fields
|
|
61
|
+
description
|
|
62
|
+
export_templates_disabled
|
|
63
|
+
export_templates_order
|
|
64
|
+
patterns
|
|
65
|
+
project_custom_field_type_mappings
|
|
66
|
+
statuses
|
|
67
|
+
workflows
|
|
68
|
+
].freeze
|
|
69
|
+
|
|
70
|
+
RESTRICT_ON_SEND = CONFIGURATION_METHODS
|
|
71
|
+
|
|
72
|
+
def_node_matcher :type_call?, <<~PATTERN
|
|
73
|
+
(send _ :type)
|
|
74
|
+
PATTERN
|
|
75
|
+
|
|
76
|
+
def on_send(node)
|
|
77
|
+
return unless type_call?(node.receiver)
|
|
78
|
+
|
|
79
|
+
add_offense(node)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RuboCop
|
|
4
|
+
module Cop
|
|
5
|
+
module OpenProject
|
|
6
|
+
# Flags `format_text` calls that pass the external-rendering primitive
|
|
7
|
+
# flags (`static_html: true`, `plain_text: true`, `only_path: false`)
|
|
8
|
+
# instead of the canonical `render_mode:` API.
|
|
9
|
+
#
|
|
10
|
+
# `format_text` renders trusted Markdown for two distinct audiences:
|
|
11
|
+
# in-app HTML (the default) and external surfaces such as mailer
|
|
12
|
+
# bodies or feeds. The external surface needs absolute URLs and a
|
|
13
|
+
# subset of macros pre-resolved to static HTML / plain text, because
|
|
14
|
+
# the recipient has no JavaScript runtime to hydrate the rest. The
|
|
15
|
+
# primitive flags express that requirement as three loosely coupled
|
|
16
|
+
# toggles, which makes call sites easy to get partially right (and
|
|
17
|
+
# silently wrong — see openproject#74762, where a mailer view skipped
|
|
18
|
+
# `static_html: true` and rendered numeric work package IDs instead of
|
|
19
|
+
# their semantic identifiers).
|
|
20
|
+
#
|
|
21
|
+
# The canonical API is `render_mode: :external_html` /
|
|
22
|
+
# `render_mode: :external_text`, with the mailer view helpers
|
|
23
|
+
# `format_mail_html` and `format_mail_text` as the preferred form
|
|
24
|
+
# inside mailer templates. The cop fires on both `.rb` and `.erb`
|
|
25
|
+
# sources via OpenProject's erb_lint Rubocop bridge.
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# # bad
|
|
29
|
+
# format_text("hi", static_html: true, only_path: false)
|
|
30
|
+
#
|
|
31
|
+
# # bad
|
|
32
|
+
# format_text("hi", plain_text: true, only_path: false)
|
|
33
|
+
#
|
|
34
|
+
# # bad
|
|
35
|
+
# format_text("hi", only_path: false)
|
|
36
|
+
#
|
|
37
|
+
# # bad (two-arg form)
|
|
38
|
+
# format_text(@user, :name, only_path: false)
|
|
39
|
+
#
|
|
40
|
+
# # good (canonical API)
|
|
41
|
+
# format_text("hi", render_mode: :external_html)
|
|
42
|
+
#
|
|
43
|
+
# # good (mailer view helper)
|
|
44
|
+
# format_mail_html("hi")
|
|
45
|
+
#
|
|
46
|
+
# # good (in-app default; no external flags)
|
|
47
|
+
# format_text("hi", only_path: true)
|
|
48
|
+
#
|
|
49
|
+
# # good (render_mode present — primitive override is an explicit escape hatch)
|
|
50
|
+
# format_text("hi", render_mode: :external_html, only_path: false)
|
|
51
|
+
class UseRenderModeInsteadOfPrimitives < Base
|
|
52
|
+
MSG_HTML = "Use `render_mode: :external_html` (or `format_mail_html` in mailer views) " \
|
|
53
|
+
"instead of the primitive flags `static_html: true` / `only_path: false`. " \
|
|
54
|
+
"The `render_mode:` API documents intent and bundles the coupled toggles " \
|
|
55
|
+
"that silently produced numeric IDs in watcher notifications (openproject#74762)."
|
|
56
|
+
|
|
57
|
+
MSG_TEXT = "Use `render_mode: :external_text` (or `format_mail_text` in mailer views) " \
|
|
58
|
+
"instead of the primitive flags `plain_text: true` / `only_path: false`. " \
|
|
59
|
+
"The `render_mode:` API documents intent and bundles the coupled toggles " \
|
|
60
|
+
"that silently produced numeric IDs in watcher notifications (openproject#74762)."
|
|
61
|
+
|
|
62
|
+
MSG_ONLY_PATH = "Use `render_mode: :external_html` (or `format_mail_html` in mailer views) " \
|
|
63
|
+
"instead of passing `only_path: false` on its own. The bare flag is " \
|
|
64
|
+
"an under-specified external render that silently produced numeric IDs " \
|
|
65
|
+
"in watcher notifications (openproject#74762)."
|
|
66
|
+
|
|
67
|
+
RESTRICT_ON_SEND = %i[format_text].freeze
|
|
68
|
+
|
|
69
|
+
def on_send(node)
|
|
70
|
+
hash_arg = node.last_argument
|
|
71
|
+
return unless hash_arg&.hash_type?
|
|
72
|
+
|
|
73
|
+
pairs = symbol_keyed_pairs(hash_arg)
|
|
74
|
+
return if render_mode_present?(pairs)
|
|
75
|
+
|
|
76
|
+
static_html = pair_with_value(pairs, :static_html, true)
|
|
77
|
+
plain_text = pair_with_value(pairs, :plain_text, true)
|
|
78
|
+
only_path_false = pair_with_value(pairs, :only_path, false)
|
|
79
|
+
|
|
80
|
+
return unless static_html || plain_text || only_path_false
|
|
81
|
+
|
|
82
|
+
add_offense(node, message: message_for(static_html, plain_text, only_path_false))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def symbol_keyed_pairs(hash_arg)
|
|
88
|
+
hash_arg.pairs.select { |p| p.key.sym_type? }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_mode_present?(pairs)
|
|
92
|
+
pairs.any? { |p| p.key.value == :render_mode }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def pair_with_value(pairs, key, value)
|
|
96
|
+
pairs.find { |p| p.key.value == key && literal_equals?(p.value, value) }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def literal_equals?(node, value)
|
|
100
|
+
case value
|
|
101
|
+
when true then node.true_type?
|
|
102
|
+
when false then node.false_type?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def message_for(static_html, plain_text, only_path_false)
|
|
107
|
+
return MSG_HTML if static_html
|
|
108
|
+
return MSG_TEXT if plain_text
|
|
109
|
+
|
|
110
|
+
MSG_ONLY_PATH if only_path_false
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -5,3 +5,5 @@ require_relative "open_project/no_not_implemented_error"
|
|
|
5
5
|
require_relative "open_project/no_params_in_work_package_where_id"
|
|
6
6
|
require_relative "open_project/use_service_result_factory_methods"
|
|
7
7
|
require_relative "open_project/no_sleep_in_feature_specs"
|
|
8
|
+
require_relative "open_project/use_effective_type_for_configuration"
|
|
9
|
+
require_relative "open_project/use_render_mode_instead_of_primitives"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rubocop-openproject
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- OpenProject GmbH
|
|
@@ -32,6 +32,7 @@ extra_rdoc_files: []
|
|
|
32
32
|
files:
|
|
33
33
|
- ".rspec"
|
|
34
34
|
- ".rubocop.yml"
|
|
35
|
+
- ".ruby-version"
|
|
35
36
|
- CHANGELOG.md
|
|
36
37
|
- LICENSE.txt
|
|
37
38
|
- README.md
|
|
@@ -42,6 +43,8 @@ files:
|
|
|
42
43
|
- lib/rubocop/cop/open_project/no_not_implemented_error.rb
|
|
43
44
|
- lib/rubocop/cop/open_project/no_params_in_work_package_where_id.rb
|
|
44
45
|
- lib/rubocop/cop/open_project/no_sleep_in_feature_specs.rb
|
|
46
|
+
- lib/rubocop/cop/open_project/use_effective_type_for_configuration.rb
|
|
47
|
+
- lib/rubocop/cop/open_project/use_render_mode_instead_of_primitives.rb
|
|
45
48
|
- lib/rubocop/cop/open_project/use_service_result_factory_methods.rb
|
|
46
49
|
- lib/rubocop/cop/open_project_cops.rb
|
|
47
50
|
- lib/rubocop/open_project.rb
|
|
@@ -69,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
69
72
|
- !ruby/object:Gem::Version
|
|
70
73
|
version: '0'
|
|
71
74
|
requirements: []
|
|
72
|
-
rubygems_version:
|
|
75
|
+
rubygems_version: 4.0.16
|
|
73
76
|
specification_version: 4
|
|
74
77
|
summary: RuboCop cops for OpenProject
|
|
75
78
|
test_files: []
|