eslint-plugin-nextfriday 5.0.0 → 5.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # eslint-plugin-nextfriday
2
2
 
3
+ ## 5.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#141](https://github.com/next-friday/eslint-plugin-nextfriday/pull/141) [`9a9ba41`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/9a9ba415a7a188ec0f0180cefd807e562a489bc8) Thanks [@joetakara](https://github.com/joetakara)! - Expand `enforce-constant-case` to enforce SCREAMING_SNAKE_CASE on RegExp and bigint constants, and refine `new RegExp()` detection to skip dynamic arguments.
8
+ - RegExp literals (`/foo/`) and `new RegExp("foo", "g")` (all args must be string literals) are now flagged when named in camelCase at global scope.
9
+ - BigInt literals (`100n`, `-1n`) are now flagged consistently with number literals.
10
+ - `new RegExp(userInput)` and other non-literal RegExp constructors are intentionally ignored — only statically-constructed patterns are treated as magic values.
11
+
12
+ ## 5.0.1
13
+
14
+ ### Patch Changes
15
+
16
+ - [#138](https://github.com/next-friday/eslint-plugin-nextfriday/pull/138) [`28c600d`](https://github.com/next-friday/eslint-plugin-nextfriday/commit/28c600d4746de76f7587e2fb5eab97ec5bdc6658) Thanks [@joetakara](https://github.com/joetakara)! - fix `no-inline-nested-object` scope — the rule now applies only to function-like contexts (call arguments, `new` expressions, `return` statements, arrow implicit returns, and JSX expressions) instead of every object property; module-level data declarations such as route tables, dependency rule arrays, and configuration objects are no longer flagged, eliminating false positives in patterns where Prettier already controls the layout
17
+
3
18
  ## 5.0.0
4
19
 
5
20
  ### Major Changes
@@ -1,23 +1,25 @@
1
1
  # enforce-constant-case
2
2
 
3
- Enforce SCREAMING_SNAKE_CASE for global magic-number and magic-text constants.
3
+ Enforce SCREAMING_SNAKE_CASE for global magic-number, magic-text, bigint, and RegExp constants.
4
4
 
5
5
  ## Rule Details
6
6
 
7
- This rule ensures that global-scope `const` declarations bound to a **magic number** or **magic text** literal use SCREAMING_SNAKE_CASE. The rule scope is intentionally narrow:
7
+ This rule ensures that global-scope `const` declarations bound to a **magic literal** value use SCREAMING_SNAKE_CASE. The rule scope is intentionally narrow:
8
8
 
9
9
  - A magic text constant is a string literal: `const API_URL = "https://api.example.com"`
10
10
  - A magic number constant is a number literal (including a unary `-`/`+` over a numeric literal): `const PAGE_LIMIT = 10`, `const OFFSET = -1`
11
+ - A bigint constant is a bigint literal (including a unary `-`/`+` over a bigint literal): `const BIG_LIMIT = 9007199254740993n`, `const NEGATIVE_BIG = -1n`
12
+ - A RegExp constant is a regex literal, or a `new RegExp(...)` call whose arguments are **all** string literals: `const PHONE_REGEX = /^[0-9]+$/`, `const EMAIL_PATTERN = new RegExp("^.+@.+$", "g")`
11
13
 
12
- Anything else is **not** checked: booleans, RegExp, template literals (static or dynamic), arrays, objects, `as const` assertions, function calls, identifiers, member expressions, JSX. Use whatever name fits the value (`metadata`, `viewport`, `statusMap`, `phoneRegex`, `isEnabled`, etc.) — the rule will not flag it.
14
+ Anything else is **not** checked: booleans, template literals (static or dynamic), arrays, objects, `as const` assertions, function calls, identifiers, member expressions, JSX, and `new RegExp(dynamicValue)` (where the pattern argument is non-literal). Use whatever name fits the value (`metadata`, `viewport`, `statusMap`, `isEnabled`, `dynamicPattern`, etc.) — the rule will not flag it.
13
15
 
14
16
  Only global scope (top-level of a file) is checked. Local scope constants inside functions are not checked by this rule.
15
17
 
16
18
  **Config files are exempt.** Files matching `*.config.{ts,mjs,cjs,js}`, `*.rc.*`, `*.setup.*`, `*.spec.*`, `*.test.*`, `.eslintrc*`, `.babelrc*`, and `.prettierrc*` skip this rule entirely. This avoids conflicts with framework conventions that require specific identifier names — e.g. Next.js expects `nextConfig` (not `NEXT_CONFIG`) in `next.config.ts`, Vite expects `config`, Tailwind expects `config`, etc.
17
19
 
18
- ### Why magic numbers and magic text only?
20
+ ### Why only static literal values?
19
21
 
20
- Reserved framework export names commonly bind to objects (Next.js App Router exports `metadata`, `viewport`, `generateStaticParams`, `dynamic`, `revalidate`, `runtime`, `fetchCache`, `dynamicParams`, `preferredRegion`, `maxDuration`; React Server Components and others have similar patterns). Forcing SCREAMING_SNAKE_CASE on any static-shaped initializer would rename those exports and break framework integration. Restricting the rule to bare number and string literals keeps the convention where it adds value (avoiding magic constants scattered through code) without colliding with framework-owned names.
22
+ Reserved framework export names commonly bind to objects (Next.js App Router exports `metadata`, `viewport`, `generateStaticParams`, `dynamic`, `revalidate`, `runtime`, `fetchCache`, `dynamicParams`, `preferredRegion`, `maxDuration`; React Server Components and others have similar patterns). Forcing SCREAMING_SNAKE_CASE on any static-shaped initializer would rename those exports and break framework integration. Restricting the rule to bare number, string, bigint, and statically-constructed RegExp values keeps the convention where it adds value (avoiding magic constants scattered through code) without colliding with framework-owned names or flagging dynamically-constructed patterns.
21
23
 
22
24
  ## Examples
23
25
 
@@ -29,6 +31,9 @@ const pageLimit = 10;
29
31
  const apiBaseUrl = "https://api.example.com";
30
32
  const negativeOne = -1;
31
33
  const default_theme = "dark";
34
+ const phoneRegex = /^[0-9]{10}$/;
35
+ const emailPattern = new RegExp("^.+@.+$");
36
+ const bigLimit = 9007199254740993n;
32
37
  ```
33
38
 
34
39
  ### Correct
@@ -39,12 +44,15 @@ const PAGE_LIMIT = 10;
39
44
  const API_BASE_URL = "https://api.example.com";
40
45
  const NEGATIVE_ONE = -1;
41
46
  const DEFAULT_THEME = "dark";
47
+ const PHONE_REGEX = /^[0-9]{10}$/;
48
+ const EMAIL_PATTERN = new RegExp("^.+@.+$", "g");
49
+ const BIG_LIMIT = 9007199254740993n;
42
50
 
43
51
  const isProduction = true;
44
52
  const hasAccess = false;
45
53
  const featureEnabled = true;
46
54
 
47
- const phoneRegex = /^[0-9]{10}$/;
55
+ const dynamicPattern = new RegExp(userInput);
48
56
  const template = `hello world`;
49
57
  const skeletonItems = [1, 2, 3, 4, 5];
50
58
  const mapStyle = { height: "320px", width: "100%" };
@@ -1,96 +1,96 @@
1
1
  # no-inline-nested-object
2
2
 
3
- Require object or array values that contain further nested objects or arrays to span multiple lines.
3
+ Require object or array values passed to functions, returned from functions, or used as JSX attributes to span multiple lines when they contain nested objects or arrays.
4
+
5
+ > This rule is auto-fixable using `--fix`.
4
6
 
5
7
  ## Rule Details
6
8
 
7
- This rule enforces that when an object property's value is itself an object or array **that contains further nested structures inside**, it must span multiple lines. Flat collections — objects of primitive properties or arrays of simple references — are allowed inline because Prettier already controls their wrapping via `printWidth`.
9
+ This rule applies **only to function-like contexts**:
10
+
11
+ - Arguments to function calls (`CallExpression`) and constructor calls (`NewExpression`)
12
+ - Values returned from functions (`return` statements)
13
+ - Implicit returns from arrow functions
14
+ - Expressions inside JSX braces (props, children)
15
+
16
+ When the value passed/returned in one of those contexts is an inline object or array that contains another object or array as a property value or element, the rule requires it to span multiple lines. Flat collections — objects of primitive properties or arrays of simple references — are allowed inline because Prettier already controls their wrapping via `printWidth`.
8
17
 
9
- A "nested structure" is any object or array element/property whose value is another object or array. The rule deliberately ignores depth-1 collections so it does not fight Prettier on simple data and configuration tables.
18
+ The rule deliberately **does not apply to module-level data declarations** (e.g., `const config = { ... }`, route tables, dependency rule arrays). These are static configuration data, not function calls — Prettier handles their layout, and forcing them multiline produces noisy diffs without aiding readability.
10
19
 
11
20
  ### Why?
12
21
 
13
- Truly nested structures are easy to misread when collapsed onto a single line, and adding or removing an inner element produces noisy diffs. Flat collections do not have the same problem and are best left to Prettier's line-length logic.
22
+ Truly nested structures passed at a call site are easy to misread when collapsed onto a single line, and adding or removing an inner element produces noisy diffs at the call site. The rule does not target data declarations because forcing static configuration arrays/objects onto multiple lines fights Prettier and bloats config files without aiding readability.
14
23
 
15
24
  ## Examples
16
25
 
17
26
  ### Incorrect
18
27
 
19
- <!-- prettier-ignore -->
20
28
  ```ts
21
- const obj = {
22
- items: [{ a: 1 }, { b: 2 }],
23
- };
29
+ useState({ a: { b: 1 } });
24
30
  ```
25
31
 
26
- <!-- prettier-ignore -->
27
32
  ```ts
28
- const obj = {
29
- matrix: [[1, 2], [3, 4]],
30
- };
33
+ doThing([{ a: 1 }, { b: 2 }]);
31
34
  ```
32
35
 
33
36
  ```ts
34
- const obj = {
35
- layer: { inner: { leaf: 1 } },
36
- };
37
+ function build() {
38
+ return { user: { id: 1 } };
39
+ }
37
40
  ```
38
41
 
39
42
  ```ts
40
- const obj = {
41
- wrap: { items: [1, 2, 3] },
42
- };
43
+ const factory = () => ({ a: { b: 1 } });
44
+ ```
45
+
46
+ ```tsx
47
+ const el = <Comp prop={{ a: { b: 1 } }} />;
43
48
  ```
44
49
 
45
50
  ### Correct
46
51
 
47
- <!-- prettier-ignore -->
52
+ Function-context calls expanded onto multiple lines:
53
+
48
54
  ```ts
49
- const obj = {
50
- items: [
51
- { a: 1 },
52
- { b: 2 },
53
- ],
54
- };
55
+ useState({
56
+ a: { b: 1 },
57
+ });
55
58
  ```
56
59
 
57
60
  ```ts
58
- const obj = {
59
- layer: {
60
- inner: { leaf: 1 },
61
- },
62
- };
61
+ doThing([{ a: 1 }, { b: 2 }]);
63
62
  ```
64
63
 
65
- Flat values stay inline:
66
-
67
64
  ```ts
68
- const obj = {
69
- config: { enabled: true, timeout: 5000 },
70
- database: { host: "localhost", port: 5432, name: "myapp" },
71
- };
65
+ function build() {
66
+ return {
67
+ user: { id: 1 },
68
+ };
69
+ }
72
70
  ```
73
71
 
72
+ Flat values stay inline:
73
+
74
74
  ```ts
75
- const obj = {
76
- options: ["primary", "foreground", "danger", "outline", "ghost", "link"],
77
- allow: [target.utils, target.types, target.constants],
78
- };
75
+ useState({ enabled: true, timeout: 5000 });
76
+ useState([1, 2, 3]);
79
77
  ```
80
78
 
81
- Empty nested objects and arrays are allowed inline:
79
+ Module-level data declarations are not checked:
82
80
 
81
+ <!-- prettier-ignore -->
83
82
  ```ts
84
- const initialState = {
85
- data: {},
86
- errors: [],
83
+ const dependencyRules = [
84
+ { from: source.modules, allow: [{ to: { type: "templates" } }] },
85
+ ];
86
+
87
+ const obj = {
88
+ items: [{ a: 1 }, { b: 2 }],
89
+ matrix: [[1, 2], [3, 4]],
90
+ layer: { inner: { leaf: 1 } },
87
91
  };
88
92
  ```
89
93
 
90
94
  ## When Not To Use It
91
95
 
92
- If your team prefers compact single-line nested structures regardless of depth, or if your project has different formatting preferences.
93
-
94
- ## Fixable
95
-
96
- This rule is auto-fixable. Running ESLint with the `--fix` flag will expand inline collections that contain nested structures onto multiple lines.
96
+ If your team prefers compact single-line nested structures at call sites regardless of nesting, or if your project has different formatting preferences.
package/lib/index.cjs CHANGED
@@ -32,7 +32,7 @@ let emoji_regex = require("emoji-regex");
32
32
  emoji_regex = __toESM(emoji_regex, 1);
33
33
  //#region package.json
34
34
  var name = "eslint-plugin-nextfriday";
35
- var version = "5.0.0";
35
+ var version = "5.0.2";
36
36
  //#endregion
37
37
  //#region src/rules/boolean-naming-prefix.ts
38
38
  const createRule$26 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`);
@@ -162,10 +162,15 @@ const SCREAMING_SNAKE_CASE_REGEX$1 = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/;
162
162
  const SNAKE_CASE_REGEX = /^[a-z]+_[a-z0-9_]*$/;
163
163
  const toScreamingSnakeCase = (str) => str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toUpperCase();
164
164
  const isMagicLiteral = (init) => {
165
- if (init.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal) return typeof init.value === "string" || typeof init.value === "number";
165
+ if (init.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal) return typeof init.value === "string" || typeof init.value === "number" || typeof init.value === "bigint" || "regex" in init;
166
166
  if (init.type === _typescript_eslint_utils.AST_NODE_TYPES.UnaryExpression) {
167
167
  const { argument, operator } = init;
168
- return (operator === "-" || operator === "+") && argument.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal && typeof argument.value === "number";
168
+ if (operator !== "-" && operator !== "+") return false;
169
+ return argument.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal && (typeof argument.value === "number" || typeof argument.value === "bigint");
170
+ }
171
+ if (init.type === _typescript_eslint_utils.AST_NODE_TYPES.NewExpression) {
172
+ if (init.callee.type !== _typescript_eslint_utils.AST_NODE_TYPES.Identifier || init.callee.name !== "RegExp") return false;
173
+ return init.arguments.every((arg) => arg.type === _typescript_eslint_utils.AST_NODE_TYPES.Literal && typeof arg.value === "string");
169
174
  }
170
175
  return false;
171
176
  };
@@ -179,7 +184,7 @@ const enforceConstantCase = createRule$25({
179
184
  name: "enforce-constant-case",
180
185
  meta: {
181
186
  type: "suggestion",
182
- docs: { description: "Enforce SCREAMING_SNAKE_CASE for global magic-number and magic-text constants" },
187
+ docs: { description: "Enforce SCREAMING_SNAKE_CASE for global magic-number, magic-text, bigint, and RegExp constants" },
183
188
  messages: {
184
189
  useScreamingSnakeCase: "Constant '{{ name }}' should use SCREAMING_SNAKE_CASE. Rename to '{{ suggestion }}'.",
185
190
  noSnakeCase: "Global constant '{{ name }}' should not use snake_case. Rename to '{{ suggestion }}'."
@@ -1778,9 +1783,6 @@ const noHelperFunctionInTest = _typescript_eslint_utils.ESLintUtils.RuleCreator(
1778
1783
  //#endregion
1779
1784
  //#region src/rules/no-inline-nested-object.ts
1780
1785
  const createRule$11 = _typescript_eslint_utils.ESLintUtils.RuleCreator((name) => `https://github.com/next-friday/eslint-plugin-nextfriday/blob/main/docs/rules/${name.replaceAll("-", "_").toUpperCase()}.md`);
1781
- function isObjectOrArray(node) {
1782
- return node.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression || node.type === _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression || node.type === _typescript_eslint_utils.AST_NODE_TYPES.TSAsExpression;
1783
- }
1784
1786
  function getInnerExpression(node) {
1785
1787
  if (node.type === _typescript_eslint_utils.AST_NODE_TYPES.TSAsExpression) return getInnerExpression(node.expression);
1786
1788
  return node;
@@ -1803,7 +1805,7 @@ const noInlineNestedObject = createRule$11({
1803
1805
  name: "no-inline-nested-object",
1804
1806
  meta: {
1805
1807
  type: "layout",
1806
- docs: { description: "Require object or array values that contain further nested objects or arrays to span multiple lines" },
1808
+ docs: { description: "Require object or array values passed to functions, returned, or used as JSX attributes to span multiple lines when they contain nested objects or arrays" },
1807
1809
  fixable: "whitespace",
1808
1810
  messages: { requireMultiline: "Inline collections containing nested objects or arrays should span multiple lines" },
1809
1811
  schema: []
@@ -1811,32 +1813,54 @@ const noInlineNestedObject = createRule$11({
1811
1813
  defaultOptions: [],
1812
1814
  create(context) {
1813
1815
  const { sourceCode } = context;
1814
- return { Property(node) {
1815
- if (!node.value || !isObjectOrArray(node.value)) return;
1816
- const valueNode = getInnerExpression(node.value);
1817
- if (valueNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression && valueNode.type !== _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) return;
1818
- if (!valueNode.loc) return;
1819
- if (valueNode.loc.start.line !== valueNode.loc.end.line) return;
1820
- if (!containsNestedStructure(valueNode)) return;
1821
- const elements = valueNode.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression ? valueNode.properties : valueNode.elements;
1816
+ function checkValue(node) {
1817
+ if (!node) return;
1818
+ const inner = getInnerExpression(node);
1819
+ if (inner.type !== _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression && inner.type !== _typescript_eslint_utils.AST_NODE_TYPES.ArrayExpression) return;
1820
+ if (!inner.loc) return;
1821
+ if (inner.loc.start.line !== inner.loc.end.line) return;
1822
+ if (!containsNestedStructure(inner)) return;
1823
+ const elements = inner.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression ? inner.properties : inner.elements;
1822
1824
  context.report({
1823
- node: valueNode,
1825
+ node: inner,
1824
1826
  messageId: "requireMultiline",
1825
1827
  fix(fixer) {
1826
- const openBrace = sourceCode.getFirstToken(valueNode);
1827
- const closeBrace = sourceCode.getLastToken(valueNode);
1828
- if (!openBrace || !closeBrace) return null;
1829
- const indent = " ".repeat(node.loc?.start.column ?? 0);
1830
- const innerIndent = `${indent} `;
1828
+ const lineIndentMatch = (sourceCode.lines[inner.loc.start.line - 1] ?? "").match(/^(\s*)/);
1829
+ const lineIndent = lineIndentMatch ? lineIndentMatch[1] : "";
1830
+ const innerIndent = `${lineIndent} `;
1831
1831
  const elementTexts = elements.filter((el) => el !== null).map((el) => sourceCode.getText(el));
1832
- const isObject = valueNode.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression;
1832
+ const isObject = inner.type === _typescript_eslint_utils.AST_NODE_TYPES.ObjectExpression;
1833
1833
  const openChar = isObject ? "{" : "[";
1834
1834
  const closeChar = isObject ? "}" : "]";
1835
- const newContent = `${openChar}\n${elementTexts.map((text) => `${innerIndent}${text},`).join("\n")}\n${indent}${closeChar}`;
1836
- return fixer.replaceText(valueNode, newContent);
1835
+ const newContent = `${openChar}\n${elementTexts.map((text) => `${innerIndent}${text},`).join("\n")}\n${lineIndent}${closeChar}`;
1836
+ return fixer.replaceText(inner, newContent);
1837
1837
  }
1838
1838
  });
1839
- } };
1839
+ }
1840
+ function checkArguments(args) {
1841
+ args.forEach((arg) => {
1842
+ if (arg.type === _typescript_eslint_utils.AST_NODE_TYPES.SpreadElement) return;
1843
+ checkValue(arg);
1844
+ });
1845
+ }
1846
+ return {
1847
+ CallExpression(node) {
1848
+ checkArguments(node.arguments);
1849
+ },
1850
+ NewExpression(node) {
1851
+ checkArguments(node.arguments);
1852
+ },
1853
+ ReturnStatement(node) {
1854
+ checkValue(node.argument);
1855
+ },
1856
+ ArrowFunctionExpression(node) {
1857
+ if (node.body.type !== _typescript_eslint_utils.AST_NODE_TYPES.BlockStatement) checkValue(node.body);
1858
+ },
1859
+ JSXExpressionContainer(node) {
1860
+ if (node.expression.type === _typescript_eslint_utils.AST_NODE_TYPES.JSXEmptyExpression) return;
1861
+ checkValue(node.expression);
1862
+ }
1863
+ };
1840
1864
  }
1841
1865
  });
1842
1866
  //#endregion