@alextheman/eslint-plugin 2.1.0 → 2.2.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/README.md +227 -0
- package/dist/index.cjs +54 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +54 -1
- package/package.json +1 -1
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.1
|
|
3718
|
+
var version = "2.2.1";
|
|
3719
3719
|
|
|
3720
3720
|
// src/configs/general/javaScriptBase.ts
|
|
3721
3721
|
var import_js = __toESM(require_src(), 1);
|
|
@@ -3878,6 +3878,7 @@ function createPluginBaseConfig(plugin) {
|
|
|
3878
3878
|
rules: {
|
|
3879
3879
|
"@alextheman/no-namespace-imports": "error",
|
|
3880
3880
|
"@alextheman/no-relative-imports": "error",
|
|
3881
|
+
"@alextheman/use-normalized-imports": "error",
|
|
3881
3882
|
"@alextheman/use-object-shorthand": "error"
|
|
3882
3883
|
}
|
|
3883
3884
|
}
|
|
@@ -4463,6 +4464,57 @@ var noSkippedTests = createRule_default({
|
|
|
4463
4464
|
});
|
|
4464
4465
|
var no_skipped_tests_default = noSkippedTests;
|
|
4465
4466
|
|
|
4467
|
+
// src/rules/use-normalized-imports.ts
|
|
4468
|
+
var import_path = __toESM(require("path"), 1);
|
|
4469
|
+
var useNormalizedImports = createRule_default({
|
|
4470
|
+
name: "use-normalized-imports",
|
|
4471
|
+
meta: {
|
|
4472
|
+
docs: {
|
|
4473
|
+
description: "Enforce the usage of normalised imports (i.e. import paths that you would only get from path.posix.normalize())"
|
|
4474
|
+
},
|
|
4475
|
+
messages: {
|
|
4476
|
+
pathNotNormalized: "Import path {{nonNormalized}} is not normalised. Please use {{normalized}} instead."
|
|
4477
|
+
},
|
|
4478
|
+
type: "suggestion",
|
|
4479
|
+
schema: [
|
|
4480
|
+
{
|
|
4481
|
+
type: "object",
|
|
4482
|
+
properties: {
|
|
4483
|
+
fixable: {
|
|
4484
|
+
type: "boolean"
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
],
|
|
4489
|
+
fixable: "code"
|
|
4490
|
+
},
|
|
4491
|
+
defaultOptions: [{ fixable: true }],
|
|
4492
|
+
create(context) {
|
|
4493
|
+
var _a, _b;
|
|
4494
|
+
const isFixable = (_b = (_a = context.options[0]) == null ? void 0 : _a.fixable) != null ? _b : true;
|
|
4495
|
+
return {
|
|
4496
|
+
ImportDeclaration(node) {
|
|
4497
|
+
const normalizedPath = import_path.default.posix.normalize(node.source.value);
|
|
4498
|
+
if (node.source.value !== normalizedPath) {
|
|
4499
|
+
return context.report({
|
|
4500
|
+
node,
|
|
4501
|
+
messageId: "pathNotNormalized",
|
|
4502
|
+
data: {
|
|
4503
|
+
nonNormalized: node.source.value,
|
|
4504
|
+
normalized: normalizedPath
|
|
4505
|
+
},
|
|
4506
|
+
fix: isFixable ? (fixer) => {
|
|
4507
|
+
const [quote] = node.source.raw;
|
|
4508
|
+
return fixer.replaceText(node.source, `${quote}${normalizedPath}${quote}`);
|
|
4509
|
+
} : void 0
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
};
|
|
4514
|
+
}
|
|
4515
|
+
});
|
|
4516
|
+
var use_normalized_imports_default = useNormalizedImports;
|
|
4517
|
+
|
|
4466
4518
|
// src/rules/use-object-shorthand.ts
|
|
4467
4519
|
var import_utils4 = require("@typescript-eslint/utils");
|
|
4468
4520
|
var useObjectShorthand = createRule_default({
|
|
@@ -4509,6 +4561,7 @@ var rules_default = {
|
|
|
4509
4561
|
"no-plugin-configs-access-from-src-configs": no_plugin_configs_access_from_src_configs_default,
|
|
4510
4562
|
"no-relative-imports": no_relative_imports_default,
|
|
4511
4563
|
"no-skipped-tests": no_skipped_tests_default,
|
|
4564
|
+
"use-normalized-imports": use_normalized_imports_default,
|
|
4512
4565
|
"use-object-shorthand": use_object_shorthand_default
|
|
4513
4566
|
};
|
|
4514
4567
|
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
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.1
|
|
3703
|
+
var version = "2.2.1";
|
|
3704
3704
|
|
|
3705
3705
|
// src/configs/general/javaScriptBase.ts
|
|
3706
3706
|
var import_js = __toESM(require_src(), 1);
|
|
@@ -3863,6 +3863,7 @@ function createPluginBaseConfig(plugin) {
|
|
|
3863
3863
|
rules: {
|
|
3864
3864
|
"@alextheman/no-namespace-imports": "error",
|
|
3865
3865
|
"@alextheman/no-relative-imports": "error",
|
|
3866
|
+
"@alextheman/use-normalized-imports": "error",
|
|
3866
3867
|
"@alextheman/use-object-shorthand": "error"
|
|
3867
3868
|
}
|
|
3868
3869
|
}
|
|
@@ -4448,6 +4449,57 @@ var noSkippedTests = createRule_default({
|
|
|
4448
4449
|
});
|
|
4449
4450
|
var no_skipped_tests_default = noSkippedTests;
|
|
4450
4451
|
|
|
4452
|
+
// src/rules/use-normalized-imports.ts
|
|
4453
|
+
import path from "path";
|
|
4454
|
+
var useNormalizedImports = createRule_default({
|
|
4455
|
+
name: "use-normalized-imports",
|
|
4456
|
+
meta: {
|
|
4457
|
+
docs: {
|
|
4458
|
+
description: "Enforce the usage of normalised imports (i.e. import paths that you would only get from path.posix.normalize())"
|
|
4459
|
+
},
|
|
4460
|
+
messages: {
|
|
4461
|
+
pathNotNormalized: "Import path {{nonNormalized}} is not normalised. Please use {{normalized}} instead."
|
|
4462
|
+
},
|
|
4463
|
+
type: "suggestion",
|
|
4464
|
+
schema: [
|
|
4465
|
+
{
|
|
4466
|
+
type: "object",
|
|
4467
|
+
properties: {
|
|
4468
|
+
fixable: {
|
|
4469
|
+
type: "boolean"
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
],
|
|
4474
|
+
fixable: "code"
|
|
4475
|
+
},
|
|
4476
|
+
defaultOptions: [{ fixable: true }],
|
|
4477
|
+
create(context) {
|
|
4478
|
+
var _a, _b;
|
|
4479
|
+
const isFixable = (_b = (_a = context.options[0]) == null ? void 0 : _a.fixable) != null ? _b : true;
|
|
4480
|
+
return {
|
|
4481
|
+
ImportDeclaration(node) {
|
|
4482
|
+
const normalizedPath = path.posix.normalize(node.source.value);
|
|
4483
|
+
if (node.source.value !== normalizedPath) {
|
|
4484
|
+
return context.report({
|
|
4485
|
+
node,
|
|
4486
|
+
messageId: "pathNotNormalized",
|
|
4487
|
+
data: {
|
|
4488
|
+
nonNormalized: node.source.value,
|
|
4489
|
+
normalized: normalizedPath
|
|
4490
|
+
},
|
|
4491
|
+
fix: isFixable ? (fixer) => {
|
|
4492
|
+
const [quote] = node.source.raw;
|
|
4493
|
+
return fixer.replaceText(node.source, `${quote}${normalizedPath}${quote}`);
|
|
4494
|
+
} : void 0
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
});
|
|
4501
|
+
var use_normalized_imports_default = useNormalizedImports;
|
|
4502
|
+
|
|
4451
4503
|
// src/rules/use-object-shorthand.ts
|
|
4452
4504
|
import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
|
|
4453
4505
|
var useObjectShorthand = createRule_default({
|
|
@@ -4494,6 +4546,7 @@ var rules_default = {
|
|
|
4494
4546
|
"no-plugin-configs-access-from-src-configs": no_plugin_configs_access_from_src_configs_default,
|
|
4495
4547
|
"no-relative-imports": no_relative_imports_default,
|
|
4496
4548
|
"no-skipped-tests": no_skipped_tests_default,
|
|
4549
|
+
"use-normalized-imports": use_normalized_imports_default,
|
|
4497
4550
|
"use-object-shorthand": use_object_shorthand_default
|
|
4498
4551
|
};
|
|
4499
4552
|
|