Skip to content

Commit 39a6f7b

Browse files
feat: add eslint-plugin-example-typed-linting
1 parent 0671384 commit 39a6f7b

File tree

13 files changed

+2317
-478
lines changed

13 files changed

+2317
-478
lines changed

package-lock.json

+2,013-478
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lib
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# `eslint-plugin-example-typed-linting`
2+
3+
An example ESLint plugin showing typed linting with `@typescript-eslint/utils`.
4+
5+
For documentation on custom ESLint plugins with typescript-eslint, see: <https://typescript-eslint.io/developers/custom-rules>.
6+
7+
```js
8+
// eslint.config.js
9+
import eslint from '@eslint/js';
10+
import exampleTypedLinting from 'eslint-plugin-example-typed-linting'
11+
import tseslint from 'typescript-eslint';
12+
13+
export default tseslint.config(
14+
{ ignores: ["lib"] },
15+
eslint.configs.recommended,
16+
...tseslint.configs.recommendedTypeChecked,
17+
exampleTypedLinting.configs.recommended // 👈
18+
{
19+
languageOptions: {
20+
parserOptions: {
21+
projectService:true,
22+
tsconfigRootDir: import.meta.dirname,
23+
},
24+
},
25+
},
26+
);
27+
```
28+
29+
## Rules
30+
31+
<!-- begin auto-generated rules list -->
32+
33+
| Name | Description |
34+
| :----------------------------------------------------- | :------------------------ |
35+
| [no-loop-over-enums](docs/rules/no-loop-over-enums.md) | Avoid looping over enums. |
36+
37+
<!-- end auto-generated rules list -->
38+
39+
## Development
40+
41+
To set up this individual package, `cd` to the path to it, then install dependencies:
42+
43+
```shell
44+
cd path/to/eslint-plugin-example-typed-linting
45+
npm i
46+
```
47+
48+
Then build files into the `lib` directory with TypeScript:
49+
50+
```shell
51+
npm run tsc
52+
```
53+
54+
You'll then be able to run standard package scripts:
55+
56+
- `npm run docs`: Regenerates documentation using [`eslint-doc-generator`](https://github.com/bmish/eslint-doc-generator)
57+
- `npm run docs --check`: Validates that documentation is generated and up-to-date.
58+
- `npm run lint`: Linting this plugin itself with ESLint
59+
60+
### Testing
61+
62+
This example uses [Vitest](https://vitest.dev):
63+
64+
```shell
65+
npm run test
66+
```
67+
68+
Note that files don't need to have been built to the `lib` directory to run tests.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Avoid looping over enums (`example-typed-linting/no-loop-over-enums`)
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Example rule that demonstrates banning `for-in` looping over `enum`s.
6+
7+
## Valid
8+
9+
```ts
10+
const values = {};
11+
for (const a in values) {
12+
}
13+
```
14+
15+
```ts
16+
const values = [];
17+
for (const a of values) {
18+
}
19+
```
20+
21+
## Invalid
22+
23+
```ts
24+
enum Values {}
25+
for (const a of values) {
26+
}
27+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import eslint from '@eslint/js';
2+
import tseslint from 'typescript-eslint';
3+
4+
export default tseslint.config(
5+
{ ignores: ["lib"] },
6+
eslint.configs.recommended,
7+
...tseslint.configs.recommendedTypeChecked,
8+
{
9+
languageOptions: {
10+
parserOptions: {
11+
projectService: {
12+
allowDefaultProject: ["*.config.*"],
13+
defaultProject: "tsconfig.json"
14+
},
15+
tsconfigRootDir: __dirname,
16+
},
17+
},
18+
},
19+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"dependencies": {
3+
"@typescript-eslint/utils": "8.0.0-alpha.59"
4+
},
5+
"devDependencies": {
6+
"@eslint/js": "^9.8.0",
7+
"@types/eslint__js": "^8.42.3",
8+
"@types/eslint": "^9.6.0",
9+
"@types/node": "^22.0.0",
10+
"@typescript-eslint/rule-tester": "^8.0.0-alpha.59",
11+
"eslint": "^9.8.0",
12+
"eslint-doc-generator": "^1.7.1",
13+
"typescript-eslint": "^8.0.0-alpha.59",
14+
"typescript": "^5.5.4",
15+
"vitest": "^2.0.4"
16+
},
17+
"exports": {
18+
".": {
19+
"types": "./lib/index.d.ts",
20+
"default": "./lib/index.js"
21+
}
22+
},
23+
"main": "lib/index.js",
24+
"scripts": {
25+
"docs": "eslint-doc-generator",
26+
"lint": "eslint",
27+
"tsc": "tsc",
28+
"test": "vitest"
29+
},
30+
"name": "eslint-plugin-example-typed-linting",
31+
"type": "commonjs",
32+
"version": "0.0.0"
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { rules } from "./rules/index.js";
2+
3+
const { name, version } =
4+
// `import`ing here would bypass the TSConfig's `"rootDir": "src"`
5+
// eslint-disable-next-line @typescript-eslint/no-require-imports
6+
require("../package.json") as typeof import("../package.json");
7+
8+
const plugin = {
9+
configs: {
10+
get recommended() {
11+
return recommended;
12+
},
13+
},
14+
meta: { name, version },
15+
rules,
16+
};
17+
18+
const recommended = {
19+
plugins: {
20+
"example-typed-linting": plugin,
21+
},
22+
rules,
23+
};
24+
25+
export = plugin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { rule as noLoopOverEnums } from "./no-loop-over-enum.js";
2+
3+
export const rules = {
4+
"no-loop-over-enums": noLoopOverEnums,
5+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import path from "node:path";
2+
import tseslint from "typescript-eslint";
3+
import { RuleTester } from "@typescript-eslint/rule-tester";
4+
import * as vitest from "vitest";
5+
6+
import { rule } from "./no-loop-over-enum.js";
7+
8+
RuleTester.afterAll = vitest.afterAll;
9+
RuleTester.it = vitest.it;
10+
RuleTester.itOnly = vitest.it.only;
11+
RuleTester.describe = vitest.describe;
12+
13+
const ruleTester = new RuleTester({
14+
languageOptions: {
15+
parser: tseslint.parser,
16+
parserOptions: {
17+
projectService: {
18+
allowDefaultProject: ["file.ts"],
19+
defaultProject: "tsconfig.json",
20+
},
21+
tsconfigRootDir: path.join(__dirname, "../.."),
22+
},
23+
},
24+
});
25+
26+
ruleTester.run("no-loop-over-enum", rule, {
27+
valid: [
28+
`enum Values {}`,
29+
`for (const a in []) {}`,
30+
`for (const a of []) {}`,
31+
`
32+
const values = {};
33+
for (const a in values) {}
34+
`,
35+
`
36+
const values = [];
37+
for (const a of values) {}
38+
`,
39+
],
40+
invalid: [
41+
{
42+
code: `
43+
enum Values {}
44+
for (const a in Values) {}
45+
`,
46+
errors: [
47+
{
48+
column: 27,
49+
endColumn: 33,
50+
line: 3,
51+
endLine: 3,
52+
messageId: "loopOverEnum",
53+
},
54+
],
55+
},
56+
],
57+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { ESLintUtils } from "@typescript-eslint/utils";
2+
import * as ts from "typescript";
3+
4+
import { createRule } from "../utils.js";
5+
6+
export const rule = createRule({
7+
create(context) {
8+
const services = ESLintUtils.getParserServices(context);
9+
10+
return {
11+
ForInStatement(node) {
12+
const type = services.getTypeAtLocation(node.right);
13+
14+
if ((type.symbol.flags & ts.SymbolFlags.Enum) !== 0) {
15+
context.report({
16+
messageId: "loopOverEnum",
17+
node: node.right,
18+
});
19+
}
20+
},
21+
};
22+
},
23+
meta: {
24+
docs: {
25+
description: "Avoid looping over enums.",
26+
recommended: true,
27+
},
28+
messages: {
29+
loopOverEnum: "Do not loop over enums.",
30+
},
31+
type: "suggestion",
32+
schema: [],
33+
},
34+
name: "no-loop-over-enum",
35+
defaultOptions: [],
36+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ESLintUtils } from "@typescript-eslint/utils";
2+
3+
export interface ExampleTypedLintingRuleDocs {
4+
description: string;
5+
recommended?: boolean;
6+
}
7+
8+
export const createRule = ESLintUtils.RuleCreator<ExampleTypedLintingRuleDocs>(
9+
(name) =>
10+
`https://github.com/typescript-eslint/examples/tree/main/eslint-plugin-example-typed-linting/docs/${name}.md`
11+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"declaration": true,
4+
"esModuleInterop": true,
5+
"module": "NodeNext",
6+
"outDir": "lib",
7+
"resolveJsonModule": true,
8+
"rootDir": "src",
9+
"skipLibCheck": true,
10+
"sourceMap": true,
11+
"strict": true,
12+
"target": "ES2022"
13+
},
14+
"include": ["src"]
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from "vitest/config";
2+
3+
export default defineConfig({
4+
test: {
5+
exclude: ["lib"],
6+
},
7+
});

0 commit comments

Comments
 (0)