From 4a7d657fd7fe5908802fbfedd1129152e766e892 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Tue, 12 May 2026 11:25:15 +0200 Subject: [PATCH 1/5] Add 6 new rules for emits, components and options - emits-kebab-case: enforce kebab-case emit names in config.emits (fixable) - emits-multi-word: enforce multi-word emit names to avoid native DOM event collisions - components-pascal-case: enforce PascalCase keys in config.components (fixable) - require-emit-declared-in-config: require this.$emit() names to be in config.emits - require-children-declared-in-config: require this.$children.Name to be in config.components - require-options-declared-in-config: require this.$options.name to be in config.options --- packages/eslint-plugin-js-toolkit/README.md | 24 ++++- .../eslint-plugin-js-toolkit/src/index.ts | 18 ++++ .../src/rules/components-pascal-case.test.ts | 49 ++++++++++ .../src/rules/components-pascal-case.ts | 69 +++++++++++++ .../src/rules/emits-kebab-case.test.ts | 60 ++++++++++++ .../src/rules/emits-kebab-case.ts | 79 +++++++++++++++ .../src/rules/emits-multi-word.test.ts | 48 +++++++++ .../src/rules/emits-multi-word.ts | 59 +++++++++++ .../src/rules/index.ts | 6 ++ ...equire-children-declared-in-config.test.ts | 55 +++++++++++ .../require-children-declared-in-config.ts | 98 +++++++++++++++++++ .../require-emit-declared-in-config.test.ts | 55 +++++++++++ .../rules/require-emit-declared-in-config.ts | 89 +++++++++++++++++ ...require-options-declared-in-config.test.ts | 55 +++++++++++ .../require-options-declared-in-config.ts | 98 +++++++++++++++++++ 15 files changed, 861 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.test.ts create mode 100644 packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts diff --git a/packages/eslint-plugin-js-toolkit/README.md b/packages/eslint-plugin-js-toolkit/README.md index abb610b28..fa11b982c 100644 --- a/packages/eslint-plugin-js-toolkit/README.md +++ b/packages/eslint-plugin-js-toolkit/README.md @@ -43,7 +43,13 @@ Add the plugin to your `.oxlintrc.json`: "js-toolkit/prefer-ref-over-query-selector": "warn", "js-toolkit/require-refs-declared-in-config": "error", "js-toolkit/no-manual-intersection-observer": "warn", - "js-toolkit/no-manual-mutation-observer": "warn" + "js-toolkit/no-manual-mutation-observer": "warn", + "js-toolkit/emits-kebab-case": "error", + "js-toolkit/emits-multi-word": "error", + "js-toolkit/components-pascal-case": "error", + "js-toolkit/require-emit-declared-in-config": "error", + "js-toolkit/require-children-declared-in-config": "error", + "js-toolkit/require-options-declared-in-config": "error" } } ``` @@ -115,6 +121,22 @@ export default [ | `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | warn | | | `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | warn | | +### Emits + +| Rule | Description | Recommended | Fixable | +| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | error | ๐Ÿ”ง | +| `js-toolkit/emits-multi-word` | Requires emit names in `config.emits` to be multi-word to avoid collisions with native DOM events (e.g. `item-click` not `click`). | error | | +| `js-toolkit/require-emit-declared-in-config` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | error | | + +### Components + +| Rule | Description | Recommended | Fixable | +| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | error | ๐Ÿ”ง | +| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | error | | +| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | error | | + ### Refs | Rule | Description | Recommended | Fixable | diff --git a/packages/eslint-plugin-js-toolkit/src/index.ts b/packages/eslint-plugin-js-toolkit/src/index.ts index 8ecb5c824..6a8acf8a4 100644 --- a/packages/eslint-plugin-js-toolkit/src/index.ts +++ b/packages/eslint-plugin-js-toolkit/src/index.ts @@ -20,6 +20,12 @@ import { requireRefsDeclaredInConfig, noManualIntersectionObserver, noManualMutationObserver, + emitsKebabCase, + emitsMultiWord, + componentsPascalCase, + requireEmitDeclaredInConfig, + requireChildrenDeclaredInConfig, + requireOptionsDeclaredInConfig, } from './rules/index.ts'; const PLUGIN_NAME = 'js-toolkit'; @@ -45,6 +51,12 @@ const rules = { 'require-refs-declared-in-config': requireRefsDeclaredInConfig, 'no-manual-intersection-observer': noManualIntersectionObserver, 'no-manual-mutation-observer': noManualMutationObserver, + 'emits-kebab-case': emitsKebabCase, + 'emits-multi-word': emitsMultiWord, + 'components-pascal-case': componentsPascalCase, + 'require-emit-declared-in-config': requireEmitDeclaredInConfig, + 'require-children-declared-in-config': requireChildrenDeclaredInConfig, + 'require-options-declared-in-config': requireOptionsDeclaredInConfig, }; const recommendedRules: Record = { @@ -68,6 +80,12 @@ const recommendedRules: Record = { [`${PLUGIN_NAME}/require-refs-declared-in-config`]: 'error', [`${PLUGIN_NAME}/no-manual-intersection-observer`]: 'warn', [`${PLUGIN_NAME}/no-manual-mutation-observer`]: 'warn', + [`${PLUGIN_NAME}/emits-kebab-case`]: 'error', + [`${PLUGIN_NAME}/emits-multi-word`]: 'error', + [`${PLUGIN_NAME}/components-pascal-case`]: 'error', + [`${PLUGIN_NAME}/require-emit-declared-in-config`]: 'error', + [`${PLUGIN_NAME}/require-children-declared-in-config`]: 'error', + [`${PLUGIN_NAME}/require-options-declared-in-config`]: 'error', }; const base = eslintCompatPlugin({ diff --git a/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.test.ts new file mode 100644 index 000000000..696f2c4a3 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.test.ts @@ -0,0 +1,49 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { componentsPascalCase } from './components-pascal-case.ts'; + +describe('components-pascal-case', () => { + it('passes and fails correctly', () => { + tester.run('components-pascal-case', componentsPascalCase as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', components: { MyChild: MyChild } }; + }`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', components: { Slider, NavMenu, HeroBlock } }; + }`, + // No components โ€” should not error + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', components: { myChild: MyChild } }; +}`, + errors: [{ messageId: 'notPascalCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', components: { MyChild: MyChild } }; +}`, + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', components: { 'nav-menu': NavMenu } }; +}`, + errors: [{ messageId: 'notPascalCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', components: { 'NavMenu': NavMenu } }; +}`, + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts new file mode 100644 index 000000000..c0630ab50 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts @@ -0,0 +1,69 @@ +import { + isBaseSubclass, + isPascalCase, + toPascalCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const componentsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'components', + ); + + if (!componentsProp?.value || componentsProp.value.type !== 'ObjectExpression') return; + + for (const prop of componentsProp.value.properties ?? []) { + if (prop.type !== 'Property') continue; + const name = prop.key?.name ?? prop.key?.value; + if (typeof name !== 'string') continue; + if (!isPascalCase(name)) { + const fixed = toPascalCase(name); + const fixedText = prop.key.type === 'Literal' ? `'${fixed}'` : fixed; + context.report({ + node: prop.key, + messageId: 'notPascalCase', + data: { name }, + fix: (fixer: any) => fixer.replaceText(prop.key, fixedText), + }); + } + } +} + +export const componentsPascalCase = createRule({ + meta: { + type: 'problem', + fixable: 'code', + docs: { + description: 'Require component keys in static config to be PascalCase', + }, + messages: { + notPascalCase: + 'Component key "{{name}}" must be PascalCase (e.g. "MyComponent").', + }, + }, + createOnce(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.test.ts new file mode 100644 index 000000000..d963c0b9f --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.test.ts @@ -0,0 +1,60 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { emitsKebabCase } from './emits-kebab-case.ts'; + +describe('emits-kebab-case', () => { + it('passes and fails correctly', () => { + tester.run('emits-kebab-case', emitsKebabCase as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; + }`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['value-input', 'panel-toggle'] }; + }`, + // No emits โ€” should not error + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['contentChange'] }; +}`, + errors: [{ messageId: 'notKebabCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; +}`, + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['ContentChange'] }; +}`, + errors: [{ messageId: 'notKebabCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; +}`, + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['content_change'] }; +}`, + errors: [{ messageId: 'notKebabCase' }], + output: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; +}`, + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.ts new file mode 100644 index 000000000..e144e0247 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/emits-kebab-case.ts @@ -0,0 +1,79 @@ +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; + +/** + * Returns true if the string is kebab-case: + * all lowercase letters, digits and hyphens, no underscores or uppercase. + */ +function isKebabCase(value: string): boolean { + return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(value); +} + +/** + * Converts a camelCase, PascalCase or snake_case string to kebab-case. + */ +function toKebabCase(value: string): string { + return value + .replace(/([A-Z])/g, (_, c: string) => `-${c.toLowerCase()}`) + .replace(/_/g, '-') + .replace(/^-/, '') + .toLowerCase(); +} + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const emitsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'emits', + ); + + if (!emitsProp?.value || emitsProp.value.type !== 'ArrayExpression') return; + + for (const element of emitsProp.value.elements ?? []) { + if (!element || element.type !== 'Literal') continue; + const name = element.value; + if (typeof name !== 'string') continue; + if (!isKebabCase(name)) { + const fixed = toKebabCase(name); + context.report({ + node: element, + messageId: 'notKebabCase', + data: { name }, + fix: (fixer: any) => fixer.replaceText(element, `'${fixed}'`), + }); + } + } +} + +export const emitsKebabCase = createRule({ + meta: { + type: 'problem', + fixable: 'code', + docs: { + description: 'Require emit names in static config to be kebab-case', + }, + messages: { + notKebabCase: 'Emit name "{{name}}" must be kebab-case (e.g. "content-change").', + }, + }, + createOnce(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.test.ts new file mode 100644 index 000000000..1efdea76e --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.test.ts @@ -0,0 +1,48 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { emitsMultiWord } from './emits-multi-word.ts'; + +describe('emits-multi-word', () => { + it('passes and fails correctly', () => { + tester.run('emits-multi-word', emitsMultiWord as any, { + valid: [ + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; + }`, + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['value-input', 'panel-toggle'] }; + }`, + // No emits โ€” should not error + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['click'] }; +}`, + errors: [{ messageId: 'singleWord' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['switch'] }; +}`, + errors: [{ messageId: 'singleWord' }], + }, + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['dragged', 'content-change'] }; +}`, + errors: [{ messageId: 'singleWord' }], + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.ts b/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.ts new file mode 100644 index 000000000..eeb452cd5 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/emits-multi-word.ts @@ -0,0 +1,59 @@ +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; + +function check(node: Node, context: RuleContext) { + if (!isBaseSubclass(node, context)) return; + + const body: Node[] = node.body?.body ?? []; + + const configProp = body.find( + (member: Node) => + member.type === 'PropertyDefinition' && + member.static === true && + member.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return; + + const emitsProp = configProp.value.properties?.find( + (prop: Node) => prop.type === 'Property' && prop.key?.name === 'emits', + ); + + if (!emitsProp?.value || emitsProp.value.type !== 'ArrayExpression') return; + + for (const element of emitsProp.value.elements ?? []) { + if (!element || element.type !== 'Literal') continue; + const name = element.value; + if (typeof name !== 'string') continue; + if (!name.includes('-')) { + context.report({ + node: element, + messageId: 'singleWord', + data: { name }, + }); + } + } +} + +export const emitsMultiWord = createRule({ + meta: { + type: 'problem', + docs: { + description: + 'Require emit names in static config to be multi-word to avoid collision with native DOM events', + }, + messages: { + singleWord: + 'Emit name "{{name}}" must be multi-word (e.g. "item-click" instead of "click") to avoid collisions with native DOM events.', + }, + }, + createOnce(context: RuleContext) { + return { + ClassDeclaration(node: Node) { + check(node, context); + }, + ClassExpression(node: Node) { + check(node, context); + }, + }; + }, +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/index.ts b/packages/eslint-plugin-js-toolkit/src/rules/index.ts index 90002d853..beac8eec7 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/index.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/index.ts @@ -18,3 +18,9 @@ export { preferRefOverQuerySelector } from './prefer-ref-over-query-selector.ts' export { requireRefsDeclaredInConfig } from './require-refs-declared-in-config.ts'; export { noManualIntersectionObserver } from './no-manual-intersection-observer.ts'; export { noManualMutationObserver } from './no-manual-mutation-observer.ts'; +export { emitsKebabCase } from './emits-kebab-case.ts'; +export { emitsMultiWord } from './emits-multi-word.ts'; +export { componentsPascalCase } from './components-pascal-case.ts'; +export { requireEmitDeclaredInConfig } from './require-emit-declared-in-config.ts'; +export { requireChildrenDeclaredInConfig } from './require-children-declared-in-config.ts'; +export { requireOptionsDeclaredInConfig } from './require-options-declared-in-config.ts'; diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.test.ts new file mode 100644 index 000000000..b3c8d8b4b --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.test.ts @@ -0,0 +1,55 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { requireChildrenDeclaredInConfig } from './require-children-declared-in-config.ts'; + +describe('require-children-declared-in-config', () => { + it('passes and fails correctly', () => { + tester.run('require-children-declared-in-config', requireChildrenDeclaredInConfig as any, { + valid: [ + // $children access matches declared component + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', components: { Slider } }; + async mounted() { const s = this.$children.Slider[0]; } + }`, + // Multiple components all accessed correctly + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', components: { Header, Footer } }; + async mounted() { + this.$children.Header[0].show(); + this.$children.Footer[0].hide(); + } + }`, + // No $children access + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', components: { Slider } }; + async mounted() { + const h = this.$children.Header[0]; + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + { + // No components in config at all + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo' }; + async mounted() { + this.$children.Slider[0].play(); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.ts new file mode 100644 index 000000000..641c3a126 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-children-declared-in-config.ts @@ -0,0 +1,98 @@ +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; + +function collectDeclaredComponents(classNode: Node): Set { + const body: Node[] = classNode.body?.body ?? []; + const configProp = body.find( + (m: Node) => m.type === 'PropertyDefinition' && m.static === true && m.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return new Set(); + + const componentsProp = configProp.value.properties?.find( + (p: Node) => p.type === 'Property' && p.key?.name === 'components', + ); + + if (!componentsProp?.value || componentsProp.value.type !== 'ObjectExpression') return new Set(); + + const names = new Set(); + for (const prop of componentsProp.value.properties ?? []) { + if (prop.type !== 'Property') continue; + const name = prop.key?.name ?? prop.key?.value; + if (typeof name === 'string') { + names.add(name); + } + } + return names; +} + +/** + * Returns true if `node` looks like `this.$children`. + */ +function isThisChildren(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$children' + ); +} + +export const requireChildrenDeclaredInConfig = createRule({ + meta: { + type: 'problem', + docs: { + description: + 'Require all this.$children accesses to use component names declared in static config.components', + }, + messages: { + undeclared: 'Component "{{name}}" is not declared in static config.components.', + }, + }, + createOnce(context: RuleContext) { + return { + 'ClassDeclaration, ClassExpression'(classNode: Node) { + if (!isBaseSubclass(classNode, context)) return; + + const declared = collectDeclaredComponents(classNode); + + walkNode(classNode.body, (node: Node) => { + if (node.type !== 'MemberExpression') return; + if (!isThisChildren(node.object)) return; + + let name: string | null = null; + + if (!node.computed && node.property?.type === 'Identifier') { + name = node.property.name; + } else if ( + node.computed && + node.property?.type === 'Literal' && + typeof node.property.value === 'string' + ) { + name = node.property.value; + } + + if (name && !declared.has(name)) { + context.report({ + node, + messageId: 'undeclared', + data: { name }, + }); + } + }); + }, + }; + }, +}); + +function walkNode(node: Node, visit: (n: Node) => void) { + if (!node || typeof node !== 'object') return; + visit(node); + for (const key of Object.keys(node)) { + if (key === 'parent') continue; + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) walkNode(item, visit); + } else if (child && typeof child === 'object' && child.type) { + walkNode(child, visit); + } + } +} diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.test.ts new file mode 100644 index 000000000..640e02923 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.test.ts @@ -0,0 +1,55 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { requireEmitDeclaredInConfig } from './require-emit-declared-in-config.ts'; + +describe('require-emit-declared-in-config', () => { + it('passes and fails correctly', () => { + tester.run('require-emit-declared-in-config', requireEmitDeclaredInConfig as any, { + valid: [ + // Emit declared and used + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; + async mounted() { this.$emit('content-change', 'value'); } + }`, + // Multiple emits all declared + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', emits: ['item-select', 'panel-close'] }; + onButtonClick() { + this.$emit('item-select', 1); + this.$emit('panel-close'); + } + }`, + // No $emit calls โ€” nothing to validate + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', emits: ['content-change'] }; + onButtonClick() { + this.$emit('item-select'); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + { + // No emits in config at all + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo' }; + onButtonClick() { + this.$emit('content-change'); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts new file mode 100644 index 000000000..d87a3b446 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts @@ -0,0 +1,89 @@ +import { isBaseSubclass, type Node, type RuleContext, createRule } from '../utils/ast.ts'; + +function collectDeclaredEmits(classNode: Node): Set { + const body: Node[] = classNode.body?.body ?? []; + const configProp = body.find( + (m: Node) => m.type === 'PropertyDefinition' && m.static === true && m.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return new Set(); + + const emitsProp = configProp.value.properties?.find( + (p: Node) => p.type === 'Property' && p.key?.name === 'emits', + ); + + if (!emitsProp?.value || emitsProp.value.type !== 'ArrayExpression') return new Set(); + + const names = new Set(); + for (const el of emitsProp.value.elements ?? []) { + if (el?.type === 'Literal' && typeof el.value === 'string') { + names.add(el.value); + } + } + return names; +} + +/** + * Returns true if `node` looks like `this.$emit`. + */ +function isThisEmit(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$emit' + ); +} + +export const requireEmitDeclaredInConfig = createRule({ + meta: { + type: 'problem', + docs: { + description: 'Require all this.$emit() calls to use event names declared in static config.emits', + }, + messages: { + undeclared: 'Emit "{{name}}" is not declared in static config.emits.', + }, + }, + createOnce(context: RuleContext) { + return { + 'ClassDeclaration, ClassExpression'(classNode: Node) { + if (!isBaseSubclass(classNode, context)) return; + + const declared = collectDeclaredEmits(classNode); + + walkNode(classNode.body, (node: Node) => { + // Match: this.$emit('event-name', ...) + if (node.type !== 'CallExpression') return; + if (!isThisEmit(node.callee)) return; + + const firstArg = node.arguments?.[0]; + if (!firstArg || firstArg.type !== 'Literal') return; + const name = firstArg.value; + if (typeof name !== 'string') return; + + if (!declared.has(name)) { + context.report({ + node: firstArg, + messageId: 'undeclared', + data: { name }, + }); + } + }); + }, + }; + }, +}); + +function walkNode(node: Node, visit: (n: Node) => void) { + if (!node || typeof node !== 'object') return; + visit(node); + for (const key of Object.keys(node)) { + if (key === 'parent') continue; + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) walkNode(item, visit); + } else if (child && typeof child === 'object' && child.type) { + walkNode(child, visit); + } + } +} diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.test.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.test.ts new file mode 100644 index 000000000..b7ea430e9 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.test.ts @@ -0,0 +1,55 @@ +import { describe, it } from 'vitest'; +import { tester } from '../utils/rule-tester.ts'; +import { requireOptionsDeclaredInConfig } from './require-options-declared-in-config.ts'; + +describe('require-options-declared-in-config', () => { + it('passes and fails correctly', () => { + tester.run('require-options-declared-in-config', requireOptionsDeclaredInConfig as any, { + valid: [ + // $options access matches declared option + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', options: { label: String } }; + async mounted() { console.log(this.$options.label); } + }`, + // Multiple options all accessed correctly + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo', options: { count: Number, isActive: Boolean } }; + async mounted() { + console.log(this.$options.count); + console.log(this.$options.isActive); + } + }`, + // No $options access + `import { Base } from '@studiometa/js-toolkit'; + class Foo extends Base { + static config = { name: 'Foo' }; + }`, + ], + invalid: [ + { + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo', options: { label: String } }; + async mounted() { + console.log(this.$options.count); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + { + // No options in config at all + code: `import { Base } from '@studiometa/js-toolkit'; +class Foo extends Base { + static config = { name: 'Foo' }; + async mounted() { + console.log(this.$options.label); + } +}`, + errors: [{ messageId: 'undeclared' }], + }, + ], + }); + }); +}); diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts new file mode 100644 index 000000000..21306ccc7 --- /dev/null +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts @@ -0,0 +1,98 @@ +import { isBaseSubclass, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; + +function collectDeclaredOptions(classNode: Node): Set { + const body: Node[] = classNode.body?.body ?? []; + const configProp = body.find( + (m: Node) => m.type === 'PropertyDefinition' && m.static === true && m.key?.name === 'config', + ); + + if (!configProp?.value || configProp.value.type !== 'ObjectExpression') return new Set(); + + const optionsProp = configProp.value.properties?.find( + (p: Node) => p.type === 'Property' && p.key?.name === 'options', + ); + + if (!optionsProp?.value || optionsProp.value.type !== 'ObjectExpression') return new Set(); + + const names = new Set(); + for (const prop of optionsProp.value.properties ?? []) { + if (prop.type !== 'Property') continue; + const name = prop.key?.name ?? prop.key?.value; + if (typeof name === 'string') { + names.add(toCamelCase(name)); + } + } + return names; +} + +/** + * Returns true if `node` looks like `this.$options`. + */ +function isThisOptions(node: Node): boolean { + return ( + node.type === 'MemberExpression' && + node.object?.type === 'ThisExpression' && + node.property?.name === '$options' + ); +} + +export const requireOptionsDeclaredInConfig = createRule({ + meta: { + type: 'problem', + docs: { + description: + 'Require all this.$options accesses to use option names declared in static config.options', + }, + messages: { + undeclared: 'Option "{{name}}" is not declared in static config.options.', + }, + }, + createOnce(context: RuleContext) { + return { + 'ClassDeclaration, ClassExpression'(classNode: Node) { + if (!isBaseSubclass(classNode, context)) return; + + const declared = collectDeclaredOptions(classNode); + + walkNode(classNode.body, (node: Node) => { + if (node.type !== 'MemberExpression') return; + if (!isThisOptions(node.object)) return; + + let name: string | null = null; + + if (!node.computed && node.property?.type === 'Identifier') { + name = node.property.name; + } else if ( + node.computed && + node.property?.type === 'Literal' && + typeof node.property.value === 'string' + ) { + name = toCamelCase(node.property.value); + } + + if (name && !declared.has(name)) { + context.report({ + node, + messageId: 'undeclared', + data: { name }, + }); + } + }); + }, + }; + }, +}); + +function walkNode(node: Node, visit: (n: Node) => void) { + if (!node || typeof node !== 'object') return; + visit(node); + for (const key of Object.keys(node)) { + if (key === 'parent') continue; + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) walkNode(item, visit); + } else if (child && typeof child === 'object' && child.type) { + walkNode(child, visit); + } + } +} From fe0fbd1c08c4d554da501b02c5652c6956435501 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Tue, 12 May 2026 11:27:27 +0200 Subject: [PATCH 2/5] Update docs with eslint-plugin rules --- packages/docs/guide/going-further/linting.md | 70 ++++++++++++++++---- 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index bd1ea589b..d942b6dce 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -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" } } ``` @@ -76,9 +89,38 @@ export default [ | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | | `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | โŒ | | `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | ๐Ÿ”ง | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | ๐Ÿ”ง | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | โŒ | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | ๐Ÿ”ง | + +### Refs + +| Rule | Description | Fixable | +| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | ๐Ÿ”ง | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | โŒ | +| `js-toolkit/refs-no-bracket-access` | 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` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | โŒ | +| `js-toolkit/prefer-ref-over-query-selector` | 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 + +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------- | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | ๐Ÿ”ง | +| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | โŒ | + +### Emits + +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | ๐Ÿ”ง | +| `js-toolkit/emits-multi-word` | 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` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | โŒ | + +### Components + +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | +| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | ๐Ÿ”ง | +| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | โŒ | ### Lifecycle methods @@ -95,10 +137,14 @@ export default [ ### Forbidden patterns -| Rule | Description | Fixable | -| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | โŒ | -| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | โŒ | -| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | โŒ | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | โŒ | -| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead โ€” the framework handles binding and cleanup automatically. | โŒ | +| Rule | Description | Fixable | +| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | โŒ | +| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | โŒ | +| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | โŒ | +| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | โŒ | +| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead โ€” the framework handles binding and cleanup automatically. | โŒ | +| `js-toolkit/no-deep-utils-import` | 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` | Disallows wrapping `withMountWhenInView` inside `withScrolledInView` โ€” the latter already includes the former internally. | โŒ | +| `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | โŒ | +| `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | โŒ | From acf7f6a86058fbb889d058b6cdd1d2e0d7836617 Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Tue, 12 May 2026 12:08:33 +0200 Subject: [PATCH 3/5] Lint files --- packages/docs/guide/going-further/linting.md | 48 +++++++++---------- .../docs/utils/storage/createLocalStorage.md | 2 +- .../utils/storage/createSessionStorage.md | 2 +- packages/docs/utils/storage/createStorage.md | 2 +- .../createUrlSearchParamsInHashStorage.md | 2 +- .../storage/createUrlSearchParamsStorage.md | 4 +- packages/docs/utils/storage/providers.md | 2 +- packages/eslint-plugin-js-toolkit/README.md | 20 ++++---- .../src/rules/components-pascal-case.ts | 3 +- .../rules/require-emit-declared-in-config.ts | 3 +- .../require-options-declared-in-config.ts | 8 +++- 11 files changed, 51 insertions(+), 45 deletions(-) diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index d942b6dce..679113edc 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -85,42 +85,42 @@ export default [ ### Class structure -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------- | -| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | โŒ | -| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | ๐Ÿ”ง | +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------- | +| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | โŒ | +| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | ๐Ÿ”ง | ### Refs -| Rule | Description | Fixable | -| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | ๐Ÿ”ง | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | โŒ | -| `js-toolkit/refs-no-bracket-access` | 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` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | โŒ | -| `js-toolkit/prefer-ref-over-query-selector` | 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. | โŒ | +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | ๐Ÿ”ง | +| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | โŒ | +| `js-toolkit/refs-no-bracket-access` | 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` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | โŒ | +| `js-toolkit/prefer-ref-over-query-selector` | 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 -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------- | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | ๐Ÿ”ง | -| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | โŒ | +| Rule | Description | Fixable | +| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | ๐Ÿ”ง | +| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | โŒ | ### Emits -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | ๐Ÿ”ง | -| `js-toolkit/emits-multi-word` | 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` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | โŒ | +| Rule | Description | Fixable | +| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | ๐Ÿ”ง | +| `js-toolkit/emits-multi-word` | 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` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | โŒ | ### Components -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | -| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | ๐Ÿ”ง | -| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | โŒ | +| Rule | Description | Fixable | +| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------- | +| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | ๐Ÿ”ง | +| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | โŒ | ### Lifecycle methods diff --git a/packages/docs/utils/storage/createLocalStorage.md b/packages/docs/utils/storage/createLocalStorage.md index c2bda786f..959d90e0e 100644 --- a/packages/docs/utils/storage/createLocalStorage.md +++ b/packages/docs/utils/storage/createLocalStorage.md @@ -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":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvQXBgAZCCLYBlNBFLMA5jAA8AFV50aYKHF4AlGCPVQdcNKXZhNFXszAYAfLwC8FqzZ2Dk4ubh6enowQWOKScAD8iLwA8gC27Gg6qupaMMkxEmBw+p6uADogWKTE7LCkFZ7cSdka2gCSRWjuIrp6nmVg7KlY6mjSsgpKrC25lCBQigiIIADCpDDMNG68rIoqaq0w0uIcmAB0c12aS8jIlcwaqXPRsUW8M9q8L4VmjFUw/HYtFcskcbHYAC8yNwLgBdCh3dZoQSkIqUMCCVisWHwkD2B4MRAATiorBgzjQ+CQAEYAKxULqkbSEkAyeR7aYHWakpy4RAABioInwD2YYjISCJAF8KOhsHyCMQJQyjHhrJ1ePYctpmlz2p1urpLNZSLZ7I5nK53F5PHN8aRCQB2amk8maSlIR0Mh7MvBaw5zDhgPkAJiFIo04vIiAAbDK5Tg8IQSOQVfQmOCuHw2ZN9tqjQFTUELaFrREogU4okUulMh88pWijpjYFzSEreFSrwKlUanUGrwAD7SEwA3lQJrvPUwDr40QFk1m4KWsI2gZDEYO8bsqb1uYLERLVbrTZHZg7Dn147sU4YC7e65IW73R7PRtmK/fOK8P7rQHAzUyE4DgoVIGEQHhREYGRVEEAoDEsRxKh7UJAAOF0QDJCkqUQOlvSZaC8BzS9p0DXkkDDEBhVFKMaTjWVqHlJMlVTahVWWX9ojITA+H+f9qzbZwhxHWBAWDKB90WPAAGkYAwXg+KBAR1F4MBmFSeAsDFEIzl4ABBLFeAAazkswAHcb1YXgACMjkU2gYCgXgLMpXhKXYMxBM0e9qC0G47jodSsDJOYAHJUgwZgsCwRBQucyzNQOI4TPk0LKRgDS4q4Xhwsi6LEHSzKINxFDJRJTC3Q9WN8N9ZZ7LI4MKPDGiaGjABmAAWeNGMTZZk2VNj02WdV7ES/NdXzWcunnZtCyXEsOxtO1GUJakQwwrD3Rwr1fIIll/W5TDyMQDrmsjVqkH5brMF6xUU0udiQEYDTKWgPh6ymw1ZsXYt21XCIzlkTIKkKmAGkYFKkhB/AMrByheCINhBBgJJrUnIgIFqZaCRpENys2qq2pqwjliBhq+VOqiIzFC7EFQ66mL6liHqGqi4jGA6dSnSaDRmlsiy8xaImxh0aVpDbKu24n9tInlGsQekqZaiUBQZ27+tYmhWee6DCAnbnDk+vm5t+lcy08M5mR0aHYfByHu2oGGNIqSdrR/ABqakvhTXZmAnEXVtpfHJaQIndtqkBfTlvlFeo86VfphiboVDWWZZEaOenCbDd5npvtbZdS07AOaVQsOCal8OSbxWWjvlmMzpplWrqTxm7oGrWWR1179Y+3OFwLhb/otq2KlYdxNDtuSocwieKlcUTmExNAADUkZR1dXY8D2vZY33/eQlaaSJCXsND6W8Cjuu+QbpX4+jROExT5m03T9mRzgQRrLgERHFspJGB8B8N4DGWND440QCGMuroz64QwoyCOwhP7f1/uwWy5NJSN1orGNWz97qvzVO/TmG8+5zjzvzeaf1zYl0gUSHaFdPQX1JrXIMfJypxybtGEMLcn7MXwYNLuL09bvWnEbchJtBbD0Bl/H+f9dA22diASI9sFFw1cFMVg1kxRGQAYjVgyNUYeCASAzGE4AHGIRqYmhbV+SKwYcSJheIZGoPQdHTBd9OEUUfj1PBHdHp/FFC9aECN16GIwNYkMdiQ4K0cXo5GGCHEeOwR1bxyc+F+NZojUgvADyCA0mANASQAAiig8nkgYFQA8R4ABU1SAAGFkTAQDMmcXJ+S0B1NqbwJEKI3jnj/GQckPQ3IQDcjDHJpT2m8HVF0ccvAnBjKOI0hYLSBgDGQAAWSKQAOX8PwQZ85YSMHwGgNAWA4CIAAPSXNgCQXYOBSBnFSBACEllmBnHUJoG5ixLkAHUYDWUuXpAACm0P5TgVnfJEGUgp3BrFtWDrA1Cji2nlISdSQUSTaYhlwekzW/jewPJ4rwEp0L2mtMmeUgAomSdpSQAASegNlyBpbDApklDx4D0OM2pdTUUFNZe0zp1TukbCgAAWkkKweShLuLyQgPwRZJLKUFPmQUsg/AxRHB6bBJVgryljM2PMsw6VukQAgGMGAtKDUKqVfysYjB+AqUCsMMkrhTU6BOakVg3grVsrGE67JjLmUTLJeUngZw1lgE2TsvZByehHJOWci51zblWq4o855rysTvM+VCuAfyAVAtBZc0lMK0BQvLfq2F1iUkwK2mLFFKq0DVoqdfGkmKOHYOpNSXFTN+GdyYLKh0GA+DBrkMkUgygV4AHFW2tM2FwaCxTkgbNUCWDZUUOVHhjbsyw+z1iHOOac85VybkwDuRmp5Ly3kfKZPmwtgKQVgvHa2m5i6gZwvAaLRAbUYyU3sTtBB1coAfsIm43CnbqbYJDI6Pt7d8XawhXQPgRTV3rpCJurAyAvKwiSF5YSwhRLjmsY6Si9jFbAZZKDdFUHlbtVpPB1OBCOJaUeNBYJcSN7WmsahTFFHYnr1o1g2mHU2pSlxNYWAeANyjF4MAbcuZOT5l4FKAQ1RUg5QAAL2EEFACQL1mCXIAFZwHFWoCArAjIZEuYIE4cBQoAG4o0ZzGocXwimSL5kYAp+ySRcpRRinFKU3BnNgAGMQwG0FGBpSdjAUKrhQqgdIEZUKoWIvTkttF2LsM0uOd4NcnKyXUsZfzFltAMXx7OASzlckeWCuXNq2AOKjBF7L24FGwrlgaYjOmSKZw8AfxOBEPo/TQl+Aaa+OlbJXRv4ddEO/JBzi5Eeci8g2RaCYAxdBjVxgXGLHAAGLwUN5aKVhoFdagpC6uhAzOKDDzXGwshcc3MQzSBQBGHJHAQoeBTMgClFKIAA==="} +// @twoslash-cache: {"v":1,"hash":"168444ac507ca8fe05e8951c976264031db52ea65923eec4212be04d143bde92","data":"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="} import { createLocalStorage } from '@studiometa/js-toolkit/utils'; const storage = createLocalStorage({ prefix: 'myapp:' }); diff --git a/packages/docs/utils/storage/createSessionStorage.md b/packages/docs/utils/storage/createSessionStorage.md index 74d945b97..bfe656d32 100644 --- a/packages/docs/utils/storage/createSessionStorage.md +++ b/packages/docs/utils/storage/createSessionStorage.md @@ -5,7 +5,7 @@ Create a storage instance backed by `globalThis.sessionStorage`. The underlying ## Usage ```js twoslash -// @twoslash-cache: {"v":1,"hash":"69de48acfe7afd1f4722f2125a3205977466917296023382c3b39c4daafd620b","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvQXBgBleHAlg5aCKWYBzGAB4AKrzo0wUOLwBKMEeqg64aUuzCaKvZmAwA+XgF4LVmzsHJxc3D09PRggscUk4AH5EXgB5AFt2NB1VdS0YZJjlOH1PVwAdECxSYnZYUnLPbiTsjW0ASTB7dxFdPU9SsHZUrHU0aVkFOCVJZtzKECgIEQREEABhUhhmGjdeWUnlGe1pcQ5MADo5tC1l5GQK5g1UuejYjt5DmF4Xwt5GSph+OxaK5ZI42OwAF5kbgXAC6FDuGzQglIHUoYEErFYsPhIE6pAYiAAnFRWDBnGh8EgAIwAdioV1I2kJIBk8kUBzULVwpKcuEQAAYqCJ8A9mGIyEgiQBfCjobD8gjESUMox4awdUb2HLaJpc3LtTqiXSWaykWz2RzOVzuLyeOb4wm0gDMpPJmkpSHp1AezLw2u5cw4YH5ACZhaKNBLyIgAGyy+U4PCEEjkVX0Jjgrh8NkTKYqfXaHSmwKWkI28KRb5xRIpdKZD75V5FEvmoJW0K2iJlCpVIg1Mj1XgAH2kJgBfKgjXehZghquxuLATbZetYTt/UGwwJY3Z+2ms7mCyWeHWm22zF2HIPOs+ghOGQwFwZ1yQt3uj2eBTiM9vX2/bx/BsgLAleYIcFCpAwiA8KIjAyKoggFAYliOJUI6SAAByuiAZIUlSiB0i+TLwXgubXgWt5BnySDhiAIpitGNLxnK1AKsmypptQaorEB0RkJgfD/CBtarpoI5jrAgIhlAR6LMsIAANIwBgvDCUCAjqLwYDMKk8BYOKIRnLwACCWK8AA1ipZgAO7sOZABGnzqbQMBQLwdmUrwlLsGYYnPj6mg3HcdC6VgZJzAA5KkGDMFgWCIJFHn2awuxcp8VmqZFlIwHpSVcLw0WxfFiA5XlMG4hhhECjheEegRsbEX6KwudRIa0RGjE0DGzosYmiopiq3EZisGr2Glt56re85dCay4WsEa5dva6GMoS1Kht6dWeog3qMs1eKHry7WIAALJ1UbdUgAoJmxSYrINXE0CNICMHplLQHwHwzYurYLR2FZ2mcsiZOUahWWA9SMJlSRgxAEPlK4RBsIIMBJLa05EBANQOmtNLOgArG6+FIDh+2kSsINtfy530ZG4pXYgmG3Zg91Kqmlw8fRcRarOU3cj93RLma/3luuES4w860EySuHujte2+hTh1Ucd/JE3TXWSoKLPsQ9nGcy9b3wYQU6/gLmqzcLpaLZ2lbA/BOjlHQWDsBscBQzDvDO7Qrvu4jvDI6wqPox4mPY7Jq1S8xW3ywRZNKyyVNq0gGsMZd2vM6xrMDQb6YsmNvOTebBqW7983tmLy2SwSNJEkKcsk7tTXKwGswp83msZzGN3Z3r7NDc9LLGx9ZvfWXQt/ZXS328yTvUPD5KeypsMLwjIDTravwANTUl8qasBAzBTjX61EphxP1aTLcsn6HfeunDOZ9KuLWLAeBbiMvDALueacn+0oBBVFSIVAAAvYQQUAJDvWYAAegAFZwAALRqAgKwCyGRYH3nsnASKABufo/RC4TW5L4X+FEPiMB/i5JIkU9j5kSrwaU3ACGQ01LeB2aBGDZUXmASKrhIrMAciIDazpIosP6G3bQnDuEuzdvAfhvBeoCgFBIthUiYBnGZNw8G5JxF4N4LA2BhUhEiNDGIuYMCkCgCMOSfMeBEEgGlNKIAA==="} +// @twoslash-cache: {"v":1,"hash":"459d66cd4edc345b8db5735849a6e5cad362c44732d8c229c98df7d15ea3c07e","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvEaRjMaAZXhwJYBWgilmAcxgAeACq86NMFDi8ASjBGaoeuGlLsw2ir2ZgMAPl4BeKxs7BycXNw8vb29GCCxxSTgAfkReAHkAW3Y0PXVNHRhUuNU4dwAdECxSYnZYUnLvbhTcrV0ASTBHTxF9A29SsHZ0rE00aVl5GCU4FUlm/MoQTtIGRABOKlYYVzR8JAAmAAYqNGZSXRWQGTlFZVU53QWOMFxEI8v8U+YxMiRVgF8KOhsC8CMQfscTHhbB1Ro48romhoWjB2p1RPprLZSPZHM5XO5PD5vAslisAOwANg2W20OyQZOOp3OeDhyMeLheeyoIg+Wm+5EQFIBQJweEIJHIEPoTDYnB4Y2uk1usyR+T0mOCuLCBMi0Vi8Q6yTSmWy9wKRQSZQqVSINTI9UavDNqJO6PVQWxITx4UJUX6g2GywVEymMzUqoeVFJSAAHFSQJttrtEIdGWcYBcriHleH4bgNhz9tzeV8aAKAIxCwHUYFisGS6iQxAgRiVWJkTB8SowfjsWhGrWuXgAH14wlgveeUBJJ2WSHLB3WCZpdMFaeZze7vdo7OeRfen35SAAzP9q5hRc3xeDG9Lm9DHLxWflEXmXV0MR6caF8REiTPThWctj2Pakk3pdcMxZCN8wTQtEAAFmLQ8yyQA5hRrS9QQlBYaDvFt0gzQgoD4Z0YQ/d0sW/b0dSJAA6OAMz0coNAAay2epGHYjAUhYiB2LAcp3CINhBBgFJCUdIgIBqAC50QcsEPLMDaWTUDqCZKDm0YhgCz3RDkL5VDEBjDCLxBa8GzwzMElhGDX2Rd83Q1T1Bx9XU5KAikAFYVNXBkNPTC5n0jOD9N8g8jJ+V4zNrK961wpsCKI6BSJgpzukozUf3c+idOYkA6CwdhZDgTjuN4wraGK0qhN4ETWDEiSvCkmTpyjWcgLJdTE1Uk9IOCqC9JeCKeRQ6LTPPOLsJvayoVsp97KddLyOcr8vW1P8ok8+dVnjXr/IG6C813F4ArGqKBXQqasMsxL8MYQidlS5a31WzKXOozbfW8OjzgKtiOJAaIKt4PiBPKR1CV4RgAGpy14etWAgZgSJ2lMDiQ5dwMQdTZw3EBmWGiDItLCa/gAXW5aAQQDEZeGAYMbmmO4YN4P4BCqdJeAAcgAAUcQQoAkJ7mAAegAKzgABaDQIFYVisjFwRxFYOAeYAbn6foHzsvN/CZpUWZVPNGEZrc+xSHnGONsBEB59nuC1wSYTzBiM0YHnAbAHn3B55gACMRHLPZjx5p3+hCmB3bQT2ipK+Bfd4Y8KQOA4I5dqO/o9r3+K2cONd4MWxd5wPg9DnmFlFpBQBMLYwzwKWQD+P4gA==="} import { createSessionStorage } from '@studiometa/js-toolkit/utils'; const storage = createSessionStorage({ prefix: 'session:' }); diff --git a/packages/docs/utils/storage/createStorage.md b/packages/docs/utils/storage/createStorage.md index b6520ff3d..2aaabe274 100644 --- a/packages/docs/utils/storage/createStorage.md +++ b/packages/docs/utils/storage/createStorage.md @@ -38,7 +38,7 @@ storage.destroy(); Use generics to type your storage keys and values: ```ts twoslash -// @twoslash-cache: {"v":1,"hash":"0c98d4b6c6bab0d7b5e11dc82b926365408b8cd29c36d881735e649e3b5a38e0","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvEaRjMaAZTQRSzAOYwAPABVedGmChxeAJRgiVUTXDSl2YNRV7MwGAHy8AvKfOXrt+0dnVzc3RggscUk4AH5EXiUVdRgAeUiJMDgdN254xNUNAElMtBcRLW03AB0wdgBbLBU0aVl5GHzkyhAoCBEERBAAYVaaZ146wVZxAFoAaxgMXhskjV57GzKYADou0rV+5GQQLGZVOq6IqMyE5QKYXkuMuB2AXQoj2TRBUkzKMEnWC83iANqQGIgAJxUVgwBxofBIACMAGYqKVSBpwSAZHJFLdOtD7LhEAAGKgifCnZhiMhICEAXwo6GwxIIxFpaP0eEwOF4AEEsFgOqtvMAarwJbx4TA6jB4lUQBw1Pg0AreAAfXgKqCnWYKgDc4slgjgZHiYqkkqtYGYsviNjsDkNlqtEvYUHi/zqACMyM6rfT/ZLWC41PaAk6avSuqDwQA2AAc0NhanhSLJ1FOmLwAqF+I0XQ4YGJACZyZTVDTyIgAKyM5k4PCEEjkTn0JhYUgRMiYPjSu1axXsZWqkAawc60h6kAx9HghM15NwhG1tFZmBY/u4QnFpCo7EV6k0asl+vUFlN9mt6hcgaMTvdsEYPgms28C3W21ypYRtRBt0erwXq+qQzrRlQsZIoiZaKimaaIAALGuGIbngr7XkWxJIQeVJVkgp5MuejYDM2HI3u2d4PjgT58DaA4OoEs6nOC0EZjCy5INh6LZgMdHbkOu6IeWuHHumZ48qypHXjQFEgPeXbUb2ayAcBZGQYgKLYexqYrjB3GoQM7qFkSnHCZWomICWCHiReJFXrst5yVRPbPrwIYOOGjpqExYJIjWbFwSuXHrli7neTuxKLjh5m0pZNnEWyLYObJFglD+KzfsKMDFBsohaLmWVuD5LEAOxaYFSAlchPEgvm/GYUgSbRUesVxvFkn2W2WIsBwXB8DibRZZoBV1WEjzRHENwZWkVxZCNGU5HkdU5aUeXDYKhU1PUjRgi0uLtHVXQ9H0eDDPtYwTFM7BzAs6V3GsJSbDsa77EghzHFS5xUON1xZQ86TRK87wgJ83y/BQ/ysICwLqYiCblRxGn7vpWIDXiGXGYJTUUiJsWIm1hESZeSVddyLL8htdVeO+RoSlu8pDiOaqatquoGrTvDoeaHOfvRv7/q67qeoIPp+hzgYc2FnmBGBxVIhCTXafBiIZijOaUxjEWNWZLXVsiJLtcTZEyaj0TNMsdxLRlK2bOteYLXLlmIgjOmVdVBm1ZrAnEirOt4aSht2ST5HdbK8LQHwWU22t813G4WymmgmgKluCphPMGAM6nlC8EQbCCN+CpKiqzMTmzY6apDrC5LnEBGRBc74SWMFKyuyMhXgieYz7GY4zF1YJoHiXG45qU2HdyRW3c0flHbhWOyWNZRa3buZihWIWwS3tQX7FkG4TtnD9JjmMGHhBQJHy2PTHGtxwnG7J9Q+AyjAaeMBnWfP7KCpOHnrAFwzYuo5xysynKXKuNciB1ygAvOM+4V6IHbuvTuqEtYaURLvWKg8D4JTIF2a86kSwlTjEuV2iASHYmgKWZECEor8HYOxL8eB7CwFoFsNACAqBGQGHg6YJYaE1j4RCZE0w4zJSxHyDEItYTNAgPwKU5MADkCpvT/1fiARRaxjCQGaFwOAw4bSqPuMoXgJwzgbjIA8eREleDKMZiXCuZcwGOKroo56sESCsDwHglQ3cd7NX9iWREQ8pLiLwGPc2dUp7JBnvlW+yQioN2YnuEkGDYKIyqmvGqm8CxoOgpg6s+8GwdWDibJgZ8I5TWntfWescElbExI/dCb8P6DmaSAGuH5JR8WlpGF0ylhai1AlGccVdeCMAANSIgeC2VgEBmAX0dvrKE6SyFIJqtmPJME+660avSYEFhYDMIaE0d8e1BpU3pAILsdRbEAAEbCCCgBIMOzAAD0AArOA0xlAQFYLMdgaA3mCHEKwOAijnQ1BsXUkUNNLT01sUAzRmpFGTlmBCo0XMzk9J/F5fUAygIixArwCWlopa4pllGSFogzYT1hWjA6GU56jUYNwalOTtiJ0YIorciinCot1IotlvA3lvN4IAUHIFE4CgDUDl980Dct5fy1RBchX4tFbwQAMuRSvuD40gsq6oNI3Ny9CaqRViuAEBL8vS/wEtUqQEloyARdFeUgUA+hYT6MkNyBA9J6RAA=="} +// @twoslash-cache: {"v":1,"hash":"ce90c11fdde0a7b0a9ad2efc8c2ef266cf7165c59f1cb9deedfa8069e382a62f","data":"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"} import { createStorage } from '@studiometa/js-toolkit/utils'; type AppStorage = { diff --git a/packages/docs/utils/storage/createUrlSearchParamsInHashStorage.md b/packages/docs/utils/storage/createUrlSearchParamsInHashStorage.md index a93b9a7df..fc965704e 100644 --- a/packages/docs/utils/storage/createUrlSearchParamsInHashStorage.md +++ b/packages/docs/utils/storage/createUrlSearchParamsInHashStorage.md @@ -9,7 +9,7 @@ By default, changes use `replaceState` to avoid polluting browser history. Use ` ## Usage ```js twoslash -// @twoslash-cache: {"v":1,"hash":"56dfa762cb1028d977df774e7d67ad47b9337903c79572f2f5b49c1316f4f379","data":"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"} +// @twoslash-cache: {"v":1,"hash":"6d7c7c16672575e0c337418c60c64c8dbfd6a058a7e8e91277363d36f36c96d0","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvEaRjMaAVVKsAynNIj8ABWalmAWzgBJMAAku+FWgh6A5jAA8AFV50aYKHF4AlGCJtQDnBopOxgthS8zGAYAHy8ALw+fgFBIWERUTGxsYwQWOKScAD8iLwA8vrsaA5WNsz25QUSYHCRADogWKTE7LCknfEAZLxKrFo9RH1kTYWt3GV1djAmwdEijk6x7WDs+lg2aNKy8jBjarqaOnqGJuZwltbLlCBrpAyIAJxUrDDhaPgkABmAAsVDQunsHxAMjkimUFw02l0BmMZgsSwauB+YVwiAADFRNCixGQkJ8AL4UdDYPEEYhk8FuPD+VpHYL1eyLJ5Y1YQ0SOXz+UiBYKhcKRaJxWIvN4fT6EkC/f6AxAAdnBkJg0I5zxxYDxACYifgSTRyIgAGxUmk4PCEEjkJn0JhsTg8Y5ws4I9RXFG3dEPTH2BxC1JijKS7K5fJzEplSrVWo8xrNIodLqTaYDEDDUbKCa9fqzFo8bmclZs9aClIitLizJSnI7PYHd6e07nX3Im5o+6PCuyiHvJAARnxiuVtgBwLB1C10NhnZ9lx7qLuGJT2KVuKQxphpr0pIto+t1OotPtDKd1GZiBhRXZW/Lyz51dDtdF6QlWWlQ90HyjgArECPx/NOqoavOpBQngupYi8HAGkgQEmmaZIEjaF52veDqMreLr3ow+jaoQUB8MGlZrAKH7Cl+DZRtKAB0cDag4nQQgARoMjAANYwBgZQccw3GULwRBsIIMBlFKCziRAfT/iOiCnqOYEqsCmowdqcE6fqeKoQe6EWgAHFhmA4fSjovDQhEPmyvDwVyvCUW+NFhnWEY/k2MpUHKe6jt8SrgTO6pabB95OduSF4iZaFHuaSD4uZl64deNl3iAxGkdAFFbm5Gy0eG36NtGLFsZ0UwwAA7jx/GCbwlXsDVnSRBJrBSTJMRyUQClQEpHyGoaUFTqFoHQRFrx6TuyGIHFRkJRhZnnhZdJ4TetmLo+jnPi5+VVu5n71pGv45ANe6fPNo2QeFOmRVuiG7ipakLcwx5JSllnrRldnZQCuV7RWBU1nRx3eWVULsfOom5PVQnQ50clSrwjAANSjrw16sBAzDkediBAvilrqRBmkTXdICwfpY4vcSi2mZ9a3pc6W0OVFL68gdhUefRJ0+fjQJAVdIU3eTOoPdTKlznTb2JZhK2pVZ+GbUwJH/eRgOvlzIPFQxp2xCxgicXAMjsJxjjCTDfECfDXGtdIbCsJxb28WUjDtZ1v58Ak8S9X0cmMN7vt9QLlrE8FGlfLdOpGyboTm49s2jtLh6y0tjNXtZLNMFg/ramQfAe9Jv4CyZc7XSh0d4EXid4sn8VpxahpnraTNZwR0JgAY8C5xs0hFBAvw7BJpD960g/FwAwgPvwvFAEAiAg95OPgMC8AABqycAT+vvD6NAgi/Lw3RFvAUSOa2R+wJxgi2LYGRj9vR8AvIvDsF4cB7OwrC6Lw1h/6vHYAApZgEkVCmwKI/Cee8/Cml2HAfQx8sywCgLwTiGBeDVRgJxNBPRqqsVIHAJiOwdgrzXvvKAh8150DbGgLwaBqoQEcjgEQ7B+DsBEP3fYkg/h0MQCQsAAAqXgABBDe09x6/F3iIH+cAvDVWqPgGB6sP6CE0FELwm8Z4wCYtjWwgd16RC0ZInRZAeikAMVkVBxin46OqroMAliX5HBENENBa9BCsVQf/aqoQaB/yYcjAActAHRAArD+IQ5D6GIUI0RvBbDY2dqwDeW8d5v21mPdhthBCyG8Uw3x1Q17/2QOvE+Gw5EsTQPPQQaB14AF1GD4DQGgLAcBEAAHoOmQFgBEpiNhbAdPnovDpP8aDBAALRECGkxWgHTmBYHYB08p8AiHNP0KwAAxCsuRwQaloD4NEKAOxSk7KIXssxDSmktLaZ07poS+kDKGQvOAozTiTOmYaWZ8zFnLJ6BUtZaANnbP+asi5pBSDcCYrwMhCSklsFSdo6Rbjza8E8TAVBCj/q1Lftw94D8ARr3Xj06SaSpF7wPr8WJOwAD6gjBEAHUHEZHpTSsosLEkQGSVAo+XKwl+DQAAci8GrMiXhdBrwNIosgj937uDQKwDAOw4AYFEPgHokBPG8A4PxABa9OJ4IIaIrQRh6GrwwbIVi+hOK/EiJAUeEq9UYK3nK3hiqdhcFVZoDVEAtU6rXo7TGhLR4hNgExCJO0TiGGhWoYpq9eClMgP4yQSCF6rN4EYDp5QrnNNae0rpJLHkwWeSMsZ8A0BTJmXMhZSyzlMXWVs5gEyk0wAmZICZZyJkSD4PwGwOx96yAyb20g+h5AtGpWAAAorQAwWAj6eIJfGzl3KbE734WAHY68t0RJ2GSnRejGCCtXqwbGmCbCsCgIK7gABuHYXTeATDCHw3gx7T2MOUFASI/89m+rQLu7RuiID6KPTAE9TCACkcBBWREFe+i9V7b1gHvY+sAz7X0FPPZ+gJO19n/pMUxMxNhGAGmqrwSdEKiOCoZYQfIbRHIQFFQ/Z2qDTRYBwAaS93Ab13o6Q+8UdDXAUdHiRORWIrE7Rdn/I8xSmHgtIOu+9vAyNCbKNRiAtHIjb0Y+ENBuMX0LPYxinjSmTOv2QDAdq9TEBARUkBYzJmohHHAaEAoTFSDCBMCvd+EjNooxJYgIg+gVJAkNCpEy3B7OmaOOUTi/KxBuY82ALzcAfNuD86EgLQXCafAJuFyLSnX7+afWQLurA/lprkR0ugfhaktHVGqFSnx8uOYTRZtg9SJm+IM2QK0iAhrNdfm11QECjjEYy8V0gpXysAqq7QGrcx1SWitPiCLSHeMOcK+N1DJW2AdNHWEKr7UaVeVsH1oEBMBFbyOF3EiiReBUe/ik7wXKwjbzAIKxDe6mL2Mm4wdeAARaI9hR4ABJgA3ZgBSAAhLwQH4QyBQ/XtxtbfGn3tNh0DmVDLHs+Be+PMAMO4fA6h1+2T1SzGbq3QI6ds752fx04S8RSLpCyPaQIrd68d2iG2r+u7UIrBRpUAxmAhBqqB0+9tMxfPtQC+iULkiovxd4eCHvDAEjbF3ZIzynR6uJ55FqZEMxyP+1q4AwekDYGz0foQ8ZlDaHQNvsw6TzGtSTe66peb9DvBIPQfu3BzjiHkP8fR17/3zvf1u4A4RixWvyPmMPWpjT9HtO2F0yx7rHGr3I6D2jsoyA482FUzRtpmnhcAiY3p1jhmoD1OdxTjd3OHIQ7uw9k9uPOKvckB9yP+GfuOIB5j0H4Pu7Q4x/D0giPs+8bt+jonWOcfPY7/jwng+SfYfrxzmyDQl7IGQFNbcyBt65I2I03Ntyun3wBEbJi/h9D3N6a8klHSbVco6Z875HBOIdK+xE7gIB6m16vDDgfBAiXQkyhRQTDiTR7q1xjiGQyzvR9YZxpTtwqxESioAzu77pAbZSib2ClC/iRBMTEGxgtBsDXCohdQYDID1I9R9QozoyYyOjYy4x/5UDDJLwgAz7Ybrw/q1K7xYpKIkYxTQoACyh84gc6AaMEggJEqGXgriUgqKucciGKkQgheqOw7ChCRw6KqCXAeqSCewugGCImcAYmRyUQbeuMUA1QZBKSehGijksc4gaAtWkgw8kkZ8n8VQP8o8JSZS/G/AjAQI3AOaNydyo6YAao/SxaSEggcyURHaWIryURQIfyT6/ATEQI9aQKrAq2jAjOkIshvC4qg6gaKhXi2GpStWrATEw6o6aABip+Ny+a9+MARagyHBby4yFa7+1avytRuRwKtRDR8gYxFakIPAUK7O26cAyuLivqqGd2QEEu+G5u/gwgaAZQ4Gl6kQmxqGU+qO8hZQBx2xvAqE2GfBf6jetigGwGZxiAvuZxRxM+pxSx5xlx361SEeYAHOAisaCa68QxExzR1yeadyhaRCTy3RZaHyVaPySyQxDamyoxNgjRExEyUxPaNgFKg6YQEx46W+tgO+e+9OGwLwRA+ITEo4NJE4/+gB/kiAIIRM4Bqo40UBFMeisBtmDciB44yBSsG0mUjAucNw+ckK4kXhVB+MIIzcbJlcYs1cXhPJwEfJcshMFIgB/gsAeArYhwvAwAHY8Iqg3YFBAY/YlEvAFIAgPQiCgqAAAsEIILYWXswB0hEhMtYIPLxNUB0rUVBohgsTtBWHdkuCaYiH6L2BuEGFuErg3vegoN4AADJlDLJYjKpbjlRNGCpcS+7CrajiDhBQZHFJmpm8Dpn2CbJcQJDgZDSsQtIZBwB1mGgCJRTZmHpVTVT5m2ChAB7GZllpm5xVk1ktkNlFkkktlDBdm1lDS9l9AtltlZlQiHp5k3oVm8YFmNnFmCoCL3q+CyzYamgPDSBwL2DzFsgViGzGymzmyrkiS+7uxeFByGk7BKZfbm5OAiSnmY5QBPFtTPmIYUg3ovBqzMBICgBuB/CfySB4ARIgAUgUhAA=="} import { createUrlSearchParamsInHashStorage } from '@studiometa/js-toolkit/utils'; const storage = createUrlSearchParamsInHashStorage(); diff --git a/packages/docs/utils/storage/createUrlSearchParamsStorage.md b/packages/docs/utils/storage/createUrlSearchParamsStorage.md index a162cfb94..28bfe05a3 100644 --- a/packages/docs/utils/storage/createUrlSearchParamsStorage.md +++ b/packages/docs/utils/storage/createUrlSearchParamsStorage.md @@ -9,7 +9,7 @@ By default, changes use `replaceState` to avoid polluting browser history. Use ` ## Usage ```js twoslash -// @twoslash-cache: {"v":1,"hash":"ce3d72adcb5a29910c2e6b9ca5bd5acb90b2b11f8456ecadee4cf57518fef382","data":"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="} +// @twoslash-cache: {"v":1,"hash":"f85048a3671a6c4d8d2fb78901c9be2140f242533e6f0dc1be854627e42939d4","data":"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"} import { createUrlSearchParamsStorage } from '@studiometa/js-toolkit/utils'; const storage = createUrlSearchParamsStorage(); @@ -32,7 +32,7 @@ storage.subscribe('page', (value) => { ### With history entries ```js twoslash -// @twoslash-cache: {"v":1,"hash":"21ad4beced16aa2d1776036762090197dc10b86eb40add40575d99b98d43b7fe","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvQXBgBVUqwDKMZqRH4ACmuYBbOAB4AKrzo0wUOLwBKMERFJQDcNKXZgA5hV7MwGAHy8ALw2dg5OLm6e3r4B/owQWOKScAD8iLwA8rrsaAZKaA7MHjCZSRJghkb+3gA6IFikxOywpPWBAGS8CqyaTUQtZGXJldwZBUUlAJKVaL4iMMb+tWDsulgOaNKyPSpqGtqkeghUUBAiCIggAMKkqjQ+3dYAMnvqWjr60uIcmAB0lGoxUuyGQDU+gMSIysEyOJR8Fiez14jWarV4UIqcABAF0KGC7mhBKRKpQwIJWKwcXiQC41AxEABOKisGCeND4JAARgAHFQ5qQSgyQDJ5Io3gdPicQBwwLhEAAGKgaHRiMhIRkAXwo6Gw8oIxHV/LMeHss14LkmMHGhThMBmdNEi1s9kczlc7i8CLigLppAZXK5ADYWWyPBykAB2flqIV4S12wGy+UAZmV+FVNHIiCD2t1ODwhBI5GN9CYbE4PG2YuUqneh2OBhd4XdUS9sX88UxKXSvEY2Vy+VtxVK5RSTbCbsinpifk7dQa/UGbRAnW6ij6aKGY9GvAAPtILDB+O4YFAxrxYSOHXMnRPXREPdFvZ2VmsNv7q7s65KjvpAWcFx4Lc9wwI8cgvBKHx/lYgg/LkGAAjGHggmCWAQlQ3aVJew7wr4UBIiiS7olh2IgHiBIwESJInOSlLUlQfoBgATCGMphhGiC8jGgpUXgorfvs0HHEmp5IGmIAqkcarZsGebUHqhaGiW1AmlcjCojg/oYHwPSbgMrTDFifxYDI+C9gARhAECsr4+6HrAJ5ylAAHnJcIAAOr4FR3mkLwhTVrwAAGplwPgBTMDQQW8O4LiqARED8MFdxYKwzALBFUVIUCKFIKCICOcwFIMFQ/BsLI5E0kx3IACzRux7KcogNU8XGVyhZyLJiYgElSelWbiZGmo0vYsB4O+my8MAX7ij+wlfJqAhNLovAAOQAAIuIIUASLoVHMAA9AAVnAAC0hQ2QA1rkB1wewrBwKtADcKwrAdB28AAoul+AWlRjB8CIdyRfAjxygA7rw+DsAmGCmGArgYCsZouBauFgSEAmzUJDb6Iw00dRkriCGBmrcE9gJ7XMSCgGYbJwBUeAnSAmqakAA==="} +// @twoslash-cache: {"v":1,"hash":"320313ee3aa1bb57931d68e8d1c2fd0ee297488ad7d6d775e6821dee5a4e3d6f","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvEaRjMaAVVKsAynNIj8ABWalmAWzgq0EPQHMYAHgAqvOjTBQ4vAEowRpqJbhpS7MGYUvMxgGAB8vAC8ru6e3r7+gcGhYWGMEFjiknAA/Ii8APL67GiWxqbMFgWZEmBwQQA6IFikxOywpE0RAGS8SqxarUTtZNVZddz55eYwAJJ1aCEiVtZhDWDs+limaNKy8jD9arqaOnqG05W4VD66DIgAnFSsMAFo+EgATAAcVIukFnuIBkckUymOGm0ugMRhMM0oIA4YFwiAADFRNNCxGQkA8AL4UdDYFEEYg4v72PAeBa8HwVCxTOFXea3URWNweUheHx+AJBELhMII26ke4ARk+ADZnq8zO8kAB2P66QF4Onw57+FEAZgx+CxNHIiElBKJODwhBI5Ap9CYbE4PD2oMO4PUp2hFyZFksHLiPMS/JSaQy41y+SKJTKXpgY1q9V4TRabQ6XV4vX6g2Toxq2UmvEuFhZizZPtiXPivKSAtS60221FToORzdUPOsPp1xAIvF2uliNl8sQP2VAJgQJBTddJ1bMILnaROr1BpxiDFJsJ1GJFrJ1uolMQIEYSZwoowfAzQxGpFj2QAdFhBHB8HleAAjCAQF4hXgAH14wlgfgtSgYV/nFSUlX7N4PkQAAWEdVQPB8nwRBckF1YF9T0bEjVghU8QAXQxaASTrHZeGARswVUFszlnaNeDxARWn0XgAHIAAEfEEKAJH0MdmAAegAKzgABaExPwAaxKQTBHEVg4DYgBudZ1kEwTeAAUWYTRaTHRg+AnGhnGYXhkQAd14fB2HVDA7DAXwMHWakfFpBjomMl0aOnOjPQ7RhKOQ/B8l8QQYEY7hlIRfjFiQUB7FeOBajwUSQDxPEgA"} import { createUrlSearchParamsStorage } from '@studiometa/js-toolkit/utils'; // Each set() creates a new history entry diff --git a/packages/docs/utils/storage/providers.md b/packages/docs/utils/storage/providers.md index 9445cce50..8a7745ead 100644 --- a/packages/docs/utils/storage/providers.md +++ b/packages/docs/utils/storage/providers.md @@ -30,7 +30,7 @@ Use the storage-backed factories when you want a fresh provider instance instead The URL-based factories accept a `push` option to customize navigation behavior. They check for a browser context when the provider is created. If `window.location` or `window.history` is unavailable, they return a no-op provider instead of throwing. ```js twoslash -// @twoslash-cache: {"v":1,"hash":"90ef5b3acd98c5ace6e14b8466f5416ce19de7d1fba0875819c96dfe5a9a7b0b","data":"N4Igdg9gJgpgziAXAbVAFwJ4AcZJACwgDcYAnEAGhDRgA808AKAQwBsBLZuASgAIAzAK5gAxmnYQwvEaRjMaAVVKsAynNIj8ABWalmAWzhbSxdrFKMsJombIB5LOMlwA/Il5LWx0+YdOwPO4qaBB6AOYw3jbmADpg7PpYoWjSsvIwnmq6mjp6hlG25FRQECIIiCAAwmk0vMweAEoAMlka2roGcLxWPmQAdJTUzGHlyMggWB36gz3R9o4SAbx+i138obxo+DDd1oUDALoU47JogqQBlGCCrKwHRyBwaLoMiACMb1SsMGBhW0gAJgAnFRnqQIq8QDI5IplK0clMjHtzIMOGBcO8qJoOmIyICAL4UdDYDEEYh40F0SEsDhcPhCUT+VIwjJw9QIvJwACSYAAElxtMiyJYhaQVs43B5lAVfAtnNwgiFwpFRXEEklSCloelMuz2pyefy4ILekUQCUynhqiy6o0Wnrcp1eOwpPgBbtTQNQcNRuNJnkZqLxUtg2sNlsdrN9iAjicYGcLggKNdbvcqE8XkgACxZr4/P74JAAZgArN7wfG8NrYaoHYjDQKZRSQGiMQCsW69LjyIgAYTiTg8IQSGaaPQq84UlhBMam6RFaFhirTQAeJ6kF1hAB8gwzmqQbwAHJ8W/n/u920MK5Dp7PRaiXRiAGwdnE0HtP/vUElD8mjqlMGwnA8MyOpstk+qdHOIqmqGkqeHOoYKrwwSLhEc5qokySgTW8KQfk97FKU5RVDUOz1AozR4Y6hgenMpBekMIxIGMExTIGsFyiGXFhqQmzbHR0axiApznJcyY3HcDx7q8bxAi+p6/OewLlhCVZkbqEE0UipoPuiSAKdiXbvoCbxfpgg4VMOzZjtSPQ4JqGB8AhQY8X0t74JKABGEAQN8zBSAAPrwwiwPwj5QIMFokQA6tsEZ8SEIVwDsAAGHnBOkqXOgENDMFAvAQPwvCpbIWCsMwIgwJlNCpYxzzMSg4xhcwNwMFQ/BsClMbSWCrwAgCh55kphaILmV5qRUHl6c+r7GXiiAlkW5k/lZf6DLZE65d0M74EaJr0QuypzmuaAbr8O7pn1gJZiCikFgeClgpNEy7ftc4zUgADsc2VSZiCfkS36WWSI4bQBFQ0sBfDVqytZafWfKNqKMH0XB7guZx/iBChSpLhh8RYZqOFw9RiPvYR5rEVaZG2pR9oI5yOW8G6xqCeY9U+ixfrsVQUaytjyw8QI4YCfz/QxscInxmJSYplJV2Zr2T5Dfd56lqplYVLDmltNpDZ3rpXyPt9v3doCJYrSD1n/uOkP2WQmDOdKrnY+5u3eb5/lBSFYBhRFUXUxUcXxtsiUQMlaUZc8tU5U8cgFUVJVlRVVU1TAdUbVzTXmjAnVtYMnWsN1aaPNdiBFgADAp3wjdmms3rtn2ID9UKdn9C1PstDwiNApLqthwBxLwJO6xyUGihQw+j+Bevk8jppT2A+ICCY+i8AA5AAAk8ghQBI+jxswAD0ABWcAALQhH5ADW7BoMfgjiMXG8ANxxHEx/Hx4KVdNH6RxzyonYqEZeAtTarwFOlVqoxxgHEXu20PJzl4AAXhnvDOenJoLAB2sadwZ1BA7HxNwd+ohJy4L2gveiqD0FkwNEjQ29FGA4I8vg0ghDeDENfoMQ+zwkCgCpD8OAiw8DnxAPifEQA=="} +// @twoslash-cache: {"v":1,"hash":"30cf328271aeb3e255906ba6983a8e307a2d394630a0ec98df4f6b582ee409ed","data":"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="} import { createLocalStorageProvider, createMemoryStorageProvider, diff --git a/packages/eslint-plugin-js-toolkit/README.md b/packages/eslint-plugin-js-toolkit/README.md index fa11b982c..b33490890 100644 --- a/packages/eslint-plugin-js-toolkit/README.md +++ b/packages/eslint-plugin-js-toolkit/README.md @@ -123,19 +123,19 @@ export default [ ### Emits -| Rule | Description | Recommended | Fixable | -| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | -| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | error | ๐Ÿ”ง | -| `js-toolkit/emits-multi-word` | Requires emit names in `config.emits` to be multi-word to avoid collisions with native DOM events (e.g. `item-click` not `click`). | error | | -| `js-toolkit/require-emit-declared-in-config` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | error | | +| Rule | Description | Recommended | Fixable | +| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | error | ๐Ÿ”ง | +| `js-toolkit/emits-multi-word` | Requires emit names in `config.emits` to be multi-word to avoid collisions with native DOM events (e.g. `item-click` not `click`). | error | | +| `js-toolkit/require-emit-declared-in-config` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | error | | ### Components -| Rule | Description | Recommended | Fixable | -| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------- | ------- | -| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | error | ๐Ÿ”ง | -| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | error | | -| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | error | | +| Rule | Description | Recommended | Fixable | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------- | ----------- | ------- | +| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | error | ๐Ÿ”ง | +| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | error | | +| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | error | | ### Refs diff --git a/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts index c0630ab50..456ecf673 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/components-pascal-case.ts @@ -52,8 +52,7 @@ export const componentsPascalCase = createRule({ description: 'Require component keys in static config to be PascalCase', }, messages: { - notPascalCase: - 'Component key "{{name}}" must be PascalCase (e.g. "MyComponent").', + notPascalCase: 'Component key "{{name}}" must be PascalCase (e.g. "MyComponent").', }, }, createOnce(context: RuleContext) { diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts index d87a3b446..230168385 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-emit-declared-in-config.ts @@ -38,7 +38,8 @@ export const requireEmitDeclaredInConfig = createRule({ meta: { type: 'problem', docs: { - description: 'Require all this.$emit() calls to use event names declared in static config.emits', + description: + 'Require all this.$emit() calls to use event names declared in static config.emits', }, messages: { undeclared: 'Emit "{{name}}" is not declared in static config.emits.', diff --git a/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts index 21306ccc7..38a864bd4 100644 --- a/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts +++ b/packages/eslint-plugin-js-toolkit/src/rules/require-options-declared-in-config.ts @@ -1,4 +1,10 @@ -import { isBaseSubclass, toCamelCase, type Node, type RuleContext, createRule } from '../utils/ast.ts'; +import { + isBaseSubclass, + toCamelCase, + type Node, + type RuleContext, + createRule, +} from '../utils/ast.ts'; function collectDeclaredOptions(classNode: Node): Set { const body: Node[] = classNode.body?.body ?? []; From 50b19af0ebb656aac9ce993dc776f4bbfef06c0f Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Tue, 12 May 2026 12:11:56 +0200 Subject: [PATCH 4/5] Restructure docs linting page with level-4 headings per rule --- packages/docs/guide/going-further/linting.md | 190 +++++++++++++++---- 1 file changed, 148 insertions(+), 42 deletions(-) diff --git a/packages/docs/guide/going-further/linting.md b/packages/docs/guide/going-further/linting.md index 679113edc..d1f1af042 100644 --- a/packages/docs/guide/going-further/linting.md +++ b/packages/docs/guide/going-further/linting.md @@ -85,66 +85,172 @@ export default [ ### Class structure -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------- | -| `js-toolkit/require-config` | Requires a `static config` property with a `name` on every class extending `Base`. | โŒ | -| `js-toolkit/require-config-name-pascal-case` | Requires `config.name` to be PascalCase. | ๐Ÿ”ง | +#### `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  ยท  **Fixable** ๐Ÿ”ง + +Requires `config.name` to be PascalCase. ### Refs -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/refs-camel-case` | Requires ref names in `config.refs` to be camelCase. Supports the `[]` multiple-ref suffix. | ๐Ÿ”ง | -| `js-toolkit/refs-plural-multiple` | Requires refs using the `[]` multiple-ref suffix to be pluralized (e.g. `links[]` not `link[]`). | โŒ | -| `js-toolkit/refs-no-bracket-access` | 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` | Requires all `this.$refs.` accesses to be declared in `static config.refs`. | โŒ | -| `js-toolkit/prefer-ref-over-query-selector` | 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. | โŒ | +#### `js-toolkit/refs-camel-case` + +**Recommended:** error  ยท  **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  ยท  **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.` 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 -| Rule | Description | Fixable | -| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/options-camel-case` | Requires option keys in `config.options` to be camelCase. | ๐Ÿ”ง | -| `js-toolkit/require-options-declared-in-config` | Requires all `this.$options.` accesses to be declared in `static config.options`. | โŒ | +#### `js-toolkit/options-camel-case` + +**Recommended:** error  ยท  **Fixable** ๐Ÿ”ง + +Requires option keys in `config.options` to be camelCase. + +#### `js-toolkit/require-options-declared-in-config` + +**Recommended:** error + +Requires all `this.$options.` accesses to be declared in `static config.options`. ### Emits -| Rule | Description | Fixable | -| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/emits-kebab-case` | Requires emit names in `config.emits` to be kebab-case (e.g. `content-change`). | ๐Ÿ”ง | -| `js-toolkit/emits-multi-word` | 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` | Requires all `this.$emit('name')` calls to use event names declared in `static config.emits`. | โŒ | +#### `js-toolkit/emits-kebab-case` + +**Recommended:** error  ยท  **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 -| Rule | Description | Fixable | -| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/components-pascal-case` | Requires component keys in `config.components` to be PascalCase. | ๐Ÿ”ง | -| `js-toolkit/require-children-declared-in-config` | Requires all `this.$children.` accesses to be declared in `static config.components`. | โŒ | +#### `js-toolkit/components-pascal-case` + +**Recommended:** error  ยท  **Fixable** ๐Ÿ”ง + +Requires component keys in `config.components` to be PascalCase. + +#### `js-toolkit/require-children-declared-in-config` + +**Recommended:** error + +Requires all `this.$children.` accesses to be declared in `static config.components`. ### Lifecycle methods -| Rule | Description | Fixable | -| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/async-lifecycle-methods` | Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. | ๐Ÿ”ง | +#### `js-toolkit/async-lifecycle-methods` + +**Recommended:** error  ยท  **Fixable** ๐Ÿ”ง + +Requires lifecycle methods (`mounted`, `destroyed`, `updated`, `terminated`, `ticked`, `scrolled`, `resized`, `moved`, `loaded`, `keyed`) to be declared `async`. ### Event handlers -| Rule | Description | Fixable | -| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------- | -| `js-toolkit/on-handler-naming` | Requires event handler methods to follow the `onXxxYyy` camelCase convention. | โŒ | -| `js-toolkit/on-global-handler-prefix` | 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 | -| ---------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `js-toolkit/no-deprecated-properties` | Disallows deprecated properties (`$parent`, `$root`, `$children`). Use `$closest()` or `$query()` instead. | โŒ | -| `js-toolkit/no-dispatch-event` | Disallows `dispatchEvent()` inside `Base` subclasses. Use `this.$emit()` instead. | โŒ | -| `js-toolkit/no-shadow-dom` | Disallows `attachShadow()` inside `Base` subclasses. The framework uses Light DOM only. | โŒ | -| `js-toolkit/no-create-app` | Disallows `createApp()` (deprecated). Use `registerComponent()` instead. | โŒ | -| `js-toolkit/no-event-listener-methods` | Disallows `addEventListener()` and `removeEventListener()` inside `Base` subclasses. Define `on*` methods instead โ€” the framework handles binding and cleanup automatically. | โŒ | -| `js-toolkit/no-deep-utils-import` | 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` | Disallows wrapping `withMountWhenInView` inside `withScrolledInView` โ€” the latter already includes the former internally. | โŒ | -| `js-toolkit/no-manual-intersection-observer` | Disallows `new IntersectionObserver()` inside `Base` subclasses. Use `withIntersectionObserver` or `withMountWhenInView` decorators instead. | โŒ | -| `js-toolkit/no-manual-mutation-observer` | Disallows `new MutationObserver()` inside `Base` subclasses. Use the `withMutation` decorator instead. | โŒ | +#### `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  ยท  **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. From f53d185dac24a4ae88473842b7fbc84298cf576c Mon Sep 17 00:00:00 2001 From: Titouan Mathis Date: Tue, 12 May 2026 13:29:40 +0200 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8ba18bba..f65013837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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