eslint-plugin-code-style 1.9.7 → 1.10.1

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.10.1] - 2026-02-03
11
+
12
+ **Bug Fix: logical-expression-multiline rule improvements**
13
+
14
+ **Version Range:** v1.10.0 → v1.10.1
15
+
16
+ ### Fixed
17
+
18
+ - **`logical-expression-multiline`** - Add collapse to single line for simple expressions (≤3 operands)
19
+ - **`logical-expression-multiline`** - Skip collapsing when any operand is multiline (e.g., JSX elements)
20
+
21
+ **Full Changelog:** [v1.10.0...v1.10.1](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.0...v1.10.1)
22
+
23
+ ---
24
+
25
+ ## [1.10.0] - 2026-02-03
26
+
27
+ **New Rule: logical-expression-multiline + Enhanced no-hardcoded-strings**
28
+
29
+ **Version Range:** v1.9.1 → v1.10.0
30
+
31
+ ### Added
32
+
33
+ **New Rules (1)**
34
+
35
+ - **`logical-expression-multiline`** - Enforce multiline formatting for logical expressions with more than maxOperands (default: 3) 🔧
36
+ - Handles variable declarations: `const err = a || b || c || d || e;`
37
+ - Handles return statements, assignments, and other contexts
38
+ - Skips if/ternary conditions (handled by other rules)
39
+ - Auto-fixes to put each operand on its own line with operator at start
40
+
41
+ ### Enhanced
42
+
43
+ - **`no-hardcoded-strings`** - Major improvements:
44
+ - Remove single-word string length limitations (now detects all single-word hardcoded strings)
45
+ - Add validation strings: `empty`, `invalid`, `missing`, `optional`, `required`, `valid`
46
+ - Add auth state strings: `anonymous`, `authenticated`, `authed`, `authorized`, `denied`, `forbidden`, etc.
47
+ - Add more status strings: `done`, `finished`, `inprogress`, `queued`, `ready`, `running`, etc.
48
+ - Skip UI component patterns in JSX attributes: `variant="ghost"`, `size="md"`, etc.
49
+ - Skip Tailwind CSS class strings: `"px-5 py-3 w-full"`, `"hover:bg-primary"`, etc.
50
+ - Make technical patterns stricter to avoid false negatives
51
+
52
+ ### Fixed
53
+
54
+ - **`no-hardcoded-strings`** - Fix detection of strings inside exported components
55
+ - **`no-hardcoded-strings`** - Fix Tailwind detection being too broad (now requires actual Tailwind syntax)
56
+
57
+ ### Stats
58
+
59
+ - Total Rules: 72 (was 71)
60
+ - Auto-fixable: 65 rules 🔧 (was 64)
61
+ - Report-only: 7 rules
62
+
63
+ **Full Changelog:** [v1.9.1...v1.10.0](https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.1...v1.10.0)
64
+
65
+ ---
66
+
10
67
  ## [1.9.7] - 2026-02-03
11
68
 
12
69
  ### Fixed
@@ -1337,6 +1394,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1337
1394
 
1338
1395
  ---
1339
1396
 
1397
+ [1.10.1]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.10.0...v1.10.1
1398
+ [1.10.0]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.7...v1.10.0
1340
1399
  [1.9.7]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.6...v1.9.7
1341
1400
  [1.9.6]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.5...v1.9.6
1342
1401
  [1.9.5]: https://github.com/Mohamed-Elhawary/eslint-plugin-code-style/compare/v1.9.4...v1.9.5
package/index.d.ts CHANGED
@@ -50,6 +50,7 @@ export type RuleNames =
50
50
  | "code-style/jsx-simple-element-one-line"
51
51
  | "code-style/jsx-string-value-trim"
52
52
  | "code-style/jsx-ternary-format"
53
+ | "code-style/logical-expression-multiline"
53
54
  | "code-style/member-expression-bracket-spacing"
54
55
  | "code-style/module-index-exports"
55
56
  | "code-style/multiline-if-conditions"
@@ -143,6 +144,7 @@ interface PluginRules {
143
144
  "jsx-simple-element-one-line": Rule.RuleModule;
144
145
  "jsx-string-value-trim": Rule.RuleModule;
145
146
  "jsx-ternary-format": Rule.RuleModule;
147
+ "logical-expression-multiline": Rule.RuleModule;
146
148
  "member-expression-bracket-spacing": Rule.RuleModule;
147
149
  "module-index-exports": Rule.RuleModule;
148
150
  "multiline-if-conditions": Rule.RuleModule;
package/index.js CHANGED
@@ -5208,6 +5208,205 @@ const ternaryConditionMultiline = {
5208
5208
  },
5209
5209
  };
