rubocop-katalyst 3.1.0 → 3.3.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: 408d4a24b9d76524197c2d2773434353c0a90752578516322fbf78731d13839e
4
- data.tar.gz: cc011b88f7a8798268c95a79da2ed7c1a7fa0da56b323e11735ff9ca1af22cce
3
+ metadata.gz: 04b7a088100580edc042b6f9197cfbb64c8b2aa3c8d978f0f4387f5774893d1c
4
+ data.tar.gz: 9bf922ffa58eb94577825d45d1e517e250cba503b0149f4fdde6858928777491
5
5
  SHA512:
6
- metadata.gz: 44dd1cab9b0424474fbaf1829d9279d6ae43e6e9a375b6f15f66ada55c5deaa7df66b306c79a2bc6769754797d0bbbd41d6942614c1efe93806e6f96f949f56b
7
- data.tar.gz: 1cf92f1bf724c885a5bbaf4773c42d2bd9a33bcdd3f3f21edf6c0353953084c286e66976aa946bd5ef61f8753b8fc0eafab8ea9d5cf5873d28e7ebda52228a8c
6
+ metadata.gz: bd0f4a6fc15f050e509908df4c3663945e290aac33dc861e371a376ae26705a20c0406ba31d663aee0e8aaeeaa85a6880e94246cfaa6109059fef8c0876667f7
7
+ data.tar.gz: 896c9831d3abfe2459bf44982a50e5ae8b17f4f4b8183fbaf43ef0be790fd06e0dc479876c75a75ca4d475453f09b37a429bc9acd8d3c8474bb5f623a952dd4a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [3.3.0] - 2026-08-18
2
+
3
+ - Add Koi/TableAsset cop: admin index tables (index/archived views, row and
4
+ table partials) should not render images or attachments. Flags
5
+ `row.attachment` cells and `image_tag`/`image_pack_tag` helpers as a smell,
6
+ without autocorrection.
7
+
8
+ ## [3.2.0] - 2026-08-17
9
+
10
+ - Add Koi/DuplicatesAssociation identifying Katalyst::Content items that
11
+ have associations but don't use the library-provided `duplicates_association`
12
+ helper.
13
+
1
14
  ## [3.1.0] - 2026-08-14
2
15
 
3
16
  - Add Koi department for custom cops specific to katalyst-koi projects,
@@ -1,10 +1,36 @@
1
1
  # Custom cops for projects built on Koi (katalyst-koi).
2
- #
3
- # These cops target admin views, which are only linted when RuboCop runs
4
- # through erb_lint, so they take effect via `rake erb_lint`.
5
2
  Koi:
6
3
  Enabled: true
7
4
 
5
+ Koi/DuplicatesAssociation:
6
+ Description: "Owned detail associations on content items should declare duplicates_association."
7
+ Enabled: true
8
+ Safe: true
9
+ SafeAutoCorrect: false
10
+ VersionAdded: "3.2"
11
+ BaseClasses:
12
+ - Katalyst::Content::Item
13
+ Include:
14
+ - "**/app/models/**/*.rb"
15
+
16
+ # Targets admin index views, which are only linted when RuboCop runs
17
+ # through erb_lint, so it takes effect via `rake erb_lint`. `_table`
18
+ # partials are included because they render index-style tables of child
19
+ # records within a parent view.
20
+ Koi/TableAsset:
21
+ Description: "Index tables should not render images or attachments."
22
+ Enabled: true
23
+ Safe: true
24
+ SafeAutoCorrect: false
25
+ VersionAdded: "3.3"
26
+ Include:
27
+ - "**/app/views/admin/**/index.html.erb"
28
+ - "**/app/views/admin/**/archived.html.erb"
29
+ - "**/app/views/admin/**/*.html+row.erb"
30
+ - "**/app/views/admin/**/_table*.erb"
31
+
32
+ # Targets admin views, which are only linted when RuboCop runs through
33
+ # erb_lint, so it takes effect via `rake erb_lint`.
8
34
  Koi/TableLinkHeading:
