eslint-plugin-th-rules 1.15.6 → 1.17.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.
package/.yarnrc.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  nodeLinker: node-modules
2
2
 
3
- yarnPath: .yarn/releases/yarn-4.6.0.cjs
3
+ yarnPath: .yarn/releases/yarn-4.12.0.cjs
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.17.0](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.16.0...v1.17.0) (2026-01-05)
2
+
3
+
4
+ ### Features
5
+
6
+ * add 'styles-in-styles-file' rule to enforce React-Native styles in dedicated files ([eca8af0](https://github.com/tomerh2001/eslint-plugin-th-rules/commit/eca8af0850aa197a35fb47cf08a9f04839a6a3cb))
7
+
8
+ # [1.16.0](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.6...v1.16.0) (2026-01-05)
9
+
10
+
11
+ ### Features
12
+
13
+ * add new rule 'types-in-dts' to enforce TypeScript type declarations in .d.ts files ([00c4769](https://github.com/tomerh2001/eslint-plugin-th-rules/commit/00c4769f9a025642f7c72023df1dea82dbf33e72))
14
+
1
15
  ## [1.15.6](https://github.com/tomerh2001/eslint-plugin-th-rules/compare/v1.15.5...v1.15.6) (2024-12-30)
2
16
 
3
17
 
package/README.md CHANGED
@@ -7,10 +7,6 @@
7
7
 
8
8
  This repository contains custom ESLint rules to enhance code quality and consistency across projects.
9
9
 
10
- # Custom ESLint Rules
11
-
12
- This repository contains custom ESLint rules to enhance code quality and consistency across projects, created by Tomer Horowitz.
13
-
14
10
  ## Rules
15
11
  <!-- begin auto-generated rules list -->
16
12
 
@@ -18,12 +14,14 @@ This repository contains custom ESLint rules to enhance code quality and consist
18
14
  ✅ Set in the `recommended` configuration.\
19
15
  🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).
20
16
 
21
- | Name                | Description | 💼 | 🔧 |
22
- | :------------------------------------------------------- | :------------------------------------------------------------------------------- | :--------------------------------------------------------------- | :- |
23
- | [no-comments](docs/rules/no-comments.md) | Disallow comments except for specified allowed patterns. | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | 🔧 |
24
- | [no-default-export](docs/rules/no-default-export.md) | Convert unnamed default exports to named default exports based on the file name. | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | 🔧 |
25
- | [no-destructuring](docs/rules/no-destructuring.md) | Disallow destructuring that does not meet certain conditions | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | |
26
- | [top-level-functions](docs/rules/top-level-functions.md) | Require all top-level functions to be named/regular functions. | | 🔧 |
17
+ | Name                  | Description | 💼 | 🔧 |
18
+ | :----------------------------------------------------------- | :---------------------------------------------------------------------------------------- | :--------------------------------------------------------------- | :- |
19
+ | [no-comments](docs/rules/no-comments.md) | Disallow comments except for specified allowed patterns. | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | 🔧 |
20
+ | [no-default-export](docs/rules/no-default-export.md) | Convert unnamed default exports to named default exports based on the file name. | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | 🔧 |
21
+ | [no-destructuring](docs/rules/no-destructuring.md) | Disallow destructuring that does not meet certain conditions | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | |
22
+ | [styles-in-styles-file](docs/rules/styles-in-styles-file.md) | Require React-Native StyleSheet.create(...) to be placed in a .styles.ts/.styles.tsx file | ![badge-recommended-react][] ![badge-recommended-typescript][] | |
23
+ | [top-level-functions](docs/rules/top-level-functions.md) | Require all top-level functions to be named/regular functions. | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | 🔧 |
24
+ | [types-in-dts](docs/rules/types-in-dts.md) | Require TypeScript type declarations (type/interface/enum) to be placed in .d.ts files | ✅ ![badge-recommended-react][] ![badge-recommended-typescript][] | |
27
25
 
28
26
  <!-- end auto-generated rules list -->
29
27
 
package/bun.lockb CHANGED
Binary file
@@ -0,0 +1,142 @@
1
+ <pre>
2
+ # Require React-Native StyleSheet.create(...) to be placed in a .styles.ts/.styles.tsx file (`th-rules/styles-in-styles-file`)
3
+
4
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, `recommended-react`, `recommended-typescript`.
5
+
6
+ <!-- end auto-generated rule header -->
7
+
8
+ This rule enforces that React-Native stylesheet declarations created via `StyleSheet.create(...)` live in a dedicated styles file, typically ending with `.styles.ts` or `.styles.tsx`.
9
+
10
+ In practice, this prevents implementation/component files from containing large style objects, and encourages consistent separation of concerns.
11
+
12
+ ## Rationale
13
+
14
+ Keeping styles in dedicated files:
15
+ - improves readability of component files by reducing visual noise,
16
+ - encourages reuse and consistency across components,
17
+ - makes style changes easier to review (diffs focus only on styles),
18
+ - standardizes project structure and naming conventions.
19
+
20
+ ## What the rule reports
21
+
22
+ The rule reports any `StyleSheet.create(...)` call in files whose names do **not** match one of the allowed suffixes (by default, `.styles.ts` and `.styles.tsx`).
23
+
24
+ Optionally, it can also report `StyleSheet.compose(...)` calls.
25
+
26
+ ## Examples
27
+
28
+ ### ❌ Incorrect
29
+
30
+ ```ts
31
+ // ArticleCard.tsx
32
+ import {StyleSheet} from 'react-native';
33
+
34
+ const styles = StyleSheet.create({
35
+ container: {padding: 12},
36
+ });
37
+ ```
38
+
39
+ ```ts
40
+ // AnyOtherFile.ts
41
+ import {StyleSheet} from 'react-native';
42
+
43
+ export default StyleSheet.create({
44
+ row: {flexDirection: 'row'},
45
+ });
46
+ ```
47
+
48
+ ### ✅ Correct
49
+
50
+ ```ts
51
+ // ArticleCard.styles.ts
52
+ import {StyleSheet} from 'react-native';
53
+
54
+ export const styles = StyleSheet.create({
55
+ container: {padding: 12},
56
+ });
57
+ ```
58
+
59
+ ```ts
60
+ // ArticleCard.tsx
61
+ import React from 'react';
62
+ import {View} from 'react-native';
63
+ import {styles} from './ArticleCard.styles';
64
+
65
+ export function ArticleCard() {
66
+ return <View style={styles.container} />;
67
+ }
68
+ ```
69
+
70
+ ### `includeCompose` example
71
+
72
+ When `includeCompose: true`, the following becomes invalid outside a `.styles.ts(x)` file:
73
+
74
+ ```ts
75
+ // ArticleCard.tsx
76
+ import {StyleSheet} from 'react-native';
77
+
78
+ const combined = StyleSheet.compose(
79
+ {padding: 12},
80
+ {margin: 8},
81
+ );
82
+ ```
83
+
84
+ With the same code moved to `ArticleCard.styles.ts`, it becomes valid.
85
+
86
+ ## Options
87
+
88
+ <!-- begin auto-generated rule options list -->
89
+
90
+ | Name | Type |
91
+ | :---------------- | :------- |
92
+ | `allowedSuffixes` | String[] |
93
+ | `includeCompose` | Boolean |
94
+
95
+ <!-- end auto-generated rule options list -->
96
+
97
+ ### `allowedSuffixes`
98
+
99
+ An array of filename suffixes that are allowed to contain `StyleSheet.create(...)`.
100
+
101
+ Default:
102
+ - `.styles.ts`
103
+ - `.styles.tsx`
104
+
105
+ Example:
106
+
107
+ ```json
108
+ {
109
+ "rules": {
110
+ "th-rules/styles-in-styles-file": ["error", {
111
+ "allowedSuffixes": [".styles.ts", ".styles.tsx", ".native-styles.ts"]
112
+ }]
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### `includeCompose`
118
+
119
+ When set to `true`, the rule also enforces the file restriction for `StyleSheet.compose(...)`.
120
+
121
+ Default: `false`
122
+
123
+ Example:
124
+
125
+ ```json
126
+ {
127
+ "rules": {
128
+ "th-rules/styles-in-styles-file": ["error", {
129
+ "includeCompose": true
130
+ }]
131
+ }
132
+ }
133
+ ```
134
+
135
+ ## Notes
136
+
137
+ - This rule only targets `StyleSheet.create(...)` (and optionally `StyleSheet.compose(...)`). It does not restrict:
138
+ - plain object styles (e.g., `const styles = { ... }`),
139
+ - other styling systems (e.g., styled-components, Tamagui, Emotion),
140
+ - calls to other `StyleSheet.*` helpers (e.g., `flatten`, `hairlineWidth`).
141
+ - The rule is filename-based. If ESLint is invoked with `<input>` (stdin), the rule will treat it as not being an allowed styles file.
142
+ </pre>
@@ -1,5 +1,7 @@
1
1
  # Require all top-level functions to be named/regular functions (`th-rules/top-level-functions`)
2
2
 
3
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, `recommended-react`, `recommended-typescript`.
4
+
3
5
  🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4
6
 
5
7
  <!-- end auto-generated rule header -->
@@ -38,9 +40,6 @@ function myFunction() {
38
40
  }
39
41
  ```
40
42
 
41
- ## Options
42
-
43
- This rule has no options.
44
43
 
45
44
  ## When Not To Use It
46
45
 
@@ -0,0 +1,112 @@
1
+ <pre>
2
+ # Require TypeScript type declarations (type/interface/enum) to be placed in .d.ts files (`th-rules/types-in-dts`)
3
+
4
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, `recommended-react`, `recommended-typescript`.
5
+
6
+ <!-- end auto-generated rule header -->
7
+
8
+ 💼 This rule is enabled in the following configs: ✅ `recommended`, `recommended-react`, `recommended-typescript`.
9
+
10
+ &lt;!-- end auto-generated rule header --&gt;
11
+
12
+ This rule enforces a strict separation between **runtime code** and **type declarations** by requiring all top-level TypeScript type declarations to be defined in `.d.ts` files.
13
+
14
+ The rule applies to:
15
+ - `type` aliases
16
+ - `interface` declarations
17
+ - `enum` declarations
18
+
19
+ Any of these constructs declared outside of a `.d.ts` file will be reported, unless explicitly allowed via configuration.
20
+
21
+ ## Rationale
22
+
23
+ Keeping type declarations in `.d.ts` files provides several benefits:
24
+
25
+ - Enforces a clean architectural boundary between runtime logic and type definitions
26
+ - Improves file readability by reducing noise in implementation files
27
+ - Encourages intentional, centralized ownership of public and shared types
28
+ - Prevents accidental runtime coupling with type-only constructs
29
+ - Aligns well with library-oriented or API-driven codebases
30
+
31
+ This rule is especially useful in large TypeScript projects, shared packages, or monorepos where type contracts are meant to be consumed independently of implementation.
32
+
33
+ ## Examples
34
+
35
+ ### ❌ Incorrect
36
+
37
+ ```ts
38
+ // user.ts
39
+ type UserId = string;
40
+
41
+ interface User {
42
+ id: UserId;
43
+ name: string;
44
+ }
45
+
46
+ enum Role {
47
+ Admin,
48
+ User,
49
+ }
50
+ ```
51
+
52
+ ### ✅ Correct
53
+
54
+ ```ts
55
+ // user.d.ts
56
+ type UserId = string;
57
+
58
+ interface User {
59
+ id: UserId;
60
+ name: string;
61
+ }
62
+
63
+ enum Role {
64
+ Admin,
65
+ User,
66
+ }
67
+ ```
68
+
69
+ ```ts
70
+ // user.ts
71
+ import type { User, UserId, Role } from './user';
72
+ ```
73
+
74
+ ### Allowed usage
75
+
76
+ Using types, interfaces, or enums is always allowed in any file:
77
+
78
+ ```ts
79
+ function getUser(id: UserId): User {
80
+ // ...
81
+ }
82
+ ```
83
+
84
+ ## Options
85
+
86
+ &lt;!-- begin auto-generated rule options list --&gt;
87
+
88
+ | Name | Type |
89
+ | :------------- | :------ |
90
+ | `allowDeclare` | Boolean |
91
+ | `allowEnums` | Boolean |
92
+
93
+ &lt;!-- end auto-generated rule options list --&gt;
94
+
95
+ ### `allowDeclare`
96
+
97
+ When set to `true`, the rule allows `declare` type declarations in non-`.d.ts` files.
98
+
99
+ This is useful when working with ambient declarations, global augmentations, or compatibility shims that must live alongside implementation code.
100
+
101
+ ### `allowEnums`
102
+
103
+ When set to `true`, the rule allows `enum` declarations in non-`.d.ts` files.
104
+
105
+ This can be helpful in codebases that rely on runtime enums while still wanting to enforce `.d.ts` placement for interfaces and type aliases.
106
+
107
+ ## Notes
108
+
109
+ - This rule does not enforce how types are imported or re-exported.
110
+ - It does not attempt to auto-fix violations, as moving declarations across files is not safe to do automatically.
111
+ - The rule operates purely at the syntax level and does not require type information.
112
+ </pre>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-th-rules",
3
- "version": "1.15.6",
3
+ "version": "1.17.0",
4
4
  "description": "A List of custom ESLint rules created by Tomer Horowitz",
5
5
  "keywords": [
6
6
  "eslint",
@@ -49,7 +49,7 @@
49
49
  "eslint-plugin-eslint-plugin": "^6.4.0",
50
50
  "eslint-plugin-node": "^11.1.0",
51
51
  "eslint-plugin-sonarjs": "^3.0.1",
52
- "eslint-plugin-th-rules": "1.14.1",
52
+ "eslint-plugin-th-rules": "1.15.6",
53
53
  "eslint-plugin-unicorn": "^56.0.1",
54
54
  "mocha": "^11.0.1",
55
55
  "npm-run-all": "^4.1.5",
@@ -62,5 +62,5 @@
62
62
  "typescript": "^5.7.2"
63
63
  },
64
64
  "license": "ISC",
65
- "packageManager": "yarn@4.6.0"
65
+ "packageManager": "yarn@4.12.0"
66
66
  }
package/src/index.js CHANGED
@@ -19,6 +19,8 @@ const configs = {
19
19
  'th-rules/no-default-export': 'error',
20
20
  'th-rules/no-comments': 'error',
21
21
  'th-rules/top-level-functions': 'error',
22
+ 'th-rules/styles-in-styles-file': 'error',
23
+ 'th-rules/types-in-dts': 'error',
22
24
  'unicorn/prefer-module': 'warn',
23
25
  'unicorn/filename-case': 'off',
24
26
  'unicorn/no-array-callback-reference': 'off',
@@ -72,6 +74,7 @@ for (const configName of Object.keys(configs)) {
72
74
  'plugin:react/recommended',
73
75
  'plugin:react-hooks/recommended',
74
76
  ...configs[configName].extends,
77
+
75
78
  ],
76
79
  rules: {
77
80
  ...configs[configName].rules,
@@ -0,0 +1,106 @@
1
+ const meta = {
2
+ type: 'problem',
3
+ docs: {
4
+ description: 'Require React-Native StyleSheet.create(...) to be placed in a .styles.ts/.styles.tsx file',
5
+ category: 'Stylistic Issues',
6
+ recommended: false,
7
+ url: 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/styles-in-styles-file.md',
8
+ },
9
+ schema: [
10
+ {
11
+ type: 'object',
12
+ properties: {
13
+ /**
14
+ * Allowed style file suffixes. Defaults:
15
+ * [".styles.ts", ".styles.tsx"]
16
+ */
17
+ allowedSuffixes: {
18
+ type: 'array',
19
+ items: {type: 'string', minLength: 1},
20
+ minItems: 1,
21
+ },
22
+
23
+ /**
24
+ * If true, also flag `StyleSheet.compose(...)` (optional).
25
+ */
26
+ includeCompose: {type: 'boolean'},
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ ],
31
+ messages: {
32
+ moveStyles:
33
+ 'React-Native styles must be moved to a .styles.ts/.styles.tsx file (for example "{{filename}}.styles.ts").',
34
+ },
35
+ };
36
+
37
+ function create(context) {
38
+ const options = context.options?.[0] ?? {};
39
+ const allowedSuffixes = options.allowedSuffixes ?? ['.styles.ts', '.styles.tsx'];
40
+ const includeCompose = Boolean(options.includeCompose);
41
+
42
+ function isAllowedStyleFile(filename) {
43
+ if (!filename || filename === '<input>') {
44
+ return false;
45
+ }
46
+
47
+ return allowedSuffixes.some(suffix => filename.endsWith(suffix));
48
+ }
49
+
50
+ function isStyleSheetMemberCall(node, memberName) {
51
+ const callee = node?.callee;
52
+ if (!callee || callee.type !== 'MemberExpression') {
53
+ return false;
54
+ }
55
+
56
+ const object = callee.object;
57
+ const property = callee.property;
58
+
59
+ const isStyleSheet
60
+ = object
61
+ && object.type === 'Identifier'
62
+ && object.name === 'StyleSheet';
63
+
64
+ const isMember
65
+ = !callee.computed
66
+ && property
67
+ && property.type === 'Identifier'
68
+ && property.name === memberName;
69
+
70
+ return isStyleSheet && isMember;
71
+ }
72
+
73
+ function report(node) {
74
+ const filename = context.getFilename();
75
+ if (isAllowedStyleFile(filename)) {
76
+ return;
77
+ }
78
+
79
+ context.report({
80
+ node,
81
+ messageId: 'moveStyles',
82
+ data: {
83
+ filename,
84
+ suffixes: allowedSuffixes.join(' or '),
85
+ },
86
+ });
87
+ }
88
+
89
+ return {
90
+ CallExpression(node) {
91
+ if (isStyleSheetMemberCall(node, 'create')) {
92
+ report(node);
93
+ return;
94
+ }
95
+
96
+ if (includeCompose && isStyleSheetMemberCall(node, 'compose')) {
97
+ report(node);
98
+ }
99
+ },
100
+ };
101
+ }
102
+
103
+ module.exports = {
104
+ meta,
105
+ create,
106
+ };
@@ -67,7 +67,6 @@ function buildFunctionExpressionReplacement(functionName, functionExprNode, sour
67
67
  */
68
68
  function buildAnonymousFunctionDeclarationReplacement(sourceCode, node, functionName = 'defaultFunction', isExport = false) {
69
69
  const originalText = sourceCode.getText(node);
70
- const asyncKeyword = node.async ? 'async ' : '';
71
70
  const exportKeyword = isExport ? 'export ' : '';
72
71
 
73
72
  let replaced = originalText;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @fileoverview Enforce that TypeScript type declarations exist only in .d.ts files.
3
+ */
4
+
5
+ const meta = {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Require TypeScript type declarations (type/interface/enum) to be placed in .d.ts files',
9
+ category: 'Best Practices',
10
+ recommended: true,
11
+ url: 'https://github.com/tomerh2001/eslint-plugin-th-rules/blob/main/docs/rules/types-in-dts.md',
12
+ },
13
+ schema: [
14
+ {
15
+ type: 'object',
16
+ properties: {
17
+ allowEnums: {type: 'boolean'},
18
+ allowDeclare: {type: 'boolean'},
19
+ },
20
+ additionalProperties: false,
21
+ },
22
+ ],
23
+ messages: {
24
+ moveToDts:
25
+ 'Type declarations must be moved to a .d.ts file (for example "{{filename}}.d.ts").',
26
+ },
27
+ };
28
+
29
+ /**
30
+ * @param {import('eslint').Rule.RuleContext} context
31
+ */
32
+ function create(context) {
33
+ const options = context.options?.[0] ?? {};
34
+ const allowEnums = Boolean(options.allowEnums);
35
+ const allowDeclare = Boolean(options.allowDeclare);
36
+
37
+ function isDtsFile(filename) {
38
+ if (!filename || filename === '<input>') {
39
+ return false;
40
+ }
41
+
42
+ return filename.endsWith('.d.ts');
43
+ }
44
+
45
+ function hasDeclareModifier(node) {
46
+ if (node && node.declare === true) {
47
+ return true;
48
+ }
49
+
50
+ const mods = node && Array.isArray(node.modifiers) ? node.modifiers : [];
51
+ return mods.some(m => m && m.type === 'TSDeclareKeyword');
52
+ }
53
+
54
+ function reportIfNotDts(node) {
55
+ const filename = context.getFilename();
56
+ if (isDtsFile(filename)) {
57
+ return;
58
+ }
59
+
60
+ if (allowDeclare && hasDeclareModifier(node)) {
61
+ return;
62
+ }
63
+
64
+ context.report({
65
+ node,
66
+ messageId: 'moveToDts',
67
+ data: {filename},
68
+ });
69
+ }
70
+
71
+ return {
72
+ TSTypeAliasDeclaration(node) {
73
+ reportIfNotDts(node);
74
+ },
75
+
76
+ TSInterfaceDeclaration(node) {
77
+ reportIfNotDts(node);
78
+ },
79
+
80
+ TSEnumDeclaration(node) {
81
+ if (allowEnums) {
82
+ return;
83
+ }
84
+
85
+ reportIfNotDts(node);
86
+ },
87
+
88
+ };
89
+ }
90
+
91
+ module.exports = {
92
+ meta,
93
+ create,
94
+ };