@simbiat/eslint-plugin-simbiat 1.0.1 → 1.0.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.
Files changed (3) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +144 -144
  3. package/package.json +51 -51
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Dmitrii Kustov
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, 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,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dmitrii Kustov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, 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,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,145 +1,145 @@
1
- # eslint-plugin-simbiat
2
-
3
- Custom ESLint rules that are used in the [simbiat.eu](https://github.com/Simbiat/simbiat.ru) project. Created with the use of Claude AI (I am realistically not that proficient) but manually reviewed, adjusted, and tested on the existing codebase.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install --save-dev @simbiat/eslint-plugin-simbiat
9
- ```
10
-
11
- Requires ESLint
12
- `>=9.38.0`, [flat config](https://eslint.org/docs/latest/use/configure/configuration-files), and [ESM](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm).
13
-
14
- ---
15
-
16
- ## Usage (flat config)
17
-
18
- ```js
19
- // eslint.config.js
20
- import simbiat from '@simbiat/eslint-plugin-simbiat';
21
-
22
- export default [
23
- {
24
- plugins: { simbiat },
25
- rules: {
26
- 'simbiat/no-forbidden-in-constructor': 'error',
27
- 'simbiat/no-external-listeners-in-constructor': 'warn',
28
- 'simbiat/prefer-field-initializer': 'warn',
29
- 'simbiat/require-type-parameter': 'warn',
30
- 'simbiat/require-super-first-in-constructor': 'error',
31
- 'simbiat/no-keypress-event': 'warn',
32
- },
33
- },
34
- ];
35
- ```
36
-
37
- ---
38
-
39
- ## Rules
40
-
41
- ### `simbiat/no-forbidden-in-constructor` - *problem*
42
-
43
- Flags things the [Custom Elements spec](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance)
44
- forbids in a constructor and in class field definitions:
45
-
46
- | Category | Members |
47
- |------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
48
- | Attribute manipulation | `this.setAttribute()`, `this.toggleAttribute()`, creation/update attributes like `this.id`, `this.className`, `this.tabIndex`, etc., that are not class fields. |
49
- | Child-element access | `this.children`, `this.childNodes`, `this.firstChild`, `this.lastChild`, `this.firstElementChild`, `this.lastElementChild`, `this.childElementCount` |
50
- | Child-element queries | `this.querySelector()`, `this.querySelectorAll()`, `this.getElementsByTagName()`, `this.getElementsByClassName()`, `this.getElementsByName()` |
51
- | Content mutation | `this.innerHTML`, `this.outerHTML`, `this.textContent`, `this.innerText`, `this.appendChild()`, `this.insertBefore`, `this.replaceChild`, `this.removeChild()`, `this.append()`, `this.prepend()`, `this.replaceChildren()`, `this.insertAdjacentHTML()`, `this.insertAdjacentElement()`, `this.insertAdjacentText()`, `this.after()`, `this.before()`, `this.replaceWith()`, `this.remove()` |
52
- | Global calls | `document.write()`, `document.open()` |
53
- | Illegal return | Any `return <expr>;` that is not a bare `return;` or `return this;` |
54
-
55
- Not flagged: `this.shadowRoot.*`, `this.attachShadow()`, `this.getAttribute()`,
56
- `this.removeAttribute()`, `appendChild` on shadow-root elements, event
57
- listeners on shadow-scoped elements.
58
-
59
- **Options**
60
-
61
- ```js
62
- 'simbiat/no-forbidden-in-constructor': ['error', {
63
- baseClasses: ['HTMLElement', 'LitElement', 'BaseComponent'],
64
- }]
65
- ```
66
-
67
- `baseClasses` defaults to `['HTMLElement']`.
68
-
69
- ---
70
-
71
- ### `simbiat/no-external-listeners-in-constructor` - *suggestion*
72
-
73
- Flags `addEventListener` calls on `document`, `window`, `document.body`,
74
- `document.documentElement`, or `document.head` that appear **directly** in
75
- the constructor body (not inside a nested callback or arrow function).
76
-
77
- These listeners belong in `connectedCallback`, paired with removal in
78
- `disconnectedCallback`; otherwise they leak when the element is moved or
79
- re-inserted into the DOM.
80
-
81
- **Options** - same `baseClasses` schema as above.
82
-
83
- ---
84
-
85
- ### `simbiat/require-super-first-in-constructor` - *problem*
86
-
87
- Flags constructors, that do not start with empty
88
- `super();`. This is normally flagged by TypeScript but may not be flagged in pure JavaScript.
89
-
90
- **Options** - same `baseClasses` schema as above.
91
-
92
- ---
93
-
94
- ### `simbiat/require-listener-cleanup` - *suggestion*
95
-
96
- Verifies that every `addEventListener` call on an external target inside
97
- `connectedCallback` of an HTMLElement subclass has a matching `removeEventListener` call in `disconnectedCallback`. If the handler in `addEventListener` is a proper class field already, it can be auto-fixed (respective `removeEventListener` will be added).
98
-
99
- **Options** - same `baseClasses` schema as above.
100
-
101
- ---
102
-
103
- ### `simbiat/prefer-field-initializer` - *suggestion*
104
-
105
- Flags `this.x = expr` assignments in a constructor when all the following hold:
106
-
107
- 1. `x` already has a class field declaration (`PropertyDefinition`).
108
- 2. The RHS does **not** reference a constructor parameter by name.
109
- 3. The RHS does **not** contain `this.anything` (field-initializer vs.
110
- constructor-assignment ordering can differ subtly).
111
-
112
- Only top-level assignments in the constructor body are checked. Assignments
113
- inside `if`/`for`/`while` blocks, ternaries, or nested functions are
114
- intentionally ignored.
115
-
116
- > **Known limitation:** references to local variables defined earlier in the
117
- > constructor (not parameters) are not detected and may produce false
118
- > positives. Suppress with `// eslint-disable-next-line` where needed.
119
-
120
- No auto-fix: the change requires removing the assignment *and* updating the
121
- field declaration simultaneously.
122
-
123
- ---
124
-
125
- ### `simbiat/require-type-parameter` - *suggestion*
126
-
127
- Flags `querySelector`, `querySelectorAll` and `closest` calls in TypeScript (`.ts` /
128
- `.tsx`) files that lack a type parameter.
129
-
130
- ```ts
131
- // ✗ flagged
132
- const element = document.querySelector('.link');
133
-
134
- // ✓ OK
135
- const element = document.querySelector<HTMLAnchorElement>('.link');
136
- ```
137
-
138
- JS files are left alone. No auto-fix: the correct type depends on the
139
- selector and must be supplied by the developer.
140
-
141
- ---
142
-
143
- ### `simbiat/no-keypress-event` - *suggestion*
144
-
1
+ # eslint-plugin-simbiat
2
+
3
+ Custom ESLint rules that are used in the [simbiat.eu](https://github.com/Simbiat/simbiat.ru) project. Created with the use of Claude AI (I am realistically not that proficient) but manually reviewed, adjusted, and tested on the existing codebase.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev @simbiat/eslint-plugin-simbiat
9
+ ```
10
+
11
+ Requires ESLint
12
+ `>=9.38.0`, [flat config](https://eslint.org/docs/latest/use/configure/configuration-files), and [ESM](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm).
13
+
14
+ ---
15
+
16
+ ## Usage (flat config)
17
+
18
+ ```js
19
+ // eslint.config.js
20
+ import simbiat from '@simbiat/eslint-plugin-simbiat';
21
+
22
+ export default [
23
+ {
24
+ plugins: { simbiat },
25
+ rules: {
26
+ 'simbiat/no-forbidden-in-constructor': 'error',
27
+ 'simbiat/no-external-listeners-in-constructor': 'warn',
28
+ 'simbiat/prefer-field-initializer': 'warn',
29
+ 'simbiat/require-type-parameter': 'warn',
30
+ 'simbiat/require-super-first-in-constructor': 'error',
31
+ 'simbiat/no-keypress-event': 'warn',
32
+ },
33
+ },
34
+ ];
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Rules
40
+
41
+ ### `simbiat/no-forbidden-in-constructor` - *problem*
42
+
43
+ Flags things the [Custom Elements spec](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance)
44
+ forbids in a constructor and in class field definitions:
45
+
46
+ | Category | Members |
47
+ |------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
48
+ | Attribute manipulation | `this.setAttribute()`, `this.toggleAttribute()`, creation/update attributes like `this.id`, `this.className`, `this.tabIndex`, etc., that are not class fields. |
49
+ | Child-element access | `this.children`, `this.childNodes`, `this.firstChild`, `this.lastChild`, `this.firstElementChild`, `this.lastElementChild`, `this.childElementCount` |
50
+ | Child-element queries | `this.querySelector()`, `this.querySelectorAll()`, `this.getElementsByTagName()`, `this.getElementsByClassName()`, `this.getElementsByName()` |
51
+ | Content mutation | `this.innerHTML`, `this.outerHTML`, `this.textContent`, `this.innerText`, `this.appendChild()`, `this.insertBefore`, `this.replaceChild`, `this.removeChild()`, `this.append()`, `this.prepend()`, `this.replaceChildren()`, `this.insertAdjacentHTML()`, `this.insertAdjacentElement()`, `this.insertAdjacentText()`, `this.after()`, `this.before()`, `this.replaceWith()`, `this.remove()` |
52
+ | Global calls | `document.write()`, `document.open()` |
53
+ | Illegal return | Any `return <expr>;` that is not a bare `return;` or `return this;` |
54
+
55
+ Not flagged: `this.shadowRoot.*`, `this.attachShadow()`, `this.getAttribute()`,
56
+ `this.removeAttribute()`, `appendChild` on shadow-root elements, event
57
+ listeners on shadow-scoped elements.
58
+
59
+ **Options**
60
+
61
+ ```js
62
+ 'simbiat/no-forbidden-in-constructor': ['error', {
63
+ baseClasses: ['HTMLElement', 'LitElement', 'BaseComponent'],
64
+ }]
65
+ ```
66
+
67
+ `baseClasses` defaults to `['HTMLElement']`.
68
+
69
+ ---
70
+
71
+ ### `simbiat/no-external-listeners-in-constructor` - *suggestion*
72
+
73
+ Flags `addEventListener` calls on `document`, `window`, `document.body`,
74
+ `document.documentElement`, or `document.head` that appear **directly** in
75
+ the constructor body (not inside a nested callback or arrow function).
76
+
77
+ These listeners belong in `connectedCallback`, paired with removal in
78
+ `disconnectedCallback`; otherwise they leak when the element is moved or
79
+ re-inserted into the DOM.
80
+
81
+ **Options** - same `baseClasses` schema as above.
82
+
83
+ ---
84
+
85
+ ### `simbiat/require-super-first-in-constructor` - *problem*
86
+
87
+ Flags constructors, that do not start with empty
88
+ `super();`. This is normally flagged by TypeScript but may not be flagged in pure JavaScript.
89
+
90
+ **Options** - same `baseClasses` schema as above.
91
+
92
+ ---
93
+
94
+ ### `simbiat/require-listener-cleanup` - *suggestion*
95
+
96
+ Verifies that every `addEventListener` call on an external target inside
97
+ `connectedCallback` of an HTMLElement subclass has a matching `removeEventListener` call in `disconnectedCallback`. If the handler in `addEventListener` is a proper class field already, it can be auto-fixed (respective `removeEventListener` will be added).
98
+
99
+ **Options** - same `baseClasses` schema as above.
100
+
101
+ ---
102
+
103
+ ### `simbiat/prefer-field-initializer` - *suggestion*
104
+
105
+ Flags `this.x = expr` assignments in a constructor when all the following hold:
106
+
107
+ 1. `x` already has a class field declaration (`PropertyDefinition`).
108
+ 2. The RHS does **not** reference a constructor parameter by name.
109
+ 3. The RHS does **not** contain `this.anything` (field-initializer vs.
110
+ constructor-assignment ordering can differ subtly).
111
+
112
+ Only top-level assignments in the constructor body are checked. Assignments
113
+ inside `if`/`for`/`while` blocks, ternaries, or nested functions are
114
+ intentionally ignored.
115
+
116
+ > **Known limitation:** references to local variables defined earlier in the
117
+ > constructor (not parameters) are not detected and may produce false
118
+ > positives. Suppress with `// eslint-disable-next-line` where needed.
119
+
120
+ No auto-fix: the change requires removing the assignment *and* updating the
121
+ field declaration simultaneously.
122
+
123
+ ---
124
+
125
+ ### `simbiat/require-type-parameter` - *suggestion*
126
+
127
+ Flags `querySelector`, `querySelectorAll` and `closest` calls in TypeScript (`.ts` /
128
+ `.tsx`) files that lack a type parameter.
129
+
130
+ ```ts
131
+ // ✗ flagged
132
+ const element = document.querySelector('.link');
133
+
134
+ // ✓ OK
135
+ const element = document.querySelector<HTMLAnchorElement>('.link');
136
+ ```
137
+
138
+ JS files are left alone. No auto-fix: the correct type depends on the
139
+ selector and must be supplied by the developer.
140
+
141
+ ---
142
+
143
+ ### `simbiat/no-keypress-event` - *suggestion*
144
+
145
145
  Flags use of `keypress` event, since it is deprecated, and `keydown` is recommended as replacement (or `beforeinput` in some cases). Provides autofix except for `removeEventListener`, because these require a complete match, and if the listener being removed is third party, it will not work as expected. Thus, manual intervention is recommended here.
package/package.json CHANGED
@@ -1,51 +1,51 @@
1
- {
2
- "name": "@simbiat/eslint-plugin-simbiat",
3
- "version": "1.0.1",
4
- "description": "Custom ESLint rules used in simbiat.eu project: class-field initializers, custom-element constructor constraints, and typed querySelector.",
5
- "keywords": [
6
- "eslint",
7
- "eslintplugin ",
8
- "eslint-plugin",
9
- "custom-elements",
10
- "typescript",
11
- "class-fields"
12
- ],
13
- "bugs": {
14
- "url": "https://github.com/simbiat/eslint-plugin-simbiat/issues"
15
- },
16
- "repository": {
17
- "type": "git",
18
- "url": "git+https://github.com/simbiat/eslint-plugin-simbiat.git"
19
- },
20
- "license": "MIT",
21
- "author": {
22
- "name": "Dmitrii Kustov",
23
- "email": "upport@simbiat.eu",
24
- "url": "https://www.simbiat.eu"
25
- },
26
- "sideEffects": false,
27
- "type": "module",
28
- "exports": {
29
- ".": {
30
- "default": "./dist/Plugin.mjs",
31
- "import": "./dist/Plugin.mjs",
32
- "types": "./dist/Plugin.d.mts"
33
- },
34
- "./package.json": "./package.json"
35
- },
36
- "files": [
37
- "dist"
38
- ],
39
- "scripts": {
40
- "prepare": "tsc"
41
- },
42
- "devDependencies": {
43
- "eslint": ">=9.38.0"
44
- },
45
- "peerDependencies": {
46
- "eslint": ">=9.38.0"
47
- },
48
- "engines": {
49
- "node": "^20.10.0 || >=21.0.0"
50
- }
51
- }
1
+ {
2
+ "name": "@simbiat/eslint-plugin-simbiat",
3
+ "version": "1.0.2",
4
+ "description": "Custom ESLint rules used in simbiat.eu project: class-field initializers, custom-element constructor constraints, and typed querySelector.",
5
+ "keywords": [
6
+ "eslint",
7
+ "eslintplugin",
8
+ "eslint-plugin",
9
+ "custom-elements",
10
+ "typescript",
11
+ "class-fields"
12
+ ],
13
+ "bugs": {
14
+ "url": "https://github.com/simbiat/eslint-plugin-simbiat/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/simbiat/eslint-plugin-simbiat.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": {
22
+ "name": "Dmitrii Kustov",
23
+ "email": "support@simbiat.eu",
24
+ "url": "https://www.simbiat.eu"
25
+ },
26
+ "sideEffects": false,
27
+ "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "default": "./dist/Plugin.mjs",
31
+ "import": "./dist/Plugin.mjs",
32
+ "types": "./dist/Plugin.d.mts"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "scripts": {
40
+ "prepare": "tsc"
41
+ },
42
+ "devDependencies": {
43
+ "eslint": ">=9.38.0"
44
+ },
45
+ "peerDependencies": {
46
+ "eslint": ">=9.38.0"
47
+ },
48
+ "engines": {
49
+ "node": "^20.10.0 || >=21.0.0"
50
+ }
51
+ }