@studiometa/eslint-plugin-js-toolkit 3.9.0 → 4.0.0-alpha.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/README.md +78 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2061 -1537
- package/dist/index.js.map +1 -7
- package/package.json +7 -7
- package/dist/rules/async-lifecycle-methods.d.ts +0 -7
- package/dist/rules/components-pascal-case.d.ts +0 -8
- package/dist/rules/emits-kebab-case.d.ts +0 -8
- package/dist/rules/emits-multi-word.d.ts +0 -8
- package/dist/rules/index.d.ts +0 -30
- package/dist/rules/no-create-app.d.ts +0 -7
- package/dist/rules/no-deep-utils-import.d.ts +0 -7
- package/dist/rules/no-deprecated-properties.d.ts +0 -7
- package/dist/rules/no-dispatch-event.d.ts +0 -7
- package/dist/rules/no-dollar-prefix.d.ts +0 -8
- package/dist/rules/no-event-listener-methods.d.ts +0 -7
- package/dist/rules/no-internal-barrel-import.d.ts +0 -7
- package/dist/rules/no-manual-intersection-observer.d.ts +0 -7
- package/dist/rules/no-manual-mutation-observer.d.ts +0 -7
- package/dist/rules/no-redundant-with-mount-when-in-view.d.ts +0 -7
- package/dist/rules/no-shadow-dom.d.ts +0 -7
- package/dist/rules/on-global-handler-prefix.d.ts +0 -7
- package/dist/rules/on-handler-naming.d.ts +0 -7
- package/dist/rules/options-camel-case.d.ts +0 -8
- package/dist/rules/prefer-destructured-lookups.d.ts +0 -8
- package/dist/rules/prefer-ref-over-query-selector.d.ts +0 -7
- package/dist/rules/refs-camel-case.d.ts +0 -8
- package/dist/rules/refs-no-bracket-access.d.ts +0 -7
- package/dist/rules/refs-plural-multiple.d.ts +0 -8
- package/dist/rules/require-children-declared-in-config.d.ts +0 -7
- package/dist/rules/require-config-name-pascal-case.d.ts +0 -8
- package/dist/rules/require-config.d.ts +0 -8
- package/dist/rules/require-destroyed-cleanup.d.ts +0 -12
- package/dist/rules/require-emit-declared-in-config.d.ts +0 -7
- package/dist/rules/require-options-declared-in-config.d.ts +0 -7
- package/dist/rules/require-refs-declared-in-config.d.ts +0 -7
- package/dist/utils/ast.d.ts +0 -51
- package/dist/utils/rule-tester.d.ts +0 -6
package/README.md
CHANGED
|
@@ -57,6 +57,39 @@ Add the plugin to your `.oxlintrc.json`:
|
|
|
57
57
|
}
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### v4
|
|
61
|
+
|
|
62
|
+
v4 is not v3 with a bigger version number, so it has its own rule set. `configs.v4` replaces `configs.recommended` rather than extending it: the rules policing something v4 removed — `config.emits`, `$children`, the v3 decorator names — are absent, and `async-lifecycle-methods` is absent because a v4 frame hook returns the function which writes rather than a promise.
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { jsToolkit } from '@studiometa/eslint-plugin-js-toolkit';
|
|
66
|
+
|
|
67
|
+
export default [jsToolkit.configs.v4];
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The same set in `.oxlintrc.json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"jsPlugins": [{ "name": "js-toolkit", "specifier": "@studiometa/eslint-plugin-js-toolkit" }],
|
|
75
|
+
"rules": {
|
|
76
|
+
"js-toolkit/no-write-in-read-phase": "error",
|
|
77
|
+
"js-toolkit/no-options-assignment": "error",
|
|
78
|
+
"js-toolkit/prefer-instance-scheduler": "warn",
|
|
79
|
+
"js-toolkit/option-default-factory": "error",
|
|
80
|
+
"js-toolkit/no-conflicting-negated-option": "error",
|
|
81
|
+
"js-toolkit/no-destroy-lifecycle": "error",
|
|
82
|
+
"js-toolkit/no-deprecated-properties": ["error", { "version": "v4" }],
|
|
83
|
+
"js-toolkit/no-dispatch-event": "warn",
|
|
84
|
+
"js-toolkit/no-shadow-dom": "error",
|
|
85
|
+
"js-toolkit/no-event-listener-methods": "error",
|
|
86
|
+
"js-toolkit/no-manual-mutation-observer": "warn",
|
|
87
|
+
"js-toolkit/refs-no-bracket-access": "error",
|
|
88
|
+
"js-toolkit/prefer-ref-over-query-selector": "warn"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
60
93
|
### ESLint
|
|
61
94
|
|
|
62
95
|
Add the recommended config to your `eslint.config.js` (ESLint v9 flat config):
|
|
@@ -114,7 +147,7 @@ export default [
|
|
|
114
147
|
|
|
115
148
|
| Rule | Description | Recommended | Fixable |
|
|
116
149
|
| ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- |
|
|
117
|
-
| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead.
|
|
150
|
+
| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. With `{ "version": "v4" }`, reports the members v4 removed instead — see below. | warn | |
|
|
118
151
|
| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | warn | |
|
|
119
152
|
| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | error | |
|
|
120
153
|
| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | warn | |
|
|
@@ -156,3 +189,47 @@ export default [
|
|
|
156
189
|
| `js-toolkit/prefer-destructured-lookups` | Warns when `this.$refs`, `this.$options` or `this.$children` members are accessed more than once in the same method. Destructure into a local variable instead. | warn | |
|
|
157
190
|
| `js-toolkit/no-dollar-prefix` | Disallows user-defined instance methods and properties prefixed with `$` in `Base` subclasses. The `$` prefix is reserved for framework-provided members. | error | |
|
|
158
191
|
| `js-toolkit/require-destroyed-cleanup` | Requires a `destroyed()` method in `Base` subclasses that call `setTimeout`, `setInterval` or `requestAnimationFrame`, to clear timers and prevent memory leaks. | warn | |
|
|
192
|
+
|
|
193
|
+
### v4
|
|
194
|
+
|
|
195
|
+
These rules describe v4 only, and ship in `configs.v4` rather than in `configs.recommended`.
|
|
196
|
+
|
|
197
|
+
| Rule | Description | v4 | Fixable |
|
|
198
|
+
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------- |
|
|
199
|
+
| `js-toolkit/no-write-in-read-phase` | Disallows a DOM write in a read-phase callback — a frame hook, a subscriber of a read-phase service, or a `$read()` task. Return a render function, or write from `$write()`. | error | |
|
|
200
|
+
| `js-toolkit/no-options-assignment` | Disallows writing to `this.$options`. Every option is a getter with no setter, so the assignment throws at runtime; this moves the failure to CI. | error | |
|
|
201
|
+
| `js-toolkit/prefer-instance-scheduler` | Warns on `defaultScheduler.read()` / `.write()` inside a component. `this.$read()` and `this.$write()` tie the task to the instance, so destroying it cancels the task. | warn | |
|
|
202
|
+
| `js-toolkit/option-default-factory` | Disallows a literal object or array as an option `default` — every instance would share it. Core warns at runtime, but only once a component mounts. | error | |
|
|
203
|
+
| `js-toolkit/no-conflicting-negated-option` | Disallows declaring both a boolean option `x` and an option named `noX`, which would make `data-option-no-x` mean two things. Core deliberately does not check this on every mount. | error | |
|
|
204
|
+
| `js-toolkit/no-deprecated-properties` | With `{ "version": "v4" }`: reports `$parent`, `$root`, `$children`, `$update`, `$warn`, `$log`, `$terminate`, `$services.enable()`/`.disable()`, `updated()`, `terminated()`, `config.emits`. | error | |
|
|
205
|
+
| `js-toolkit/no-destroy-lifecycle` | Renames `$destroy()` to `$unmount()` and the `destroyed()` hook to `unmounted()` — the v4 names. See below. | error | 🔧 |
|
|
206
|
+
|
|
207
|
+
#### `no-destroy-lifecycle`
|
|
208
|
+
|
|
209
|
+
v4 has no destroyed state. `$mount()` and `$unmount()` are one boolean with two values, and the method that used to be called `$destroy()` documented itself as the reversible inverse of `$mount()`. `$mount()`/`$unmount()` and `mounted()`/`unmounted()` are symmetric pairs where `$mount()`/`$destroy()` was not. Vue 3 made the same rename to `beforeDestroy`/`destroyed`.
|
|
210
|
+
|
|
211
|
+
The rename is not an entry in `no-deprecated-properties`, for three reasons. That rule only flags a member expression whose object is `this`, deliberately, so that `someLibrary.$parent` is not noise — but most `$destroy()` calls are `instance.$destroy()`, so folding the rename in would miss the common case, and lifting the guard for one entry would lift it for all of them. That rule is also not fixable and should not become so: `$parent` → `$closest()` is not a safe textual rewrite, while a rename is exactly the case a fixer fits. And the two rules say different things — `no-deprecated-properties` reports v3 names v4 dropped, whereas `$destroy` is v4's own name, renamed while v4 is unreleased.
|
|
212
|
+
|
|
213
|
+
The two halves are asymmetric, because the two names are.
|
|
214
|
+
|
|
215
|
+
`$destroy` is flagged on **any** receiver — `this.$destroy()`, `instance.$destroy()`, `super.$destroy()` — and on a `$destroy()` method definition, which is what a service mixin overriding the framework's teardown writes. The `$` prefix is the toolkit's own namespace, and the rule ships in `configs.v4` only; that opt-in is the guard against rewriting an unrelated library's API.
|
|
216
|
+
|
|
217
|
+
`destroyed` has no prefix to lean on — `emitter.destroyed`, `record.destroyed` and a plain `destroyed` boolean are ordinary code — so that half is narrowed to the two places where it can only be the hook: a non-static `destroyed()` method definition in a class that reads as a v4 component, and a `super.destroyed()` call inside one. A bare `foo.destroyed()` is left alone.
|
|
218
|
+
|
|
219
|
+
Computed access (`instance['$destroy']()`) is not flagged: it is rare, and the fixer would have to guess the quote style of a string it did not write.
|
|
220
|
+
|
|
221
|
+
#### `no-write-in-read-phase`
|
|
222
|
+
|
|
223
|
+
`useRaf()`, `useScroll()` and `useScrollProgress()` fan their subscribers out inside `defaultScheduler.read()`, so that every measurement of a frame precedes every write of that frame. A callback which measures **and** writes defeats the split: the write forces layout for every component still measuring. The contract is that a callback _returns_ the function which mutates.
|
|
224
|
+
|
|
225
|
+
A read-phase context is a `ticked()`, `scrolled()` or `scrolledInView()` method, a `subscribe()` callback of one of those services or of a `smoothTo()` value, or a `$read()` / `defaultScheduler.read()` task. A DOM write is an assignment to `style.*`, `textContent`, `innerHTML`, `className`, `scrollTop`, `scrollLeft` or `value`, or a call to `setProperty`, `setAttribute` and friends, `classList.*`, `append`/`prepend`/`remove`/`replaceChildren`/`insertAdjacentHTML`, `scrollTo`/`scrollIntoView` or `animate`. The receiver has to reach the DOM from `this.$el`, `this.$refs.*`, `document`, `window` or a local initialised from one of those, which is what makes a rule with no type information accurate enough to turn on.
|
|
226
|
+
|
|
227
|
+
Two mechanisms remove the false positives. The armed state clears at **any** nested function boundary, which is why a returned render function and a `$write()` callback are correct by construction. And a `this.<name>(…)` call is resolved **one** level, into that method, unless it carries `@read` or `@write` — so a helper which only mutates is caught, while `@write render()` is not.
|
|
228
|
+
|
|
229
|
+
#### What counts as a component
|
|
230
|
+
|
|
231
|
+
These rules have to recognise a v4 component with no type information and without leaving the file. A component always extends something — `Base`, a `with<Service>(…)` mixin, or a local abstract component — so a superclass is necessary, and nowhere near sufficient: `class Store extends Error` must not be told to call a `this.$read()` it does not have.
|
|
232
|
+
|
|
233
|
+
So the test is a superclass **plus** a second signal in the same class body: a `@component({ … })` decorator, a `static config`, or use of the framework surface off `this` (`$el`, `$refs`, `$options`, `$emit`, `$read`, `$write`, `$services` and the rest). Lifecycle and service hook names are deliberately excluded from that surface — they are what `no-write-in-read-phase` keys on, so counting them would make the test circular.
|
|
234
|
+
|
|
235
|
+
This keeps a subclass of a local abstract component, whose base is in another file, and it keeps `Base` itself out: `Base` declares `static config` and defines the whole surface, but it extends nothing. A component using none of the three signals anywhere in its body is missed, which is the right way to be wrong — a false negative rather than advice that does not apply.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
1
2
|
declare const plugin: import("@oxlint/plugins").Plugin & {
|
|
2
|
-
|
|
3
|
+
configs: Record<string, object>;
|
|
3
4
|
};
|
|
4
|
-
|
|
5
|
-
export default plugin;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { plugin as default, plugin as jsToolkit };
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|