rubocop-openproject 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 275bc3f46bfa1dd8ecc461306e5671cadbce64a83a571044d8fff2d08af163a0
4
- data.tar.gz: 6c492b822185a201a389c3fd035fed0f02b60a44a87497090bf34d57c422af52
3
+ metadata.gz: 821ee2f0425e32049c380dd582471e37feb47c8565b1a8b05e2746809e498993
4
+ data.tar.gz: b8c1b464b25dbb3f0c07dcd5d7f20fd8bd331b91565f31782c7d27920a6148be
5
5
  SHA512:
6
- metadata.gz: 65aacad0b8c4b61e690253a4b86169bdac9b598df89da79f98995e113fb14cad688395495863382a023c002c1ef4db101eb1d0ef162be0571c2b078d38443c8f
7
- data.tar.gz: 0ceddce3d195d754dbdf194de88a68a284e86c099ee6f4d9d2069caef736bfb7b9a1ea9d4d9f1ac17e502321814c87115c197bed15bc47486a17042202b7ede9
6
+ metadata.gz: 20ea7aec95cb84472a6e145dfc71e31a542a3cda8e6a126ea5f6adf8832c6852aff30d506c624bb930fca6ef4a2fb324ecf777b88a2afb3d7d9e2e227c7a436e
7
+ data.tar.gz: 240bcf2a89496e23e73ca909171b754eb92e417bb86d67c07cf3dc2415ca1404879d59fabff2c829bec58d40484b8c49e833a719749c95a2b1b1828b5b0de294
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.2
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ## [Unreleased]
1
+ # rubocop-openproject
2
+
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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
8
 
3
9
  ## [0.5.0] - 2026-05-05
4
10
 
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`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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
- 1. Update the version in `lib/rubocop/open_project/version.rb`
32
- 2. Update the changelog in `CHANGELOG.md`
33
- 3. Run `bundle install` to update `Gemfile.lock`
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,11 @@ OpenProject/NoSleepInFeatureSpecs:
23
23
  Enabled: true
24
24
  VersionAdded: '0.1.0'
25
25
 
26
+ OpenProject/UseRenderModeInsteadOfPrimitives:
27
+ Description: 'Use `render_mode:` instead of `static_html:`/`plain_text:`/`only_path:` on `format_text`.'
28
+ Enabled: true
29
+ VersionAdded: '0.6.0'
30
+
26
31
  OpenProject/UseServiceResultFactoryMethods:
27
32
  Description: 'Use ServiceResult factory methods instead of ServiceResult.new.'
28
33
  Enabled: true
@@ -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,4 @@ 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_render_mode_instead_of_primitives"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module OpenProject
5
- VERSION = "0.5.0"
5
+ VERSION = "0.6.0"
6
6
  end
7
7
  end
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.5.0
4
+ version: 0.6.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,7 @@ 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_render_mode_instead_of_primitives.rb
45
47
  - lib/rubocop/cop/open_project/use_service_result_factory_methods.rb
46
48
  - lib/rubocop/cop/open_project_cops.rb
47
49
  - lib/rubocop/open_project.rb
@@ -69,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
71
  - !ruby/object:Gem::Version
70
72
  version: '0'
71
73
  requirements: []
72
- rubygems_version: 3.6.9
74
+ rubygems_version: 4.0.6
73
75
  specification_version: 4
74
76
  summary: RuboCop cops for OpenProject
75
77
  test_files: []