Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/eslint-plugin-qwik/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { preferClasslist } from './src/preferClasslist';
import { unusedServer } from './src/unusedServer';
import { useMethodUsage } from './src/useMethodUsage';
import { validLexicalScope } from './src/validLexicalScope';
import { noAsyncPreventDefault } from './src/noAsyncPreventDefault';
import pkg from './package.json';

type Rules = NonNullable<TSESLint.FlatConfig.Plugin['rules']>;
Expand All @@ -26,6 +27,7 @@ const rules = {
'jsx-img': jsxImg,
'jsx-a': jsxAtag,
'no-use-visible-task': noUseVisibleTask,
'no-async-prevent-default': noAsyncPreventDefault,
} satisfies Rules;

const recommendedRulesLevels = {
Expand All @@ -40,6 +42,7 @@ const recommendedRulesLevels = {
'qwik/jsx-img': 'warn',
'qwik/jsx-a': 'warn',
'qwik/no-use-visible-task': 'warn',
'qwik/no-async-prevent-default': 'warn',
} satisfies TSESLint.FlatConfig.Rules;

const strictRulesLevels = {
Expand All @@ -54,6 +57,7 @@ const strictRulesLevels = {
'qwik/jsx-img': 'error',
'qwik/jsx-a': 'error',
'qwik/no-use-visible-task': 'warn',
'qwik/no-async-prevent-default': 'warn',
} satisfies TSESLint.FlatConfig.Rules;

const configs = {
Expand Down
38 changes: 38 additions & 0 deletions packages/eslint-plugin-qwik/src/noAsyncPreventDefault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Rule } from 'eslint';

export const noAsyncPreventDefault: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'Detect preventDefault in $(()=>{}) Async Functions.',
recommended: true,
url: 'https://qwik.dev/docs/components/events/#preventdefault--stoppropagation',
},
messages: {
noAsyncPreventDefault:
'This is an asynchronous function and does not support preventDefault. \nUse preventDefault attributes instead',
Copy link
Contributor

@maiieul maiieul Jul 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a link to the docs? Also I think this should suggest the use of sync$ too. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Maiieul, I think it's a great idea to include sync$ also

Is this a more agreeable message?

This is an asynchronous function and does not support preventDefault.
Use sync$ or prevent:default attributes instead.
Documentation: https://qwik.dev/docs/components/events/#preventdefault--stoppropagation
sync$: https://qwik.dev/docs/cookbook/sync-events/

image

Once agreed I'll commit and push up the message to avoid too many commits for editing the message.

Follow-up question:
Is a changeset needed for this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good.

Do note that it's preventdefault:click etc, not what you wrote. Furthermore, those props only work if there is also a handler defined, in this case onClick$. Perhaps that should be classified as a bug though.

},
},
create(context) {
return {
"CallExpression[callee.property.name='preventDefault']"(node) {
let parent = node.parent;
while (parent) {
if (
parent.type === 'CallExpression' &&
parent.callee &&
parent.callee.type === 'Identifier' &&
parent.callee.name === '$'
) {
context.report({
node,
messageId: 'noAsyncPreventDefault',
});
break;
}
parent = parent.parent;
}
},
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Expect error: { "messageId": "noAsyncPreventDefault" }
import { $ } from '@builder.io/qwik';

export const InvalidPreventDefaultArrow = () => {
const handleSubmit = $((event: SubmitEvent) => {
event.preventDefault();
});
return (
<form onSubmit$={handleSubmit}>
<button type="submit">Hello World</button>
</form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Expect error: { "messageId": "noAsyncPreventDefault" }
import { $ } from '@builder.io/qwik';

export const NoAsyncPreventDefault = () => {
return (
<form
onSubmit$={$((event: SubmitEvent) => {
event.preventDefault();
})}
>
<button type="submit">Hello World</button>
</form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { sync$ } from '@builder.io/qwik';

export const ValidPreventDefaultTest = () => {
const handleSubmit = sync$((event: SubmitEvent) => {
event.preventDefault();
});
return (
<form onSubmit$={handleSubmit}>
<button type="submit">Hello World</button>
</form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { sync$ } from '@builder.io/qwik';

export const ValidPreventDefaultTest = () => {
return (
<form
onSubmit$={sync$((event: SubmitEvent) => {
event.preventDefault();
})}
>
<button type="submit">Hello World</button>
</form>
);
};
Loading