@alextheman/eslint-plugin 2.2.0 → 2.2.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/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # @alextheman/eslint-plugin
2
+
3
+ This is my personal ESLint plugin. It provides custom ESLint rules along with some base configs that you can just drop straight into your project with minimal setup.
4
+
5
+ ## Installation
6
+
7
+ To install this plugin into your project, you can do so with the following command:
8
+
9
+ ```bash
10
+ npm install --save-dev @alextheman/eslint-plugin
11
+ ```
12
+
13
+ Since most of the time, you will be using this plugin only to check your code as opposed to using it at runtime, we recommend installing it as a dev dependency. If you are using this in your own ESLint plugin, however, we recommend installing it as a peer dependency
14
+
15
+ ```bash
16
+ npm install --save-peer @alextheman/eslint-plugin
17
+ ```
18
+
19
+ ## Creating a new rule
20
+
21
+ To add a new rule, you must first create the skeleton structure of your rule, following the given template:
22
+
23
+ ```typescript
24
+ const myRule = createRule({
25
+ name: "my-rule",
26
+ meta: {
27
+ docs: {
28
+ description: "Description of the rule",
29
+ },
30
+ messages: {
31
+ message: "Message to be displayed on violation",
32
+ },
33
+ type: "suggestion",
34
+ schema: [],
35
+ },
36
+ defaultOptions: [],
37
+ create(context) {
38
+
39
+ },
40
+ });
41
+
42
+ export default myRule;
43
+ ```
44
+
45
+ The schema may take an array of objects defining what options can be configured. At the very least, these take in a type, but if you specify the type as object, you must also provide the properties. For example:
46
+
47
+ ```typescript
48
+ schema: [
49
+ {
50
+ type: "object",
51
+ properties: {
52
+ preference: {
53
+ type: "string",
54
+ },
55
+ },
56
+ additionalProperties: false,
57
+ }
58
+ ]
59
+ ```
60
+
61
+ Add the rule to `src/rules/index.ts`:
62
+
63
+ ```typescript
64
+ import consistentTestFunction from "src/rules/consistent-test-function";
65
+ import noIsolatedTests from "src/rules/no-isolated-tests";
66
+ import noNamespaceImports from "src/rules/no-namespace-imports";
67
+ // ...
68
+ import myRule from "src/rules/my-rule"
69
+
70
+ export default {
71
+ "consistent-test-function": consistentTestFunction,
72
+ "no-isolated-tests": noIsolatedTests,
73
+ "no-namespace-imports": noNamespaceImports,
74
+ // ...
75
+ "my-rule": myRule
76
+ };
77
+ ```
78
+
79
+
80
+ Next, create the test suite for the rule. You have two choices for which test runner to use - you may either use the `standardRuleTester` or `ruleTesterWithParser`.
81
+
82
+ Use the `ruleTesterWithParser` if you are writing a rule that requires context about the current directory structure. You will then have to specify a filename property for every valid and invalid entry, and it must be a path that exists relative to `tests/fixtures`. Note that we are currently looking into the possibility of using something like tempy to generate a temporary directory per test to run the tests in, but this is not quite ready at the moment so this is the best for now. Feel free to create a pull request if you've come up with something, though.
83
+
84
+ For all other rules, `standardRuleTester` is the best to use.
85
+
86
+ In both cases, you must first pass the rule name, then the actual rule property, then an object containing all valid and invalid cases:
87
+
88
+ ```typescript
89
+ standardRuleTester.run("my-rule", rules["my-rule"], {
90
+ valid: [
91
+ {
92
+ code: "Valid code here"
93
+ }
94
+ ],
95
+ invalid: [
96
+ {
97
+ code: "Invalid code here",
98
+ errors: {
99
+ messageId: "message",
100
+ data: {
101
+ source: "source"
102
+ }
103
+ }
104
+ }
105
+ ]
106
+ })
107
+ ```
108
+
109
+ Finally, you may create your rule by adding code to the `create(context)` method. Creating an ESLint rule is one of things that is very involved and goes beyond the scope of this README, but you can check out [the ESLint docs](https://eslint.org/docs/latest/extend/custom-rule-tutorial) for more information.
110
+
111
+ ## Configs
112
+ ### The Config Groups
113
+
114
+ The configs of this plugin are structured in a very particular way. We have our general configs in `src/configs/general`, our plugin configs in `src/configs/plugin`, and our combined configs in `src/configs/combined`. In all three cases, we use the [ESLint flat config style](https://eslint.org/blog/2022/08/new-config-system-part-2/) as that's the most up-to-date config style and allows for more flexibility than just using a package.json or .eslintrc.
115
+
116
+
117
+ The general configs are to be used for defining a ruleset that does NOT rely on the custom plugin rules. They must ONLY use external rules. These rules have already been broken down into `javaScriptBase`, `reactBase`, `testsBase`, and `typeScriptBase` (and also `prettierRules` because Prettier can sneak into this ESLint plugin because why not?). Try and keep the configs as separate and scoped to their main point of focus as possible. The only one that extends another is `typeScriptBase` extending `javaScriptBase` as of now. That one is fine, but otherwise, try not to extend another custom config from another unless there is good reason to. This allows the user more freedom to truly customise the rules to their usage.
118
+
119
+ The plugin configs are to be used for defining a ruleset that ONLY relies on the custom plugin rules. They must NOT use any external rules. This ensures that, for any users who just want a few recommended configs for only the plugin's rules, they can choose from the ones provided without also having to deal with a bunch of other external rules polluting it as well.
120
+
121
+ Lastly, the combined configs may use combinations of both external rules and custom rules. They will most frequently extend configs from both the general rules and plugin rules, but we may also add/disable rules specifically in these combined rules, in case there may be some overlap that none of the other configs account for. This is the most flexible group of the three, but is also the most likely one to break on new updates. As such, I would recommend against using configs from this group in production code and go with one of the other more stable ones (unless you're me and actually control the plugin entirely).
122
+
123
+ ### Usage
124
+
125
+ The configs are defined on the configs property of the plugin. All general rules are prefixed with `general/`, and likewise for the plugin and combined rules. All rulesets themselves are given in `kebab-case`. With that in mind, an example usage in `eslint.config.js` may look like this:
126
+
127
+ ```javascript
128
+ import plugin from "@alextheman/eslint-plugin"
129
+
130
+ export default plugin.configs["general/typescript-base"]
131
+ ```
132
+
133
+ If you want to extend this, you can do so by spreading the rules into an array and adding extra configuration properties:
134
+
135
+ ```javascript
136
+ import plugin from "@alextheman/eslint-plugin"
137
+
138
+ export default [
139
+ ...plugin.configs["general/typescript-base"],
140
+ {
141
+ rules: {
142
+ "no-unused-vars": "off"
143
+ }
144
+ }
145
+ ]
146
+ ```
147
+
148
+ ### Adding a config
149
+
150
+ Starting with the general config because that's the easiest - you can create a config file in `src/configs/general` and define a config in the same way you would define any regular ESLint flat config. Again, please make sure you do NOT include any plugin-specific rules.
151
+
152
+ Once you have done this, export it from `src/configs/general/index.ts`, then in `src/alexPlugin.ts`, go to where `alexPlugin.configs`is defined and add the config to the object defined under the `general` property.
153
+
154
+ ```typescript
155
+ alexPlugin.configs = createPluginConfigs({
156
+ general: {
157
+ javaScript: javaScriptBase,
158
+ typeScript: typeScriptBase,
159
+ react: reactBase,
160
+ tests: testsBase,
161
+ },
162
+ // ...
163
+ });
164
+ ```
165
+
166
+ The `createPluginConfigs` helper will map this to a more standard ESLint naming convention. That is, something like `{ general: { myRuleset } }` will be accessible on the plugin from `plugin.configs["general/my-ruleset"]`.
167
+
168
+ For plugin/combined configs, this is where it gets trickier. These rulesets tend to rely on the usage of the plugin itself, but we also need to define the plugin to be able to use it in configs. As such, the workaround for this is to provide a function that takes in the plugin and returns the config. For example:
169
+
170
+ ```typescript
171
+ import type { Linter } from "eslint";
172
+ import type { AlexPlugin } from "src/index";
173
+
174
+ function createPluginBaseConfig(plugin: AlexPlugin): Linter.Config[] {
175
+ return [
176
+ {
177
+ plugins: {
178
+ "@alextheman": plugin,
179
+ },
180
+ rules: {
181
+ "@alextheman/no-namespace-imports": "error",
182
+ "@alextheman/no-relative-imports": "error",
183
+ "@alextheman/use-normalized-imports": "error",
184
+ "@alextheman/use-object-shorthand": "error",
185
+ },
186
+ },
187
+ ];
188
+ }
189
+
190
+ export default createPluginBaseConfig;
191
+ ```
192
+
193
+ This then gets exported from the relevant folder's `index.ts` file again, and then where we define our configs in `src/alexPlugin.ts`, we invoke the function passing in the plugin.
194
+
195
+ ```typescript
196
+ alexPlugin.configs = createPluginConfigs({
197
+ // ...
198
+ plugin: {
199
+ base: createPluginBaseConfig(alexPlugin),
200
+ tests: createPluginTestsBaseConfig(alexPlugin),
201
+ },
202
+ combined: {
203
+ javaScript: createCombinedJavaScriptBaseConfig(alexPlugin),
204
+ typeScript: createCombinedTypeScriptBaseConfig(alexPlugin),
205
+ react: createCombinedReactBaseConfig(alexPlugin),
206
+ tests: createCombinedTestsBaseConfig(alexPlugin),
207
+ typeScriptReact: createCombinedTypeScriptReactBaseConfig(alexPlugin),
208
+ javaScriptReact: createCombinedJavaScriptReactBaseConfig(alexPlugin),
209
+ },
210
+ });
211
+ ```
212
+
213
+ Note that this also means that, in config files that provide them using this functional approach, we should NEVER import the plugin directly. This would most likely create circular imports where the plugin ends up calling itself while trying to define itself. Instead, always use the given plugin argument if you want to access the plugin. In the combined configs, if you want to refer to an existing plugin ruleset, it's best to import the function for that ruleset, then call it and pass in the plugin, like so:
214
+
215
+ ```typescript
216
+ import type { Linter } from "eslint";
217
+ import type { AlexPlugin } from "src/index";
218
+
219
+ import { typeScriptBase } from "src/configs/general";
220
+ import { createPluginBaseConfig } from "src/configs/plugin";
221
+
222
+ function createCombinedTypeScriptBaseConfig(plugin: AlexPlugin): Linter.Config[] {
223
+ return [...createPluginBaseConfig(plugin), ...typeScriptBase];
224
+ }
225
+
226
+ export default createCombinedTypeScriptBaseConfig;
227
+ ```
package/dist/index.cjs CHANGED
@@ -3715,7 +3715,7 @@ module.exports = __toCommonJS(index_exports);
3715
3715
 
3716
3716
  // package.json
3717
3717
  var name = "@alextheman/eslint-plugin";
3718
- var version = "2.2.0";
3718
+ var version = "2.2.2";
3719
3719
 
3720
3720
  // src/configs/general/javaScriptBase.ts
3721
3721
  var import_js = __toESM(require_src(), 1);
@@ -3929,7 +3929,8 @@ var reactBase = [
3929
3929
  }
3930
3930
  ],
3931
3931
  "react-hooks/exhaustive-deps": "off",
3932
- "react-refresh/only-export-components": "off"
3932
+ "react-refresh/only-export-components": "off",
3933
+ "react/no-unescaped-entities": "off"
3933
3934
  }),
