Skip to content

Commit c3248ac

Browse files
committed
feat(require-event-prefix): implemented the rule
1 parent 86115a6 commit c3248ac

File tree

6 files changed

+135
-0
lines changed

6 files changed

+135
-0
lines changed

Diff for: .changeset/rich-dogs-design.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat: added the `require-event-prefix` rule

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
395395
| [svelte/no-spaces-around-equal-signs-in-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-spaces-around-equal-signs-in-attribute/) | disallow spaces around equal signs in attribute | :wrench: |
396396
| [svelte/prefer-class-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
397397
| [svelte/prefer-style-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
398+
| [svelte/require-event-prefix](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-prefix/) | require component event names to start with "on" | |
398399
| [svelte/shorthand-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
399400
| [svelte/shorthand-directive](https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/) | enforce use of shorthand syntax in directives | :wrench: |
400401
| [svelte/sort-attributes](https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/) | enforce order of attributes | :wrench: |

Diff for: docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
9292
| [svelte/no-spaces-around-equal-signs-in-attribute](./rules/no-spaces-around-equal-signs-in-attribute.md) | disallow spaces around equal signs in attribute | :wrench: |
9393
| [svelte/prefer-class-directive](./rules/prefer-class-directive.md) | require class directives instead of ternary expressions | :wrench: |
9494
| [svelte/prefer-style-directive](./rules/prefer-style-directive.md) | require style directives instead of style attribute | :wrench: |
95+
| [svelte/require-event-prefix](./rules/require-event-prefix.md) | require component event names to start with "on" | |
9596
| [svelte/shorthand-attribute](./rules/shorthand-attribute.md) | enforce use of shorthand syntax in attribute | :wrench: |
9697
| [svelte/shorthand-directive](./rules/shorthand-directive.md) | enforce use of shorthand syntax in directives | :wrench: |
9798
| [svelte/sort-attributes](./rules/sort-attributes.md) | enforce order of attributes | :wrench: |

Diff for: packages/eslint-plugin-svelte/src/rule-types.ts

+9
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ export interface RuleOptions {
306306
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/
307307
*/
308308
'svelte/require-event-dispatcher-types'?: Linter.RuleEntry<[]>
309+
/**
310+
* require component event names to start with "on"
311+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-prefix/
312+
*/
313+
'svelte/require-event-prefix'?: Linter.RuleEntry<SvelteRequireEventPrefix>
309314
/**
310315
* require style attributes that can be optimized
311316
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/
@@ -531,6 +536,10 @@ type SveltePreferConst = []|[{
531536
destructuring?: ("any" | "all")
532537
ignoreReadBeforeAssign?: boolean
533538
}]
539+
// ----- svelte/require-event-prefix -----
540+
type SvelteRequireEventPrefix = []|[{
541+
checkAsyncFunctions?: boolean
542+
}]
534543
// ----- svelte/shorthand-attribute -----
535544
type SvelteShorthandAttribute = []|[{
536545
prefer?: ("always" | "never")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { createRule } from '../utils/index.js';
2+
import { type TSTools, getTypeScriptTools } from 'src/utils/ts-utils/index.js';
3+
import {
4+
type MethodSignature,
5+
type Symbol,
6+
SymbolFlags,
7+
SyntaxKind,
8+
type Type,
9+
type TypeReferenceNode,
10+
type PropertySignature
11+
} from 'typescript';
12+
import type { CallExpression } from 'estree';
13+
14+
export default createRule('require-event-prefix', {
15+
meta: {
16+
docs: {
17+
description: 'require component event names to start with "on"',
18+
category: 'Stylistic Issues',
19+
conflictWithPrettier: false,
20+
recommended: false
21+
},
22+
schema: [
23+
{
24+
type: 'object',
25+
properties: {
26+
checkAsyncFunctions: {
27+
type: 'boolean'
28+
}
29+
},
30+
additionalProperties: false
31+
}
32+
],
33+
messages: {
34+
nonPrefixedFunction: 'Component event name must start with "on".'
35+
},
36+
type: 'suggestion',
37+
conditions: [
38+
{
39+
svelteVersions: ['5'],
40+
svelteFileTypes: ['.svelte']
41+
}
42+
]
43+
},
44+
create(context) {
45+
const tsTools = getTypeScriptTools(context);
46+
if (!tsTools) {
47+
return {};
48+
}
49+
50+
const checkAsyncFunctions = context.options[0]?.checkAsyncFunctions ?? false;
51+
52+
return {
53+
CallExpression(node) {
54+
const propsType = getPropsType(node, tsTools);
55+
if (propsType === undefined) {
56+
return;
57+
}
58+
for (const property of propsType.getProperties()) {
59+
if (isFunctionLike(property) && !property.getName().startsWith('on')) {
60+
if (!checkAsyncFunctions && isFunctionAsync(property)) {
61+
continue;
62+
}
63+
context.report({
64+
node,
65+
messageId: 'nonPrefixedFunction'
66+
});
67+
}
68+
}
69+
}
70+
};
71+
}
72+
});
73+
74+
function getPropsType(node: CallExpression, tsTools: TSTools): Type | undefined {
75+
if (
76+
node.callee.type !== 'Identifier' ||
77+
node.callee.name !== '$props' ||
78+
node.parent.type !== 'VariableDeclarator'
79+
) {
80+
return undefined;
81+
}
82+
83+
const tsNode = tsTools.service.esTreeNodeToTSNodeMap.get(node.parent.id);
84+
if (tsNode === undefined) {
85+
return undefined;
86+
}
87+
88+
const checker = tsTools.service.program.getTypeChecker();
89+
return checker.getTypeAtLocation(tsNode);
90+
}
91+
92+
function isFunctionLike(functionSymbol: Symbol): boolean {
93+
return (
94+
(functionSymbol.getFlags() & SymbolFlags.Method) !== 0 ||
95+
(functionSymbol.valueDeclaration?.kind === SyntaxKind.PropertySignature &&
96+
(functionSymbol.valueDeclaration as PropertySignature).type?.kind === SyntaxKind.FunctionType)
97+
);
98+
}
99+
100+
function isFunctionAsync(functionSymbol: Symbol): boolean {
101+
return (
102+
functionSymbol.getDeclarations()?.some((declaration) => {
103+
if (declaration.kind !== SyntaxKind.MethodSignature) {
104+
return false;
105+
}
106+
const declarationType = (declaration as MethodSignature).type;
107+
if (declarationType?.kind !== SyntaxKind.TypeReference) {
108+
return false;
109+
}
110+
const declarationTypeName = (declarationType as TypeReferenceNode).typeName;
111+
return (
112+
declarationTypeName.kind === SyntaxKind.Identifier &&
113+
declarationTypeName.escapedText === 'Promise'
114+
);
115+
}) ?? false
116+
);
117+
}

Diff for: packages/eslint-plugin-svelte/src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import preferDestructuredStoreProps from '../rules/prefer-destructured-store-pro
6060
import preferStyleDirective from '../rules/prefer-style-directive.js';
6161
import requireEachKey from '../rules/require-each-key.js';
6262
import requireEventDispatcherTypes from '../rules/require-event-dispatcher-types.js';
63+
import requireEventPrefix from '../rules/require-event-prefix.js';
6364
import requireOptimizedStyleAttribute from '../rules/require-optimized-style-attribute.js';
6465
import requireStoreCallbacksUseSetParam from '../rules/require-store-callbacks-use-set-param.js';
6566
import requireStoreReactiveAccess from '../rules/require-store-reactive-access.js';
@@ -133,6 +134,7 @@ export const rules = [
133134
preferStyleDirective,
134135
requireEachKey,
135136
requireEventDispatcherTypes,
137+
requireEventPrefix,
136138
requireOptimizedStyleAttribute,
137139
requireStoreCallbacksUseSetParam,
138140
requireStoreReactiveAccess,

0 commit comments

Comments
 (0)