immosquare-cleaner 1.0.0 → 1.0.2

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: fe48e81e3af7200a3967f640a3f3a34f7632a32d71323b2829841516ad5f8cb8
4
- data.tar.gz: 571422d60f872ce01a3e3896260b4fee7d0e2282f16978fe7cfd18e2530a1a23
3
+ metadata.gz: 7165a24bce4d5074d6c51081764a2689116e996bafa14ed85ddc1fc43ea50e29
4
+ data.tar.gz: a8b504859e4cfe9f9f1ecf552a058fad07a773b54639630601feee28c5038aeb
5
5
  SHA512:
6
- metadata.gz: 9f9ec67de3055d93376abc0df67abcc4368e661a4f46a0bd702c9d679407407119364f3b4a68817e39b8ec8ae888ae3365eeb3e692b453317396e21abd0ab875
7
- data.tar.gz: e32bd136ee29441e48f17af89cac59741e11e931b7d335da8a8ccf8be3914deb4f4c558b75a99f85c108c66f18189ad23f3bca93cd7bfa354a25679f1af3a084
6
+ metadata.gz: b6dbd75516dc30b9cecc8c6400597ec6b75c99a70882c9d2bc18523b7dada2d670ecd20287c4de7cdd6e0ad2a2949c64254f6a37b2856ca2cfa434427008809b
7
+ data.tar.gz: 69c695dc0204e4ef7a7ebfeaec6d1fcad0f5ba8adb8622d33dfc983a31b1453c8c668d149f31dfc344a17083a12c052bffa608c8d4dbd39b3545a6fc3d631f81
@@ -66,6 +66,17 @@ module ImmosquareCleaner
66
66
 
67
67
  attr_reader :root
68
68
 
69
+ ##============================================================##
70
+ ## Everything under `generators/**/templates/` is raw template
71
+ ## content, usually ERB behind the target file's extension: any
72
+ ## formatter would mangle its tags. Shared with
73
+ ## ImmosquareCleaner.clean so a single-file clean skips it too
74
+ ##============================================================##
75
+ def self.generator_template?(folders)
76
+ generators = folders.index("generators")
77
+ !generators.nil? && folders.drop(generators + 1).include?("templates")
78
+ end
79
+
69
80
  def initialize(root)
70
81
  @root = File.expand_path(root)
71
82
  end
@@ -139,6 +150,7 @@ module ImmosquareCleaner
139
150
  segments = path.split("/")
140
151
  folders = directory ? segments : segments[0..-2]
141
152
  return true if folders.intersect?(EXCLUDED_DIRS) || "#{path}/".start_with?(*EXCLUDED_PATHS.map {|excluded| "#{excluded}/" })
153
+ return true if self.class.generator_template?(folders)
142
154
 
143
155
  !directory && (EXCLUDED_FILES.include?(segments.last) || segments.last.end_with?(*EXCLUDED_SUFFIXES))
144
156
  end
@@ -3,6 +3,7 @@ require "json"
3
3
  module ImmosquareCleaner
4
4
  module Processors
5
5
  class Json < Base
6
+
6
7
  def self.match?(file_path)
7
8
  file_path.end_with?(".json")
8
9
  end
@@ -14,6 +15,7 @@ module ImmosquareCleaner
14
15
  File.write(file_path, formatted)
15
16
  normalize_last_line
16
17
  end
18
+
17
19
  end
18
20
  end
19
21
  end
@@ -1,6 +1,7 @@
1
1
  module ImmosquareCleaner
2
2
  module Processors
3
3
  class Markdown < Base
4
+
4
5
  def self.match?(file_path)
5
6
  file_path.end_with?(".md", ".md.erb")
6
7
  end
@@ -10,6 +11,7 @@ module ImmosquareCleaner
10
11
  File.write(file_path, formatted_md)
11
12
  normalize_last_line
12
13
  end
14
+
13
15
  end
14
16
  end
15
17
  end
@@ -1,3 +1,3 @@
1
1
  module ImmosquareCleaner
