Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format

## [Unreleased]

### Added

- **ESLint Plugin:** add 6 new rules for emits, components and options ([#728](https://github.com/studiometa/js-toolkit/pull/728), [4a7d657f](https://github.com/studiometa/js-toolkit/commit/4a7d657f))

## [v3.6.0-beta.1](https://github.com/studiometa/js-toolkit/compare/3.5.0..3.6.0-beta.0) (2026-05-07)

### Added
Expand Down
198 changes: 175 additions & 23 deletions packages/docs/guide/going-further/linting.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,28 @@ Add the plugin to your `.oxlintrc.json`:
"js-toolkit/require-config-name-pascal-case": "error",
"js-toolkit/refs-camel-case": "error",
"js-toolkit/refs-plural-multiple": "error",
"js-toolkit/refs-no-bracket-access": "error",
"js-toolkit/require-refs-declared-in-config": "error",
"js-toolkit/options-camel-case": "error",
"js-toolkit/require-options-declared-in-config": "error",
"js-toolkit/async-lifecycle-methods": "error",
"js-toolkit/on-handler-naming": "error",
"js-toolkit/on-global-handler-prefix": "warn",
"js-toolkit/no-deprecated-properties": "error",
"js-toolkit/emits-kebab-case": "error",
"js-toolkit/emits-multi-word": "error",
"js-toolkit/require-emit-declared-in-config": "error",
"js-toolkit/components-pascal-case": "error",
"js-toolkit/require-children-declared-in-config": "error",
"js-toolkit/no-deprecated-properties": "warn",
"js-toolkit/no-dispatch-event": "warn",
"js-toolkit/no-shadow-dom": "error",
"js-toolkit/no-create-app": "warn",
"js-toolkit/no-event-listener-methods": "error"
"js-toolkit/no-event-listener-methods": "error",
"js-toolkit/no-deep-utils-import": "error",
"js-toolkit/no-redundant-with-mount-when-in-view": "warn",
"js-toolkit/no-manual-intersection-observer": "warn",
"js-toolkit/no-manual-mutation-observer": "warn",
"js-toolkit/prefer-ref-over-query-selector": "warn"
}
}
```
Expand Down Expand Up @@ -72,33 +85,172 @@ export default [

### Class structure

| Rule | Description | Fixable |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- |
| <span class="text-nowrap">`js-toolkit/require-config`</span> | Requires a `static config` property with a `name` on every class extending `Base`. | ❌ |
| <span class="text-nowrap">`js-toolkit/require-config-name-pascal-case`</span> | Requires `config.name` to be PascalCase. | πŸ”§ |
| <span class="text-nowrap">`js-toolkit/refs-camel-case`</span> | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | πŸ”§ |
| <span class="text-nowrap">`js-toolkit/refs-plural-multiple`</span> | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | ❌ |
| <span class="text-nowrap">`js-toolkit/options-camel-case`</span> | Requires option keys in `config.options` to be camelCase. | πŸ”§ |
#### `js-toolkit/require-config`

**Recommended:** error

Requires a `static config` property with a `name` on every class extending `Base`.

#### `js-toolkit/require-config-name-pascal-case`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires `config.name` to be PascalCase.

### Refs

#### `js-toolkit/refs-camel-case`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix.

#### `js-toolkit/refs-plural-multiple`

**Recommended:** error

Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`).

#### `js-toolkit/refs-no-bracket-access`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Disallows bracket access with a `[]` suffix on `this.$refs` (e.g. `this.$refs['items[]']`). Rewrites to dot notation camelCase.

#### `js-toolkit/require-refs-declared-in-config`

**Recommended:** error

Requires all `this.$refs.<name>` accesses to be declared in `static config.refs`.

#### `js-toolkit/prefer-ref-over-query-selector`

**Recommended:** warn

Warns when `this.$el.querySelector()` or `this.$el.querySelectorAll()` is used inside a `Base` subclass. Declare a ref in `static config` and use `this.$refs` instead.

### Options

#### `js-toolkit/options-camel-case`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires option keys in `config.options` to be camelCase.

#### `js-toolkit/require-options-declared-in-config`

