@kazupon/eslint-plugin 0.1.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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 kazuya kawaguchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # @kazupon/eslint-plugin
2
+
3
+ ESLint plugin for @kazupon
4
+
5
+ <!-- eslint-disable markdown/no-missing-label-refs -->
6
+
7
+ > [!WARNING]
8
+ > This eslint-plugin is a rule that I’ve made as I needed it to advance the open-source project.
9
+ > Therefore, updates are often made, and sometimes there are also destructive changes.
10
+ > This eslint-plugin is open-source, so you can use it for your own projects, but please keep in mind that this plugin is specialized for my own use.
11
+ > Of course, since it is open-source, you are free to fork it and use it yourself 😉.
12
+
13
+ <!-- eslint-enable markdown/no-missing-label-refs -->
14
+
15
+ ## 💿 Installation
16
+
17
+ ```sh
18
+ # npm
19
+ npm install --save-dev @kazupon/eslint-plugin
20
+
21
+ ## yarn
22
+ yarn add -D @kazupon/eslint-plugin
23
+
24
+ ## pnpm
25
+ pnpm add -D @kazupon/eslint-plugin
26
+
27
+ ## bum
28
+ bun add -D @kazupon/eslint-plugin
29
+ ```
30
+
31
+ ## 📋 Requirements
32
+
33
+ - **ESLint**: v9 or later
34
+ - **Configuration**: flat config style `eslint.config.[js|ts]`, not support legacy config style `.eslintrc`
35
+ - **Node.js**: v20 or later
36
+
37
+ ## 🚀 Usage
38
+
39
+ Example `eslint.config.js`:
40
+
41
+ ```js
42
+ import { defineConfig } from 'eslint/config'
43
+ import kazupon from '@kazupon/eslint-plugin'
44
+
45
+ export default defineConfig(
46
+ ...kazupon.configs.recommended,
47
+ {
48
+ // ...
49
+ }
50
+ // ... something other configurations
51
+ )
52
+ ```
53
+
54
+ ## ✅ Rules
55
+
56
+ The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench 🔧 below.
57
+ The rules with the following star ⭐ are included in the configs.
58
+
59
+ <!--RULES_TABLE_START-->
60
+
61
+ ### @kazupon/eslint-plugin Rules
62
+
63
+ | Rule ID | Description | Category | Fixable | RECOMMENDED |
64
+ | :----------------------------------------------------------------------------------------------------- | :---------------------------------------------- | :------- | :-----: | :---------: |
65
+ | [@kazupon/enforce-header-comment](https://eslint-plugin.kazupon.dev/rules/enforce-header-comment.html) | Enforce heading the comment in source code file | Comment | | ⭐ |
66
+
67
+ <!--RULES_TABLE_END-->
68
+
69
+ ## 🙌 Contributing guidelines
70
+
71
+ If you are interested in contributing to `gunshi`, I highly recommend checking out [the contributing guidelines](/CONTRIBUTING.md) here. You'll find all the relevant information such as [how to make a PR](/CONTRIBUTING.md#pull-request-guidelines), [how to setup development](/CONTRIBUTING.md#development-setup)) etc., there.
72
+
73
+ ## 💖 Credits
74
+
75
+ This project is inspired by:
76
+
77
+ - `README.md` and `docs/**/*.md`, inspired by [ota-meshi](https://github.com/ota-meshi)
78
+
79
+ Thank you!
80
+
81
+ ## ©️ License
82
+
83
+ [MIT](http://opensource.org/licenses/MIT)
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { ESLint } from "eslint";
2
+
3
+ //#region lib/.tsdown-types-es/index.d.ts
4
+ /** @alias */
5
+ declare const plugin: ESLint.Plugin;
6
+ /** @alias */
7
+ declare const configs: ESLint.Plugin["configs"];
8
+
9
+ //#endregion
10
+ export { configs, plugin as default, plugin };
package/lib/index.js ADDED
@@ -0,0 +1,215 @@
1
+ import { parseComment } from "@es-joy/jsdoccomment";
2
+ import fs from "node:fs";
3
+
4
+ //#region src/utils/package.ts
5
+ function readPackageJson(path) {
6
+ return JSON.parse(fs.readFileSync(path, "utf8"));
7
+ }
8
+
9
+ //#endregion
10
+ //#region src/utils/meta.ts
11
+ const pkg = readPackageJson(new URL("../../package.json", import.meta.url));
12
+ const name = pkg.name;
13
+ const version = pkg.version;
14
+ const namespace = pkg.name.split("/")[0];
15
+
16
+ //#endregion
17
+ //#region src/utils/rule.ts
18
+ const blobUrl = "https://github.com/kazupon/eslint-plugin/tree/main/src/rules";
19
+ /**
20
+ * Creates reusable function to create rules with default options and docs URLs.
21
+ *
22
+ * @param urlCreator Creates a documentation URL for a given rule name.
23
+ * @returns Function to create a rule with the docs URL format.
24
+ */
25
+ function RuleCreator(urlCreator, namespace$1 = "") {
26
+ return function createNamedRule({ meta, name: name$1,...rule$1 }) {
27
+ const ruleId = namespace$1 ? `${namespace$1}/${name$1}` : name$1;
28
+ return _createRule({
29
+ meta: {
30
+ ...meta,
31
+ docs: {
32
+ ...meta.docs,
33
+ url: urlCreator(name$1),
34
+ ruleId,
35
+ ruleName: name$1
36
+ }
37
+ },
38
+ ...rule$1
39
+ });
40
+ };
41
+ }
42
+ function _createRule({ create, defaultOptions, meta }) {
43
+ return {
44
+ create: (context) => {
45
+ const optionsWithDefault = context.options.map((options, index) => {
46
+ return {
47
+ ...defaultOptions[index],
48
+ ...options
49
+ };
50
+ });
51
+ return create(context, optionsWithDefault);
52
+ },
53
+ defaultOptions,
54
+ meta
55
+ };
56
+ }
57
+ const createRule = RuleCreator((ruleName) => {
58
+ return `${blobUrl}/${ruleName}.ts`;
59
+ }, namespace);
60
+
61
+ //#endregion
62
+ //#region src/rules/enforce-header-comment.ts
63
+ const ENFORCED_TAGS = ["author", "license"];
64
+ function initializeTagDiagnosis(tags) {
65
+ const tagDiagnosis = {};
66
+ for (const tag of tags) tagDiagnosis[tag] = "require";
67
+ return tagDiagnosis;
68
+ }
69
+ function validTagDiagnosis(tagDiagnosis) {
70
+ return Object.keys(tagDiagnosis).every((tag) => tagDiagnosis[tag] === "ok");
71
+ }
72
+ const rule = createRule({
73
+ name: "enforce-header-comment",
74
+ meta: {
75
+ type: "suggestion",
76
+ hasSuggestions: true,
77
+ docs: {
78
+ description: "Enforce heading the comment in source code file",
79
+ category: "Comment",
80
+ recommended: true,
81
+ defaultSeverity: "error"
82
+ },
83
+ messages: {
84
+ headerCommentEnforce: "Header comment is enforced",
85
+ headerCommentNeedTag: "Header comment need `@{{tag}}` tag",
86
+ headerCommentNeedTagValue: "Header `@{{tag}}` tag need a value"
87
+ },
88
+ schema: []
89
+ },
90
+ defaultOptions: [{}],
91
+ create(ctx) {
92
+ /**
93
+ * Report the tag diagnosis
94
+ * @param comment - A target comment node
95
+ * @param tags - A list of tags to check
96
+ * @param tagDiagnosis - A map of tag diagnosis
97
+ * @returns true if the comment has all required tags, false otherwise
98
+ */
99
+ function reportTagDiagnosis(comment, tags, tagDiagnosis) {
100
+ let reported = false;
101
+ for (const tag of tags) {
102
+ if (tagDiagnosis[tag] === "ok") continue;
103
+ ctx.report({
104
+ loc: comment.loc,
105
+ messageId: tagDiagnosis[tag] === "require" ? "headerCommentNeedTag" : "headerCommentNeedTagValue",
106
+ data: { tag }
107
+ });
108
+ reported = true;
109
+ }
110
+ return reported;
111
+ }
112
+ /**
113
+ * The target comments, which is able to lint
114
+ */
115
+ let taregetComments = [];
116
+ return {
117
+ Program: (node) => {
118
+ taregetComments.length = 0;
119
+ const hasCommentOnly = node.body.length === 0;
120
+ const start = hasCommentOnly ? node.range[1] : node.range[0];
121
+ const comments = ctx.sourceCode.getAllComments().filter((comment) => comment.range[1] <= start).filter((comment) => comment.type === "Block").filter((comment) => comment.value.startsWith("*"));
122
+ if (comments.length === 0) {
123
+ taregetComments = [];
124
+ return;
125
+ }
126
+ if (hasCommentOnly) taregetComments = comments;
127
+ else {
128
+ const lastComment = comments.at(-1);
129
+ const firstNode = node.body[0];
130
+ const distance = Math.abs(lastComment.range[1] - firstNode.range[0]);
131
+ taregetComments = distance > 1 ? comments : comments.slice(0, -1);
132
+ }
133
+ },
134
+ "Program:exit": (node) => {
135
+ const comments = taregetComments;
136
+ if (comments.length === 0) {
137
+ const topLoc = {
138
+ start: {
139
+ line: node.loc.start.line - 1,
140
+ column: node.loc.start.column
141
+ },
142
+ end: {
143
+ line: node.loc.end.line - 1,
144
+ column: node.loc.end.column
145
+ }
146
+ };
147
+ ctx.report({
148
+ loc: topLoc,
149
+ messageId: "headerCommentEnforce"
150
+ });
151
+ ctx.report({
152
+ loc: topLoc,
153
+ messageId: "headerCommentNeedTag",
154
+ data: { tag: "author" }
155
+ });
156
+ ctx.report({
157
+ loc: topLoc,
158
+ messageId: "headerCommentNeedTag",
159
+ data: { tag: "license" }
160
+ });
161
+ return;
162
+ }
163
+ const commentWithAstNodes = comments.map((comment) => ({
164
+ comment,
165
+ ast: parseComment(comment)
166
+ }));
167
+ let reported = false;
168
+ for (const { comment, ast } of commentWithAstNodes) {
169
+ if (reported) break;
170
+ const found = ast.tags.some((tag) => ENFORCED_TAGS.includes(tag.tag));
171
+ if (ast.tags.length > 0 && !found) continue;
172
+ const tagDiagnosis = initializeTagDiagnosis(ENFORCED_TAGS);
173
+ for (const tag of ast.tags) if (tagDiagnosis[tag.tag]) tagDiagnosis[tag.tag] = tag.description ? "ok" : "enforce";
174
+ if (validTagDiagnosis(tagDiagnosis)) break;
175
+ reported = reportTagDiagnosis(comment, ENFORCED_TAGS, tagDiagnosis);
176
+ }
177
+ }
178
+ };
179
+ }
180
+ });
181
+ var enforce_header_comment_default = rule;
182
+
183
+ //#endregion
184
+ //#region src/rules/index.ts
185
+ const rules = { "enforce-header-comment": enforce_header_comment_default };
186
+
187
+ //#endregion
188
+ //#region src/index.ts
189
+ const plugin = {
190
+ meta: {
191
+ name,
192
+ version
193
+ },
194
+ rules
195
+ };
196
+ const commentConfig = [{
197
+ name: "@kazupon/eslint-plugin/comment",
198
+ files: ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
199
+ ignores: ["**/*.md", "**/*.md/**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
200
+ plugins: { [namespace]: plugin },
201
+ rules: Object.entries(rules).reduce((rules$1, [ruleName, rule$1]) => {
202
+ const ruleId = rule$1.meta.docs?.ruleId || (namespace ? `${namespace}/${ruleName}` : ruleName);
203
+ rules$1[ruleId] = rule$1.meta.docs?.defaultSeverity || "warn";
204
+ return rules$1;
205
+ }, Object.create(null))
206
+ }];
207
+ const configs = {
208
+ recommended: [...commentConfig],
209
+ comment: commentConfig
210
+ };
211
+ plugin.configs = configs;
212
+ var src_default = plugin;
213
+
214
+ //#endregion
215
+ export { configs, src_default as default, plugin };
package/package.json ADDED
@@ -0,0 +1,130 @@
1
+ {
2
+ "name": "@kazupon/eslint-plugin",
3
+ "description": "ESLint plugin for @kazupon",
4
+ "version": "0.1.0",
5
+ "license": "MIT",
6
+ "funding": "https://github.com/sponsors/kazupon",
7
+ "bugs": {
8
+ "url": "https://github.com/kazupon/eslint-plugin/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/kazupon/eslint-plugin.git"
13
+ },
14
+ "keywords": [
15
+ "eslint",
16
+ "plugin",
17
+ "lint"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "engines": {
23
+ "node": ">= 20"
24
+ },
25
+ "type": "module",
26
+ "sideEffects": false,
27
+ "files": [
28
+ "lib"
29
+ ],
30
+ "module": "lib/index.js",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./lib/index.d.ts",
34
+ "import": "./lib/index.js",
35
+ "require": "./lib/index.js",
36
+ "default": "./lib/index.js"
37
+ },
38
+ "./package.json": "./package.json"
39
+ },
40
+ "types": "lib/index.d.ts",
41
+ "typesVersions": {
42
+ "*": {
43
+ "*": [
44
+ "./lib/*",
45
+ "./*"
46
+ ]
47
+ }
48
+ },
49
+ "dependencies": {
50
+ "@es-joy/jsdoccomment": "^0.50.0"
51
+ },
52
+ "peerDependencies": {
53
+ "eslint": "^9.0.0",
54
+ "typescript-eslint": "^8.29.1"
55
+ },
56
+ "devDependencies": {
57
+ "@eslint/markdown": "^6.3.0",
58
+ "@kazupon/eslint-config": "^0.27.0",
59
+ "@kazupon/prettier-config": "^0.1.1",
60
+ "@shikijs/vitepress-twoslash": "^3.2.2",
61
+ "@types/node": "^22.14.1",
62
+ "@typescript-eslint/utils": "^8.29.1",
63
+ "@vitest/eslint-plugin": "^1.1.42",
64
+ "bumpp": "^10.1.0",
65
+ "eslint": "^9.24.0",
66
+ "eslint-config-prettier": "^10.1.2",
67
+ "eslint-import-resolver-typescript": "^4.3.2",
68
+ "eslint-plugin-import": "^2.31.0",
69
+ "eslint-plugin-jsonc": "^2.20.0",
70
+ "eslint-plugin-module-interop": "^0.3.1",
71
+ "eslint-plugin-promise": "^7.2.1",
72
+ "eslint-plugin-unicorn": "^58.0.0",
73
+ "eslint-plugin-unused-imports": "^4.1.4",
74
+ "eslint-plugin-yml": "^1.17.0",
75
+ "eslint-vitest-rule-tester": "^2.2.0",
76
+ "gh-changelogen": "^0.2.8",
77
+ "knip": "^5.50.2",
78
+ "lint-staged": "^15.5.1",
79
+ "pkg-pr-new": "^0.0.42",
80
+ "prettier": "^3.5.3",
81
+ "publint": "^0.3.11",
82
+ "tsdown": "^0.7.5",
83
+ "tsx": "^4.19.3",
84
+ "twoslash-eslint": "^0.3.1",
85
+ "typescript": "^5.8.3",
86
+ "typescript-eslint": "^8.29.1",
87
+ "vite-plugin-eslint4b": "^0.5.1",
88
+ "vitepress": "^1.6.3",
89
+ "vitepress-plugin-group-icons": "^1.4.1",
90
+ "vitest": "^3.1.1"
91
+ },
92
+ "prettier": "@kazupon/prettier-config",
93
+ "lint-staged": {
94
+ "*.ts?(x)": [
95
+ "prettier --parser=typescript --write",
96
+ "eslint --fix"
97
+ ],
98
+ "*.{js,mjs,cjs}": [
99
+ "prettier --write",
100
+ "eslint --fix"
101
+ ],
102
+ "*.{json,jsonc,json5,md,yml,yaml}": [
103
+ "prettier --write"
104
+ ]
105
+ },
106
+ "scripts": {
107
+ "build": "tsdown",
108
+ "changelog": "gh-changelogen --repo=kazupon/eslint-plugin",
109
+ "clean": "git clean -df",
110
+ "dev": "pnpx @eslint/config-inspector --config eslint.config.ts",
111
+ "dev:eslint": "pnpx @eslint/config-inspector --config eslint.config.ts",
112
+ "docs:build": "vitepress build docs",
113
+ "docs:dev": "vitepress dev docs",
114
+ "docs:preview": "vitepress preview docs",
115
+ "docs:readme": "tsx scripts/update-readme.ts",
116
+ "docs:rules": "tsx scripts/update-docs.ts",
117
+ "fix": "pnpm run --stream --color \"/^fix:/\"",
118
+ "fix:eslint": "eslint . --fix",
119
+ "fix:knip": "knip --fix --no-exit-code",
120
+ "fix:prettier": "prettier . --write",
121
+ "lint": "pnpm run --stream --color \"/^lint:/\"",
122
+ "lint:eslint": "eslint .",
123
+ "lint:knip": "knip",
124
+ "lint:prettier": "prettier . --check",
125
+ "release": "bumpp --commit \"release: v%s\" --all --push --tag",
126
+ "test": "vitest run",
127
+ "typecheck": "pnpm run --stream --color \"/^typecheck:/\"",
128
+ "typecheck:tsc": "tsc --noEmit"
129
+ }
130
+ }