2
- VERSION = "1.0.0".freeze
2
+ VERSION = "1.0.2".freeze
3
3
  end
@@ -70,10 +70,13 @@ module ImmosquareCleaner
70
70
  setup_linter_configs!
71
71
 
72
72
  ##============================================================##
73
- ## Return if the file is in the exclusion list
73
+ ## Return if the file is in the exclusion list or is a generator
74
+ ## template. With `-p`, file_path is a /tmp copy: the template
75
+ ## check runs on origin_path
74
76
  ##============================================================##
75
77
  exclude_files = configuration.exclude_files.map {|file| File.join(Dir.pwd, file) }
76
78
  return if exclude_files.include?(file_path)
79
+ return if DirectoryCleaner.generator_template?(File.dirname(origin_path || file_path).split("/"))
77
80
 
78
81
  begin
79
82
  processor = processor_for(file_path)
@@ -96,8 +99,6 @@ module ImmosquareCleaner
96
99
  File.expand_path("..", __dir__)
97
100
  end
98
101
 
99
- private
100
-
101
102
  ##============================================================##
102
103
  ## Processor registry — order matters.
103
104
  ##
@@ -125,6 +126,8 @@ module ImmosquareCleaner
125
126
  Processors::Toml
126
127
  ].freeze
127
128
 
129
+ private
130
+
128
131
  def processor_for(file_path)
129
132
  PROCESSORS.find {|p| p.match?(file_path) } || Processors::Prettier
130
133
  end
@@ -286,7 +286,9 @@ module ERBLint
286
286
  ##============================================================##
287
287
  def extract_erb_code(erb_node)
288
288
  code_node = erb_node.children.find {|c| c&.type == :code }
289
- code_node&.loc&.source&.strip
289
+ return nil unless code_node
290
+
291
+ code_node.loc&.source&.strip
290
292
  end
291
293
 
292
294
  ##============================================================##
@@ -74,7 +74,7 @@ module ERBLint
74
74
  context = {
75
75
  :new_code => "<%= #{new_code} %>",
76
76
  :output_node => output_node,
77
- :is_output_node => is_output_erb?(output_node)
77
+ :is_output_node => output_erb?(output_node)
78
78
  }
79
79
 
80
80
  add_offense(full_range, MSG, context)
@@ -102,9 +102,9 @@ module ERBLint
102
102
  ## output_node should be <%= (output)
103
103
  ## end_node should be <% (not <%=)
104
104
  ##============================================================##
105
- return false unless is_statement_erb?(if_node)
106
- return false unless is_output_erb?(output_node)
107
- return false unless is_statement_erb?(end_node)
105
+ return false unless statement_erb?(if_node)
106
+ return false unless output_erb?(output_node)
107
+ return false unless statement_erb?(end_node)
108
108
 
109
109
  ##============================================================##
110
110
  ## Extract and validate the code
@@ -133,7 +133,7 @@ module ERBLint
133
133
  ##============================================================##
134
134
  ## Check if ERB node is a statement (<% ... %>)
135
135
  ##============================================================##
136
- def is_statement_erb?(erb_node)
136
+ def statement_erb?(erb_node)
137
137
  indicator = erb_node.children.first
138
138
  indicator.nil? || (indicator.respond_to?(:children) && indicator.children.first.nil?)
139
139
  end
@@ -141,7 +141,7 @@ module ERBLint
141
141
  ##============================================================##
142
142
  ## Check if ERB node is an output (<%= ... %>)
143
143
  ##============================================================##
144
- def is_output_erb?(erb_node)
144
+ def output_erb?(erb_node)
145
145
  indicator = erb_node.children.first
146
146
  indicator.respond_to?(:children) && indicator.children.first == "="
147
147
  end