**Recommended:** error

Requires all `this.$options.<name>` accesses to be declared in `static config.options`.

### Emits

#### `js-toolkit/emits-kebab-case`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`).

#### `js-toolkit/emits-multi-word`

**Recommended:** error

Requires emit names in `config.emits` to be multi-word to avoid collisions with native DOM events (e.g. `item-click` not `click`).

#### `js-toolkit/require-emit-declared-in-config`

**Recommended:** error

Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`.

### Components

#### `js-toolkit/components-pascal-case`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires component keys in `config.components` to be PascalCase.

#### `js-toolkit/require-children-declared-in-config`

**Recommended:** error

Requires all `this.$children.<Name>` accesses to be declared in `static config.components`.

### Lifecycle methods

| Rule | Description | Fixable |
| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| <span class="text-nowrap">`js-toolkit/async-lifecycle-methods`</span> | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | πŸ”§ |
#### `js-toolkit/async-lifecycle-methods`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`.

### Event handlers

| Rule | Description | Fixable |
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- |
| <span class="text-nowrap">`js-toolkit/on-handler-naming`</span> | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | ❌ |
| <span class="text-nowrap">`js-toolkit/on-global-handler-prefix`</span> | Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix. | ❌ |
#### `js-toolkit/on-handler-naming`

**Recommended:** error

Requires event handler methods to follow the `onXxxYyy` camelCase convention.

#### `js-toolkit/on-global-handler-prefix`

**Recommended:** warn

Requires handlers for window-only events (e.g. `resize`) to use the `onWindow` or `onDocument` prefix.

### Forbidden patterns

| Rule | Description | Fixable |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| <span class="text-nowrap">`js-toolkit/no-deprecated-properties`</span> | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | ❌ |
| <span class="text-nowrap">`js-toolkit/no-dispatch-event`</span> | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | ❌ |
| <span class="text-nowrap">`js-toolkit/no-shadow-dom`</span> | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | ❌ |
| <span class="text-nowrap">`js-toolkit/no-create-app`</span> | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | ❌ |
| <span class="text-nowrap">`js-toolkit/no-event-listener-methods`</span> | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead β€” the framework handles binding and cleanup automatically. | ❌ |
#### `js-toolkit/no-deprecated-properties`

**Recommended:** warn

Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead.

#### `js-toolkit/no-dispatch-event`

**Recommended:** warn

Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead.

#### `js-toolkit/no-shadow-dom`

**Recommended:** error

Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only.

#### `js-toolkit/no-create-app`

**Recommended:** warn

Disallows `createApp()` (deprecated). Use `registerComponent()` instead.

#### `js-toolkit/no-event-listener-methods`

**Recommended:** error

Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead β€” the framework handles binding and cleanup automatically.

#### `js-toolkit/no-deep-utils-import`

**Recommended:** error &nbsp;Β·&nbsp; **Fixable** πŸ”§

Disallows deep imports from `@studiometa/js-toolkit/utils/*`. Use the public entrypoint `@studiometa/js-toolkit/utils` instead.

#### `js-toolkit/no-redundant-with-mount-when-in-view`

**Recommended:** warn

Disallows wrapping `withMountWhenInView` inside `withScrolledInView` β€” the latter already includes the former internally.

#### `js-toolkit/no-manual-intersection-observer`

**Recommended:** warn

Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead.

#### `js-toolkit/no-manual-mutation-observer`

**Recommended:** warn

Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead.
2 changes: 1 addition & 1 deletion packages/docs/utils/storage/createLocalStorage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Create a storage instance backed by `globalThis.localStorage`. The underlying st
## Usage

```js twoslash
// @twoslash-cache: {"v":1,"hash":"088a272689b725269922c7afba5cb8bac42148bc848708f323c0939c2712e2d5","data":"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="}
// @twoslash-cache: {"v":1,"hash":"168444ac507ca8fe05e8951c976264031db52ea65923eec4212be04d143bde92","data":"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="}
import { createLocalStorage } from '@studiometa/js-toolkit/utils';

const storage = createLocalStorage({ prefix: 'myapp:' });
Expand Down
Loading
Loading