diff --git a/packages/eslint-plugin-qwik/index.ts b/packages/eslint-plugin-qwik/index.ts index 88ddea738e0..1d43733c3e4 100644 --- a/packages/eslint-plugin-qwik/index.ts +++ b/packages/eslint-plugin-qwik/index.ts @@ -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; @@ -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 = { @@ -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 = { @@ -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 = { diff --git a/packages/eslint-plugin-qwik/src/noAsyncPreventDefault.ts b/packages/eslint-plugin-qwik/src/noAsyncPreventDefault.ts new file mode 100644 index 00000000000..858914374f4 --- /dev/null +++ b/packages/eslint-plugin-qwik/src/noAsyncPreventDefault.ts @@ -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', + }, + }, + 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; + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default-arrow.tsx b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default-arrow.tsx new file mode 100644 index 00000000000..a590e6f5f2a --- /dev/null +++ b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default-arrow.tsx @@ -0,0 +1,13 @@ +// Expect error: { "messageId": "noAsyncPreventDefault" } +import { $ } from '@builder.io/qwik'; + +export const InvalidPreventDefaultArrow = () => { + const handleSubmit = $((event: SubmitEvent) => { + event.preventDefault(); + }); + return ( +
+ +
+ ); +}; diff --git a/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default.tsx b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default.tsx new file mode 100644 index 00000000000..cda2eaa57f8 --- /dev/null +++ b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/invalid-prevent-default.tsx @@ -0,0 +1,14 @@ +// Expect error: { "messageId": "noAsyncPreventDefault" } +import { $ } from '@builder.io/qwik'; + +export const NoAsyncPreventDefault = () => { + return ( +
{ + event.preventDefault(); + })} + > + +
+ ); +}; diff --git a/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default-arrow.tsx b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default-arrow.tsx new file mode 100644 index 00000000000..d45194be29d --- /dev/null +++ b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default-arrow.tsx @@ -0,0 +1,12 @@ +import { sync$ } from '@builder.io/qwik'; + +export const ValidPreventDefaultTest = () => { + const handleSubmit = sync$((event: SubmitEvent) => { + event.preventDefault(); + }); + return ( +
+ +
+ ); +}; diff --git a/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default.tsx b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default.tsx new file mode 100644 index 00000000000..c643cdf9573 --- /dev/null +++ b/packages/eslint-plugin-qwik/tests/no-async-prevent-default/valid-prevent-default.tsx @@ -0,0 +1,13 @@ +import { sync$ } from '@builder.io/qwik'; + +export const ValidPreventDefaultTest = () => { + return ( +
{ + event.preventDefault(); + })} + > + +
+ ); +};