@@ -0,0 +1,188 @@
1
+ //============================================================//
2
+ // Local fork of eslint-plugin-align-assignments (v1.1.2, by
3
+ // Lucas Florio, MIT). Upstream is unmaintained since 2019.
4
+ //
5
+ // Differences with upstream:
6
+ // - reads `context.sourceCode` (ESLint 10 removed
7
+ // `context.getSourceCode()`);
8
+ // - knows the ES2021 logical assignment operators `||=`, `&&=`
9
+ // and `??=`. Without them the rule locates the `=` one column
10
+ // off, reports the group forever and its autofix never settles
11
+ // ("Circular fixes detected").
12
+ //============================================================//
13
+
14
+ //============================================================//
15
+ // MIT License
16
+ //
17
+ // Copyright (c) Lucas Florio <lucasefe@gmail.com>
18
+ //
19
+ // Permission is hereby granted, free of charge, to any person
20
+ // obtaining a copy of this software and associated
21
+ // documentation files (the "Software"), to deal in the
22
+ // Software without restriction, including without limitation
23
+ // the rights to use, copy, modify, merge, publish, distribute,
24
+ // sublicense, and/or sell copies of the Software, and to
25
+ // permit persons to whom the Software is furnished to do so,
26
+ // subject to the following conditions:
27
+ //
28
+ // The above copyright notice and this permission notice shall
29
+ // be included in all copies or substantial portions of the
30
+ // Software.
31
+ //
32
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
33
+ // KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
34
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
35
+ // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
36
+ // OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
37
+ // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
38
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
39
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40
+ //============================================================//
41
+ const hasRequire = /require\(/
42
+ const spaceMatcher = /(\s*)((?:\|\||&&|\?\?|\+|-|\*|\/|%|&|\^|\||<<|>>|\*\*|>>>)?=)/
43
+
44
+ const assignmentOperators = ["=", "+=", "-=", "*=", "/=", "%=", "&=", "^=", "|=", "<<=", ">>=", "**=", ">>>=", "||=", "&&=", "??="]
45
+
46
+ const getLast = (ary) => ary[ary.length - 1]
47
+
48
+ const isAssignmentExpression = (node) => node.type === "AssignmentExpression"
49
+
50
+ const isMultiline = (firstToken, assignmentToken) => firstToken.loc.start.line !== assignmentToken.loc.start.line
51
+
52
+ const rule = {
53
+ meta: {
54
+ fixable: "code",
55
+ schema: [
56
+ {
57
+ type: "object",
58
+ properties: { requiresOnly: { type: "boolean" } },
59
+ additionalProperties: false
60
+ }
61
+ ]
62
+ },
63
+
64
+ create: (context) => {
65
+ const { options } = context
66
+ const requiresOnly = options && options.length > 0 && options[0].requiresOnly
67
+ const { sourceCode } = context
68
+ const groups = []
69
+ let previousNode
70
+
71
+ const shouldStartNewGroup = (node) => {
72
+ //============================================================//
73
+ // First line of all
74
+ //============================================================//
75
+ if (!previousNode) return true
76
+
77
+ //============================================================//
78
+ // Switching parent nodes
79
+ //============================================================//
80
+ if (node.parent !== previousNode.parent) return true
81
+
82
+ //============================================================//
83
+ // If previous node was a for and included the declarations,
84
+ // new group
85
+ //============================================================//
86
+ if (previousNode.parent.type === "ForStatement" && previousNode.declarations) return true
87
+
88
+ //============================================================//
89
+ // Previous line was blank
90
+ //============================================================//
91
+ const lineOfNode = sourceCode.getFirstToken(node).loc.start.line
92
+ const lineOfPrev = sourceCode.getLastToken(previousNode).loc.start.line
93
+ return lineOfNode - lineOfPrev !== 1
94
+ }
95
+
96
+ const addNode = (groupNode, node) => {
97
+ if (shouldStartNewGroup(groupNode)) groups.push([node])
98
+ else getLast(groups).push(node)
99
+
100
+ previousNode = groupNode
101
+ }
102
+
103
+ const getPrefix = (node) => {
104
+ const nodeBefore = isAssignmentExpression(node) ? node.left : node.declarations.find((dcl) => dcl.type === "VariableDeclarator").id
105
+ return nodeBefore.loc.end.column - nodeBefore.loc.start.column
106
+ }
107
+
108
+ const findAssigment = (node) => {
109
+ const prefix = getPrefix(node)
110
+ const source = sourceCode.getText(node)
111
+ const match = source.slice(prefix).match(spaceMatcher)
112
+ return match ? match.index + prefix + match[2].length : null
113
+ }
114
+
115
+ const assignmentOnFirstLine = (node) => {
116
+ if (isAssignmentExpression(node)) return node.left.loc.start.line === node.right.loc.start.line
117
+
118
+ const source = sourceCode.getText(node)
119
+ const lines = source.split("\n")
120
+ return lines[0].includes("=")
121
+ }
122
+
123
+ const areAligned = (maxPos, nodes) => nodes
124
+ .filter(assignmentOnFirstLine)
125
+ .map((node) => sourceCode.getText(node))
126
+ .every((source) => source.charAt(maxPos) === "=")
127
+
128
+ const getMaxPos = (nodes) => nodes
129
+ .filter(assignmentOnFirstLine)
130
+ .map(findAssigment)
131
+ .reduce((last, current) => Math.max(last, current), [])
132
+
133
+ const check = (group) => {
134
+ const maxPos = getMaxPos(group)
135
+
136
+ if (areAligned(maxPos, group)) return
137
+
138
+ context.report({
139
+ loc: {
140
+ start: group[0].loc.start,
141
+ end: getLast(group).loc.end
142
+ },
143
+ message: "This group of assignments is not aligned",
144
+ fix: (fixer) => group.map((node) => {
145
+ const tokens = sourceCode.getTokens(node)
146
+ const firstToken = tokens[0]
147
+ const assignmentToken = tokens.find((token) => assignmentOperators.includes(token.value))
148
+ const line = sourceCode.getText(node)
149
+ const lineIsAligned = line.charAt(maxPos) === "="
150
+
151
+ if (lineIsAligned || !assignmentToken || isMultiline(firstToken, assignmentToken)) return fixer.replaceText(node, line)
152
+
153
+ //============================================================//
154
+ // Source line may include spaces, we need to accomodate
155
+ // for that.
156
+ //============================================================//
157
+ const spacePrefix = firstToken.loc.start.column
158
+ const startDelimiter = assignmentToken.loc.start.column - spacePrefix
159
+ const endDelimiter = assignmentToken.loc.end.column - spacePrefix
160
+ const start = line.slice(0, startDelimiter).replace(/\s+$/m, "")
161
+ const ending = line.slice(endDelimiter).replace(/^\s+/m, "")
162
+ const spacesRequired = maxPos - start.length - assignmentToken.value.length + 1
163
+ const spaces = " ".repeat(spacesRequired)
164
+ return fixer.replaceText(node, `${start}${spaces}${assignmentToken.value} ${ending}`)
165
+ })
166
+ })
167
+ }
168
+
169
+ return {
170
+ VariableDeclaration: (node) => {
171
+ const source = sourceCode.getText(node)
172
+ if (requiresOnly && !hasRequire.test(source)) return
173
+
174
+ addNode(node, node)
175
+ },
176
+
177
+ ExpressionStatement: (node) => {
178
+ if (node.expression.type !== "AssignmentExpression") return
179
+
180
+ addNode(node, node.expression)
181
+ },
182
+
183
+ "Program:exit": () => groups.forEach(check)
184
+ }
185
+ }
186
+ }
187
+
188
+ export default rule
@@ -1,27 +1,26 @@
1
1
  //============================================================//
2
2
  // ESLint 10 compatibility shim for unmaintained plugins.
3
3
  //
4
- // Context: we rely on two npm plugins to enforce our JS style
5
- // (see rules/javascript.md — alignment of assignments and
6
- // imports is mandatory):
7
- // - eslint-plugin-align-assignments (last release: 2019)
8
- // - eslint-plugin-align-import (last release: 2020)
4
+ // Context: alignment of imports is mandatory in our JS style
5
+ // (see rules/javascript.md) and relies on
6
+ // eslint-plugin-align-import (last release: 2020). Assignment
7
+ // alignment uses the local fork in ./align-assignments.mjs.
9
8
  //
10
- // Both plugins call `context.getSourceCode()` inside their
11
- // rule `create()`. ESLint 10 removed that method: the source
12
- // code is now exposed as the `context.sourceCode` property.
13
- // Loading either plugin as-is throws:
9
+ // The plugin calls `context.getSourceCode()` inside its rules'
10
+ // `create()`. ESLint 10 removed that method: the source code
11
+ // is now exposed as the `context.sourceCode` property.
12
+ // Loading it as-is throws:
14
13
  // TypeError: context.getSourceCode is not a function
15
14
  //
16
- // Since neither plugin is maintained and no drop-in
17
- // replacement exists (Prettier/Biome don't align `=`), we wrap
18
- // each rule with a Proxy that re-injects `getSourceCode()` on
19
- // the context before delegating to the original `create()`.
20
- // The upstream packages stay untouched — when a maintained
15
+ // Since it is not maintained and no drop-in replacement
16
+ // exists, we wrap each rule with a Proxy that re-injects
17
+ // `getSourceCode()` on the context before delegating to the
18
+ // original `create()`.
19
+ // The upstream package stays untouched — when a maintained
21
20
  // alternative surfaces, delete this file and import directly.
22
21
  //============================================================//
23
- import alignAssignmentsPlugin from "eslint-plugin-align-assignments"
24
- import alignImportPlugin from "eslint-plugin-align-import"
22
+ import alignAssignmentsRule from "./align-assignments.mjs"
23
+ import alignImportPlugin from "eslint-plugin-align-import"
25
24
 
26
25
 
27
26
  const withLegacyContext = (rule) => ({
@@ -35,7 +34,7 @@ const withLegacyContext = (rule) => ({
35
34
 
36
35
  export const alignAssignments = {
37
36
  rules: {
38
- "align-assignments": withLegacyContext(alignAssignmentsPlugin.rules["align-assignments"])
37
+ "align-assignments": alignAssignmentsRule
39
38
  }
40
39
  }
41
40
 
@@ -1,5 +1,5 @@
1
1
  import { readFileSync, writeFileSync } from "fs"
2
- import * as babelParser from "@babel/parser"
2
+ import * as babelParser from "@babel/parser"
3
3
 
4
4
  //============================================================//
5
5
  // Constants
@@ -36,7 +36,7 @@ const normalizeComments = (filePath) => {
36
36
  // Process blocks in reverse order to avoid line number shifts
37
37
  //============================================================//
38
38
  const blocksReversed = [...commentBlocks].reverse()
39
- let hasChanges = false
39
+ let hasChanges = false
40
40
 
41
41
  blocksReversed.forEach((block) => {
42
42
  const normalized = normalizeCommentBlock(block, lines)
@@ -111,7 +111,7 @@ const groupConsecutiveComments = (comments, originalLines) => {
111
111
  // Check if this is a standalone comment (line starts with //)
112
112
  // Skip end-of-line comments (e.g., const x = 1 // comment)
113
113
  //============================================================//
114
- const lineContent = originalLines[comment.loc.start.line - 1]
114
+ const lineContent = originalLines[comment.loc.start.line - 1]
115
115
  const isStandalone = lineContent.trim().startsWith("//")
116
116
 
117
117
  if (!isStandalone) {
@@ -104,16 +104,13 @@ module RuboCop
104
104
  in_code = !in_code
105
105
  body << text
106
106
  content_seen = true
107
- elsif in_code || text.match?(RAW_LINE)
107
+ elsif verbatim_line?(text, in_code)
108
108
  body << text
109
109
  content_seen = true
110
110
  elsif text == BLANK_LINE
111
111
  body << BLANK_LINE if content_seen
112
112
  elsif text.match?(USER_SEPARATOR)
113
113
  body << INSIDE_SEPARATOR if content_seen
114
- elsif text.match?(STRUCTURED_LINE) || text.match?(INDENTED_LINE)
115
- body << text
116
- content_seen = true
117
114
  else
118
115
  body << cleaned_line(text)
119
116
  content_seen = true
@@ -123,6 +120,20 @@ module RuboCop
123
120
  body
124
121
  end
125
122
 
123
+ ##============================================================##
124
+ ## Lines kept as written. Precedence matters: inside a code
125
+ ## block or on a raw line everything is verbatim, even "##" or
126
+ ## a separator; elsewhere a blank line and a user separator win
127
+ ## over the structured/indented patterns ("## ---" is a
128
+ ## separator, not an indented line).
129
+ ##============================================================##
130
+ def verbatim_line?(text, in_code)
131
+ return true if in_code || text.match?(RAW_LINE)
132
+ return false if text == BLANK_LINE || text.match?(USER_SEPARATOR)
133
+
134
+ text.match?(STRUCTURED_LINE) || text.match?(INDENTED_LINE)
135
+ end
136
+
126
137
  ##============================================================##
127
138
  ## Cleans up a standard text line:
128
139
  ## - "### foo" → "## foo" (collapse repeated #)
@@ -32,7 +32,7 @@ module RuboCop
32
32
 
33
33
  extend AutoCorrector
34
34
 
35
- MSG = "Collapse multi-line `%<name>s` call into a single line.".freeze
35
+ MSG = "Collapse multi-line `%{name}` call into a single line.".freeze
36
36
 
37
37
  DEFAULT_METHODS = ["link_to"].freeze
38
38
 
@@ -24,7 +24,7 @@ module RuboCop
24
24
 
25
25
  extend AutoCorrector
26
26
 
27
- MSG = "Move priority kwargs (%<keys>s) to the front of the call.".freeze
27
+ MSG = "Move priority kwargs (%{keys}) to the front of the call.".freeze
28
28
 
29
29
  DEFAULT_METHODS = ["link_to"].freeze
30
30
  DEFAULT_PRIORITY_KEYS = ["remote", "method"].freeze
@@ -39,14 +39,14 @@ module RuboCop
39
39
  ##============================================================##
40
40
  jbuilder_block_node = node.each_ancestor(:block).find do |ancestor_node|
41
41
  block_method_name = ancestor_node.children.first.method_name
42
- (block_method_name == :encode || block_method_name == :new) && ancestor_node.children.first.receiver&.const_name == "Jbuilder"
42
+ [:encode, :new].include?(block_method_name) && ancestor_node.children.first.receiver&.const_name == "Jbuilder"
43
43
  end
44
44
 
45
45
  ##============================================================##
46
46
  ## Source Name is the name of the method called. Here in our
47
47
  ## block it will be "json" but it can be any value put in the
48
48
  ## Jbuilder.encode do |name|
49
- ## ----
49
+ ## ---------
50
50
  ## In .json.jbuilder files, it will always be "json"
51
51
  ##============================================================##
52
52
  source_name = receiver.loc.expression.source
@@ -32,6 +32,14 @@ Naming/FileName:
32
32
  Enabled: false
33
33
  Naming/MethodParameterName:
34
34
  MinNameLength: 1
35
+ Naming/PredicateMethod:
36
+ AllowedMethods:
37
+ - call
38
+ - move_to_cache
39
+ - move_to_store
40
+ - save
41
+ - check_health
42
+ - elastic_import_with_progressbar
35
43
  Layout/LeadingEmptyLines:
36
44
  Enabled: false
37
45
  Layout/InitialIndentation:
@@ -88,6 +96,11 @@ Style/NegatedIf:
88
96
  Enabled: false
89
97
  Style/FormatStringToken:
90
98
  EnforcedStyle: template
99
+ Mode: conservative
100
+ Style/OptionalBooleanParameter:
101
+ AllowedMethods:
102
+ - respond_to_missing?
103
+ - perform
91
104
  Style/NumericPredicate:
92
105
  EnforcedStyle: comparison
93
106
  Style/StringLiterals:
@@ -217,3 +230,5 @@ CustomCops/Style/KwargPriorityOrder:
217
230
  - method
218
231
  Gemspec/RequireMFA:
219
232
  Enabled: false
233
+ Gemspec/RequiredRubyVersion:
234
+ Enabled: false
data/linters/rubocop.yml CHANGED
@@ -37,6 +37,14 @@ Naming/FileName:
37
37
  Enabled: false # Allow filenames with dashes (e.g. immosquare-cleaner.rb)
38
38
  Naming/MethodParameterName:
39
39
  MinNameLength: 1 # Allow single-character parameter names
40
+ Naming/PredicateMethod:
41
+ AllowedMethods:
42
+ - call
43
+ - move_to_cache # CarrierWave hooks: the gem calls them by these names and reads their boolean return.
44
+ - move_to_store
45
+ - save # ActiveModel contract: form objects expose `save` returning true/false, like ActiveRecord.
46
+ - check_health # Commands returning their success status (immosquare-railtie PumaManager, Elasticsearch import): a `?` name would hide the side effects.
47
+ - elastic_import_with_progressbar
40
48
 
41
49
  #################### Layout ###########################
42
50
  Layout/LeadingEmptyLines:
@@ -98,6 +106,11 @@ Style/NegatedIf:
98
106
  Enabled: false # Allow rewriting `if !` as `unless`
99
107
  Style/FormatStringToken:
100
108
  EnforcedStyle: "template" ## Use %{my_string} interpolation, not %<my_string>s
109
+ Mode: conservative # Only strings passed to format/sprintf/%. In aggressive mode any literal containing `%<x>s` is flagged, including prose that lists placeholders (e.g. an AI prompt telling the model which tokens to preserve), where rewriting the token changes the text.
110
+ Style/OptionalBooleanParameter:
111
+ AllowedMethods:
112
+ - respond_to_missing?
113
+ - perform # Sidekiq and ActiveJob serialize `perform` arguments as JSON: keyword arguments are not reliably supported, and jobs already enqueued keep their positional shape.
101
114
  Style/NumericPredicate:
102
115
  EnforcedStyle: comparison # No `.zero?` `.negative?` `.positive?`
103
116
  Style/StringLiterals:
@@ -247,3 +260,5 @@ CustomCops/Style/KwargPriorityOrder:
247
260
  #################### GEM ###########################
248
261
  Gemspec/RequireMFA:
249
262
  Enabled: false # Don't require MFA to push a gem
263
+ Gemspec/RequiredRubyVersion:
264
+ Enabled: false # TargetRubyVersion is always set to the Ruby running the cleaner, so it never matches the minimum a gemspec declares (`>= 3.2.6` vs 4.0.x): the cop would flag every gemspec.
data/package.json CHANGED
@@ -13,7 +13,6 @@
13
13
  "@typescript-eslint/parser": "^8.71.0",
14
14
  "@typescript/native": "npm:typescript@^7.0.2",
15
15
  "eslint": "^10.11.0",
16
- "eslint-plugin-align-assignments": "^1.1.2",
17
16
  "eslint-plugin-align-import": "^1.0.0",
18
17
  "eslint-plugin-erb": "^2.1.1",
19
18
  "eslint-plugin-prefer-arrow": "^1.2.3",
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immosquare-cleaner
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - immosquare
@@ -183,6 +183,7 @@ files:
183
183
  - linters/erb_lint/custom_align_consecutive_calls.rb
184
184
  - linters/erb_lint/custom_html_to_content_tag.rb
185
185
  - linters/erb_lint/custom_single_line_if_modifier.rb
186
+ - linters/eslint-plugins/align-assignments.mjs
186
187
  - linters/eslint-plugins/eslint10-compat.mjs
187
188
  - linters/eslint.config.mjs
188
189
  - linters/js-erb-lint-4.0.6.yml