Skip to content

Commit 494b18c

Browse files
Merge pull request #13 from typescript-eslint/example-typed-plugin
feat: add eslint-plugin-example-typed-linting
2 parents 0671384 + b91625d commit 494b18c

File tree

13 files changed

+2350
-482
lines changed

13 files changed

+2350
-482
lines changed

package-lock.json

+2,037-482
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,70 @@
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+
💭 Requires [type information](https://typescript-eslint.io/linting/typed-linting).
34+
35+
| Name | Description | 💭 |
36+
| :----------------------------------------------------- | :------------------------ | :- |
37+
| [no-loop-over-enums](docs/rules/no-loop-over-enums.md) | Avoid looping over enums. | 💭 |
38+
39+
<!-- end auto-generated rules list -->
40+
41+
## Development
42+
43+
To set up this individual package, `cd` to the path to it, then install dependencies:
44+
45+
```shell
46+
cd path/to/eslint-plugin-example-typed-linting
47+
npm i
48+
```
49+
50+
Then build files into the `lib` directory with TypeScript:
51+
52+
```shell
53+
npm run tsc
54+
```
55+
56+
You'll then be able to run standard package scripts:
57+
58+
- `npm run docs`: Regenerates documentation using [`eslint-doc-generator`](https://github.com/bmish/eslint-doc-generator)
59+
- `npm run docs --check`: Validates that documentation is generated and up-to-date.
60+
- `npm run lint`: Linting this plugin itself with ESLint
61+
62+
### Testing
63+
64+
This example uses [Vitest](https://vitest.dev):
65+
66+
```shell
67+
npm run test
68+
```
69+
70+
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,29 @@
1+
# Avoid looping over enums (`example-typed-linting/no-loop-over-enums`)
2+
3+
💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Example rule that demonstrates banning `for-in` looping over `enum`s.
8+
9+
## Valid
10+
11+
```ts
12+
const values = {};
13+
for (const a in values) {
14+
}
15+
```
16+
17+
```ts
18+
const values = [];
19+
for (const a of values) {
20+
}
21+
```
22+
23+
## Invalid
24+
25+
```ts
26+
enum Values {}
27+
for (const a of values) {
28+
}
29+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import eslint from '@eslint/js';
2+
import eslintPlugin from 'eslint-plugin-eslint-plugin'
3+
import tseslint from 'typescript-eslint';
4+
5+
export default tseslint.config(
6+
{ ignores: ["lib"] },
7+
eslint.configs.recommended,
8+
...tseslint.configs.recommendedTypeChecked,
9+
eslintPlugin.configs['flat/recommended'],
10+
{
11+
languageOptions: {
12+
parserOptions: {
13+
projectService: {
14+
allowDefaultProject: ["*.config.*"],
15+
defaultProject: "tsconfig.json"
16+
},
17+
tsconfigRootDir: import.meta.dirname,
18+
},
19+
},
20+
},
21+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"dependencies": {
3+
"@typescript-eslint/utils": "8.0.0-alpha.59"
4+
},
5+
"devDependencies": {
6+
"@eslint/js": "^9.8.0",
7+
"@types/eslint": "^9.6.0",
8+
"@types/eslint__js": "^8.42.3",
9+
"@types/node": "^22.0.2",
10+
"@typescript-eslint/rule-tester": "^8.0.0-alpha.59",
11+
"eslint": "^9.8.0",
12+
"eslint-doc-generator": "^1.7.1",
13+
"eslint-plugin-eslint-plugin": "^6.2.0",
14+
"typescript": "^5.5.4",
15+
"typescript-eslint": "^8.0.0-alpha.59",
16+
"vitest": "^2.0.4"
17+
},
18+
"exports": {
19+
".": {
20+
"types": "./lib/index.d.ts",
21+
"default": "./lib/index.js"
22+
}
23+
},
24+
"main": "lib/index.js",
25+
"scripts": {
26+
"docs": "eslint-doc-generator",
27+
"lint": "eslint",
28+
"tsc": "tsc",
29+
"test": "vitest"
30+
},
31+
"name": "eslint-plugin-example-typed-linting",
32+
"type": "commonjs",
33+
"version": "0.0.0"
34+
}
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: ["*.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,37 @@
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) {
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+
requiresTypeChecking: true,
28+
},
29+
messages: {
30+
loopOverEnum: "Do not loop over enums.",
31+
},
32+
type: "suggestion",
33+
schema: [],
34+
},
35+
name: "no-loop-over-enum",
36+
defaultOptions: [],
37+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ESLintUtils } from "@typescript-eslint/utils";
2+
3+
export interface ExampleTypedLintingRuleDocs {
4+
description: string;
5+
recommended?: boolean;
6+
requiresTypeChecking?: boolean;
7+
}
8+
9+
export const createRule = ESLintUtils.RuleCreator<ExampleTypedLintingRuleDocs>(
10+
(name) =>
11+
`https://github.com/typescript-eslint/examples/tree/main/eslint-plugin-example-typed-linting/docs/${name}.md`
12+
);
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)