3934
3935
  settings: {
3935
3936
  react: {
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@ import z from 'zod';
3
3
  import { Config } from 'prettier';
4
4
 
5
5
  var name = "@alextheman/eslint-plugin";
6
- var version = "2.2.0";
6
+ var version = "2.2.2";
7
7
 
8
8
  interface AlexPluginConfigs {
9
9
  general: {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import z from 'zod';
3
3
  import { Config } from 'prettier';
4
4
 
5
5
  var name = "@alextheman/eslint-plugin";
6
- var version = "2.2.0";
6
+ var version = "2.2.2";
7
7
 
8
8
  interface AlexPluginConfigs {
9
9
  general: {
package/dist/index.js CHANGED
@@ -3700,7 +3700,7 @@ var require_globals2 = __commonJS({
3700
3700
 
3701
3701
  // package.json
3702
3702
  var name = "@alextheman/eslint-plugin";
3703
- var version = "2.2.0";
3703
+ var version = "2.2.2";
3704
3704
 
3705
3705
  // src/configs/general/javaScriptBase.ts
3706
3706
  var import_js = __toESM(require_src(), 1);
@@ -3914,7 +3914,8 @@ var reactBase = [
3914
3914
  }
3915
3915
  ],
3916
3916
  "react-hooks/exhaustive-deps": "off",
3917
- "react-refresh/only-export-components": "off"
3917
+ "react-refresh/only-export-components": "off",
3918
+ "react/no-unescaped-entities": "off"
3918
3919
  }),
3919
3920
  settings: {
3920
3921
  react: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alextheman/eslint-plugin",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "A package to provide custom ESLint rules and configs",
5
5
  "license": "ISC",
6
6
  "author": "alextheman",
@@ -35,16 +35,16 @@
35
35
  "dependencies": {
36
36
  "@alextheman/utility": "^2.2.0",
37
37
  "common-tags": "^1.8.2",
38
- "eslint-plugin-package-json": "^0.57.0",
38
+ "eslint-plugin-package-json": "^0.58.0",
39
39
  "zod": "^4.1.12"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/common-tags": "^1.8.4",
43
43
  "@types/eslint": "^9.6.1",
44
- "@types/node": "^24.8.1",
45
- "@typescript-eslint/rule-tester": "^8.46.1",
46
- "@typescript-eslint/utils": "^8.46.1",
47
- "eslint-plugin-eslint-plugin": "^7.0.0",
44
+ "@types/node": "^24.9.1",
45
+ "@typescript-eslint/rule-tester": "^8.46.2",
46
+ "@typescript-eslint/utils": "^8.46.2",
47
+ "eslint-plugin-eslint-plugin": "^7.1.0",
48
48
  "globals": "^16.4.0",
49
49
  "husky": "^9.1.7",
50
50
  "jiti": "^2.6.1",
@@ -52,7 +52,7 @@
52
52
  "tsup": "^8.5.0",
53
53
  "typescript": "^5.9.3",
54
54
  "vite-tsconfig-paths": "^5.1.4",
55
- "vitest": "^3.2.4"
55
+ "vitest": "^4.0.3"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@typescript-eslint/eslint-plugin": "^8.37.0",