5210
5210
 
5211
+ /**
5212
+ * ───────────────────────────────────────────────────────────────
5213
+ * Rule: Logical Expression Multiline
5214
+ * ───────────────────────────────────────────────────────────────
5215
+ *
5216
+ * Description:
5217
+ * Enforce multiline formatting for logical expressions with more
5218
+ * than maxOperands. This applies to variable declarations, return
5219
+ * statements, assignment expressions, and other contexts.
5220
+ *
5221
+ * Options:
5222
+ * { maxOperands: 3 } - Maximum operands on single line (default: 3)
5223
+ *
5224
+ * ✓ Good:
5225
+ * const err = data.error
5226
+ * || data.message
5227
+ * || data.status
5228
+ * || data.fallback;
5229
+ *
5230
+ * ✗ Bad:
5231
+ * const err = data.error || data.message || data.status || data.fallback;
5232
+ */
5233
+ const logicalExpressionMultiline = {
5234
+ create(context) {
5235
+ const sourceCode = context.sourceCode || context.getSourceCode();
5236
+ const options = context.options[0] || {};
5237
+ const maxOperands = options.maxOperands !== undefined ? options.maxOperands : 3;
5238
+
5239
+ // Check if node is wrapped in parentheses
5240
+ const isParenthesizedHandler = (node) => {
5241
+ const tokenBefore = sourceCode.getTokenBefore(node);
5242
+ const tokenAfter = sourceCode.getTokenAfter(node);
5243
+
5244
+ if (!tokenBefore || !tokenAfter) return false;
5245
+
5246
+ return tokenBefore.value === "(" && tokenAfter.value === ")";
5247
+ };
5248
+
5249
+ // Collect all operands from a logical expression (flattening non-parenthesized ones)
5250
+ const collectOperandsHandler = (node) => {
5251
+ const operands = [];
5252
+
5253
+ const collectHelperHandler = (n) => {
5254
+ if (n.type === "LogicalExpression" && !isParenthesizedHandler(n)) {
5255
+ collectHelperHandler(n.left);
5256
+ collectHelperHandler(n.right);
5257
+ } else {
5258
+ operands.push(n);
5259
+ }
5260
+ };
5261
+
5262
+ collectHelperHandler(node);
5263
+
5264
+ return operands;
5265
+ };
5266
+
5267
+ // Get the operator between two operands
5268
+ const getOperatorHandler = (leftNode, rightNode) => {
5269
+ const tokens = sourceCode.getTokensBetween(leftNode, rightNode);
5270
+ const opToken = tokens.find((t) => t.value === "||" || t.value === "&&" || t.value === "??" || t.value === "|" || t.value === "&");
5271
+
5272
+ return opToken ? opToken.value : "||";
5273
+ };
5274
+
5275
+ // Check if the expression is already multiline
5276
+ const isMultilineHandler = (node) => node.loc.start.line !== node.loc.end.line;
5277
+
5278
+ // Check if we're inside an if condition or ternary test (handled by other rules)
5279
+ const isInIfOrTernaryHandler = (node) => {
5280
+ let current = node.parent;
5281
+
5282
+ while (current) {
5283
+ if (current.type === "IfStatement" && current.test === node) return true;
5284
+
5285
+ if (current.type === "ConditionalExpression" && current.test === node) return true;
5286
+
5287
+ // Stop if we hit a statement or declaration boundary
5288
+ if (current.type.includes("Statement") || current.type.includes("Declaration")) return false;
5289
+
5290
+ current = current.parent;
5291
+ }
5292
+
5293
+ return false;
5294
+ };
5295
+
5296
+ // Handle logical expression
5297
+ const checkLogicalExpressionHandler = (node) => {
5298
+ // Only process top-level logical expressions (not nested ones)
5299
+ if (node.parent.type === "LogicalExpression" && !isParenthesizedHandler(node)) return;
5300
+
5301
+ // Skip if this is in an if condition or ternary test (handled by other rules)
5302
+ if (isInIfOrTernaryHandler(node)) return;
5303
+
5304
+ // Collect all operands
5305
+ const operands = collectOperandsHandler(node);
5306
+
5307
+ // Case 1: Simple expression (≤maxOperands) that's multiline → collapse to single line
5308
+ if (operands.length <= maxOperands) {
5309
+ if (isMultilineHandler(node)) {
5310
+ // Skip if any operand is itself multiline (e.g., JSX elements, function calls)
5311
+ const hasMultilineOperand = operands.some((op) => op.loc.start.line !== op.loc.end.line);
5312
+
5313
+ if (hasMultilineOperand) return;
5314
+
5315
+ context.report({
5316
+ fix(fixer) {
5317
+ // Build single line: operand1 op operand2 op operand3
5318
+ const parts = [sourceCode.getText(operands[0])];
5319
+
5320
+ for (let i = 1; i < operands.length; i++) {
5321
+ const operator = getOperatorHandler(operands[i - 1], operands[i]);
5322
+ parts.push(` ${operator} ${sourceCode.getText(operands[i])}`);
5323
+ }
5324
+
5325
+ return fixer.replaceText(node, parts.join(""));
5326
+ },
5327
+ message: `Logical expression with ${operands.length} operands should be on a single line (max for multiline: ${maxOperands})`,
5328
+ node,
5329
+ });
5330
+ }
5331
+
5332
+ return;
5333
+ }
5334
+
5335
+ // Case 2: Complex expression (>maxOperands) → enforce multiline
5336
+ // Check if already properly multiline
5337
+ if (isMultilineHandler(node)) {
5338
+ // Check if each operand is on its own line
5339
+ let allOnOwnLines = true;
5340
+
5341
+ for (let i = 1; i < operands.length; i++) {
5342
+ if (operands[i].loc.start.line === operands[i - 1].loc.end.line) {
5343
+ allOnOwnLines = false;
5344
+ break;
5345
+ }
5346
+ }
5347
+
5348
+ if (allOnOwnLines) return;
5349
+ }
5350
+
5351
+ // Report and fix
5352
+ context.report({
5353
+ fix(fixer) {
5354
+ // Build the formatted expression
5355
+ const firstOperandText = sourceCode.getText(operands[0]);
5356
+
5357
+ // Get the line containing the first operand
5358
+ const firstToken = sourceCode.getFirstToken(operands[0]);
5359
+ const lineStart = sourceCode.text.lastIndexOf("\n", firstToken.range[0]) + 1;
5360
+ const lineContent = sourceCode.text.slice(lineStart, firstToken.range[0]);
5361
+
5362
+ // Extract only the whitespace indentation from the line
5363
+ const indentMatch = lineContent.match(/^(\s*)/);
5364
+ const baseIndent = indentMatch ? indentMatch[1] : "";
5365
+
5366
+ // Build each line: first operand, then operator + operand for each subsequent
5367
+ const lines = [firstOperandText];
5368
+
5369
+ for (let i = 1; i < operands.length; i++) {
5370
+ const operator = getOperatorHandler(operands[i - 1], operands[i]);
5371
+ const operandText = sourceCode.getText(operands[i]);
5372
+ lines.push(`${baseIndent} ${operator} ${operandText}`);
5373
+ }
5374
+
5375
+ // Get the full range of the expression
5376
+ const fullText = lines.join("\n");
5377
+
5378
+ return fixer.replaceText(node, fullText);
5379
+ },
5380
+ message: `Logical expression with ${operands.length} operands should be on multiple lines (max: ${maxOperands})`,
5381
+ node,
5382
+ });
5383
+ };
5384
+
5385
+ return {
5386
+ LogicalExpression: checkLogicalExpressionHandler,
5387
+ };
5388
+ },
5389
+ meta: {
5390
+ docs: { description: "Enforce single line for ≤maxOperands, multiline for >maxOperands logical expressions" },
5391
+ fixable: "code",
5392
+ schema: [
5393
+ {
5394
+ additionalProperties: false,
5395
+ properties: {
5396
+ maxOperands: {
5397
+ default: 3,
5398
+ description: "Maximum operands to keep on single line (default: 3)",
5399
+ minimum: 1,
5400
+ type: "integer",
5401
+ },
5402
+ },
5403
+ type: "object",
5404
+ },
5405
+ ],
5406
+ type: "layout",
5407
+ },
5408
+ };
5409
+
5211
5410
  /**
5212
5411
  * ───────────────────────────────────────────────────────────────
5213
5412
  * Rule: Empty Line After Block
@@ -19918,6 +20117,7 @@ export default {
19918
20117
  "empty-line-after-block": emptyLineAfterBlock,
19919
20118
  "if-else-spacing": ifElseSpacing,
19920
20119
  "if-statement-format": ifStatementFormat,
20120
+ "logical-expression-multiline": logicalExpressionMultiline,
19921
20121
  "multiline-if-conditions": multilineIfConditions,
19922
20122
  "no-empty-lines-in-switch-cases": noEmptyLinesInSwitchCases,
19923
20123
  "ternary-condition-multiline": ternaryConditionMultiline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-code-style",
3
- "version": "1.9.7",
3
+ "version": "1.10.1",
4
4
  "description": "A custom ESLint plugin for enforcing consistent code formatting and style rules in React/JSX projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",