9
35
  Description: "Tables should have a single record link, rendered as the row heading."
10
36
  Enabled: true
@@ -1,3 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "koi/duplicates_association"
4
+ require_relative "koi/table_asset"
3
5
  require_relative "koi/table_link_heading"
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Koi
6
+ # Content items are duplicated for copy-on-write editing, and owned
7
+ # detail records must be duplicated with them. katalyst-content
8
+ # provides `duplicates_association` for this; without it the duplicate
9
+ # silently loses the detail record when the item is next edited.
10
+ #
11
+ # An association is considered an owned detail when it declares
12
+ # `dependent: :destroy` and is autosaved, either explicitly with
13
+ # `autosave: true` or implicitly via `accepts_nested_attributes_for`.
14
+ #
15
+ # The cop only applies where `duplicates_association` is available:
16
+ # classes that extend one of the configured `BaseClasses`
17
+ # (`Katalyst::Content::Item` by default), include
18
+ # `Katalyst::Content::DuplicatesAssociations`, or already call
19
+ # `duplicates_association`.
20
+ #
21
+ # @example
22
+ # # bad
23
+ # class Embed < Katalyst::Content::Item
24
+ # has_one :embed_detail, autosave: true, dependent: :destroy
25
+ # end
26
+ #
27
+ # # good
28
+ # class Embed < Katalyst::Content::Item
29
+ # has_one :embed_detail, autosave: true, dependent: :destroy
30
+ # duplicates_association :embed_detail
31
+ # end
32
+ class DuplicatesAssociation < Base
33
+ extend AutoCorrector
34
+
35
+ MSG = "Owned associations are duplicated with the item; " \
36
+ "declare `duplicates_association :%<name>s`."
37
+
38
+ RESTRICT_ON_SEND = %i[has_one has_many].freeze
39
+
40
+ BASE_CLASSES_DEFAULT = ["Katalyst::Content::Item"].freeze
41
+
42
+ # @!method association(node)
43
+ def_node_matcher :association, <<~PATTERN
44
+ (send nil? {:has_one :has_many} (sym $_) $(hash ...))
45
+ PATTERN
46
+
47
+ # @!method duplicates_association_names(node)
48
+ def_node_search :duplicates_association_names, <<~PATTERN
49
+ (send nil? :duplicates_association (sym $_)+)
50
+ PATTERN
51
+
52
+ # @!method includes_concern?(node)
53
+ def_node_search :includes_concern?, <<~PATTERN
54
+ (send nil? :include (const _ :DuplicatesAssociations))
55
+ PATTERN
56
+
57
+ # @!method nested_attributes(node)
58
+ def_node_search :nested_attributes, <<~PATTERN
59
+ $(send nil? :accepts_nested_attributes_for (sym $_) ...)
60
+ PATTERN
61
+
62
+ def on_send(node)
63
+ association(node) do |name, options|
64
+ klass = node.each_ancestor(:class).first
65
+ return unless klass
66
+ return unless owned?(klass, name, options)
67
+ return unless duplication_available?(klass)
68
+ return if declared?(klass, name)
69
+
70
+ add_offense(node, message: format(MSG, name:)) do |corrector|
71
+ anchor = nested_attributes_node(klass, name) || node
72
+ corrector.insert_after(anchor, "\n#{' ' * anchor.loc.column}duplicates_association :#{name}")
73
+ end
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def owned?(klass, name, options)
80
+ option?(options, :dependent, :destroy) &&
81
+ (option?(options, :autosave, true) || nested_attributes_node(klass, name))
82
+ end
83
+
84
+ def option?(options, key, value)
85
+ options.pairs.any? do |pair|
86
+ pair.key.sym_type? && pair.key.value == key && option_value?(pair.value, value)
87
+ end
88
+ end
89
+
90
+ def option_value?(node, value)
91
+ case value
92
+ when true then node.true_type?
93
+ when Symbol then node.sym_type? && node.value == value
94
+ end
95
+ end
96
+
97
+ def duplication_available?(klass)
98
+ base_classes.include?(klass.parent_class&.source) ||
99
+ includes_concern?(klass) ||
100
+ duplicates_association_names(klass).any?
101
+ end
102
+
103
+ def base_classes
104
+ cop_config.fetch("BaseClasses", BASE_CLASSES_DEFAULT)
105
+ end
106
+
107
+ def declared?(klass, name)
108
+ duplicates_association_names(klass).any? { |names| names.include?(name) }
109
+ end
110
+
111
+ def nested_attributes_node(klass, name)
112
+ nested_attributes(klass).find { |_node, sym| sym == name }&.first
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Koi
6
+ # Koi admin index tables should not render images or attachments.
7
+ # Index views are for scanning and comparing records; rendering assets
8
+ # makes rows uneven, slows the page down (variant generation and blob
9
+ # queries per row), and rarely helps identify a record. Display text in
10
+ # the table and show the asset on the record's page instead.
11
+ #
12
+ # This cop flags `row.attachment` cells and `image_tag`/`image_pack_tag`
13
+ # helpers. It does not autocorrect; it identifies a smell for a human to
14
+ # resolve.
15
+ #
16
+ # This cop only takes effect through erb_lint: plain `rubocop` does not
17
+ # inspect `.erb` files, and the `Include` configuration restricts it to
18
+ # Koi admin index views, so `row.attachment` in show views is fine.
19
+ #
20
+ # @example
21
+ # # bad
22
+ # row.attachment :image, variant: :admin_thumb
23
+ #
24
+ # # bad
25
+ # row.cell(:image) { image_tag(record.image.variant(:thumb)) }
26
+ #
27
+ # # good
28
+ # row.text :name
29
+ class TableAsset < Base
30
+ # This cop never registers a correction, but it must advertise
31
+ # autocorrect support: erb_lint mobilizes RuboCop in autocorrect mode,
32
+ # where non-autocorrecting cops are skipped for any fragment that an
33
+ # autocorrecting cop has corrected (see RuboCop::Cop::Team).
34
+ extend AutoCorrector
35
+
36
+ MSG_ATTACHMENT = "Avoid attachments in index tables; display text in the table " \
37
+ "and show the attachment on the record's page instead."
38
+ MSG_IMAGE = "Avoid images in index tables; display text in the table " \
39
+ "and show the image on the record's page instead."
40
+
41
+ RESTRICT_ON_SEND = %i[attachment image_tag image_pack_tag].freeze
42
+
43
+ # @!method row_attachment?(node)
44
+ def_node_matcher :row_attachment?, <<~PATTERN
45
+ (send {(send nil? :row) (lvar :row)} :attachment ...)
46
+ PATTERN
47
+
48
+ # @!method image_helper?(node)
49
+ def_node_matcher :image_helper?, <<~PATTERN
50
+ (send nil? {:image_tag :image_pack_tag} ...)
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ if row_attachment?(node)
55
+ add_offense(node, message: MSG_ATTACHMENT)
56
+ elsif image_helper?(node)
57
+ add_offense(node, message: MSG_IMAGE)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-katalyst
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Katalyst Interactive
@@ -160,6 +160,8 @@ files:
160
160
  - config/rubocop-security.yml
161
161
  - config/rubocop-style.yml
162
162
  - lib/rubocop/cop/katalyst_cops.rb
163
+ - lib/rubocop/cop/koi/duplicates_association.rb
164
+ - lib/rubocop/cop/koi/table_asset.rb
163
165
  - lib/rubocop/cop/koi/table_link_heading.rb
164
166
  - lib/rubocop/katalyst.rb
165
167
  - lib/rubocop/katalyst/erb_lint_task.rb