diff --git a/packages/docs/src/routes/docs/(qwik)/advanced/eslint/index.mdx b/packages/docs/src/routes/docs/(qwik)/advanced/eslint/index.mdx index b188d797430..d8cfaad36ef 100644 --- a/packages/docs/src/routes/docs/(qwik)/advanced/eslint/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/advanced/eslint/index.mdx @@ -36,18 +36,18 @@ import './styles.css';

Possible Problems

These rules are available.

- +
use-method-usage - Object destructuring is not recommended for component$ + Detect invalid use of use hooks.
@@ -62,7 +62,7 @@ import './styles.css';
- +
valid-lexical-scope @@ -73,7 +73,7 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': false, - }} + }} > ✅ @@ -88,18 +88,18 @@ import './styles.css';
- +
loader-location - Detect declaration location of loader$ + Detect declaration location of loader$.
@@ -114,7 +114,7 @@ import './styles.css';
- +
no-react-props @@ -125,7 +125,7 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': false, - }} + }} > ✅ @@ -140,7 +140,7 @@ import './styles.css';
- +
prefer-classlist @@ -151,7 +151,7 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': true, - }} + }} > ✅ @@ -166,7 +166,7 @@ import './styles.css';
- +
jsx-no-script-url @@ -177,7 +177,7 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': true, - }} + }} > ✅ @@ -192,7 +192,7 @@ import './styles.css';
- +
jsx-key @@ -203,7 +203,7 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': true, - }} + }} > ✅ @@ -218,18 +218,18 @@ import './styles.css';
- +
unused-server - Detect unused server$() functions + Detect unused server$() functions.
@@ -244,7 +244,7 @@ import './styles.css';
- +
jsx-img @@ -255,7 +255,33 @@ import './styles.css'; class={{ 'icon': false, 'icon icon-inactive': true, - }} + }} + > + ✅ + + + 🔔 + +
+
+ + + +
+ jsx-a + For a perfect SEO score, always provide href attribute for <a> elements. +
+
+ @@ -276,11 +302,9 @@ import './styles.css';

use-method-usage

- Object destructuring is not recommended for component$ + Detect invalid use of use hooks. -

use-after-await

-
-

use-wrong-function

+

useWrongFunction

Examples of correct code for this rule:

@@ -290,34 +314,13 @@ export const Counter = component$(() => { }); ```
-

Examples of incorrect code for this rule:

-
- -```tsx {2} /component$/#a -export const Counter = (() => { - const count = useSignal(0); -}); -``` -

`use*` methods can just be used in `component$` functions.

-
-
- -```tsx {2} /component$/#a -export const Counter = (() => { - const count = useSignal(0); -}); -``` -

`use*` methods can just be used in `component$` functions.

-
- -

use-not-root

-

Examples of correct code for this rule:

```tsx {2} /component$/#a -export const Counter = component$(() => { +export const useCounter = () => { const count = useSignal(0); -}); + return count; +}; ```

Examples of incorrect code for this rule:

@@ -328,16 +331,7 @@ export const Counter = (() => { const count = useSignal(0); }); ``` -

`use*` methods can just be used in `component$` functions.

-
-
- -```tsx {2} /component$/#a -export const Counter = (() => { - const count = useSignal(0); -}); -``` -

`use*` methods can just be used in `component$` functions.

+

`use*` methods can just be used in `component$` functions or inside `use*` hooks (e.g. `useCounter`).

@@ -467,7 +461,7 @@ export const HelloWorld = component$(() => {

loader-location

- Detect declaration location of loader$ + Detect declaration location of loader$.

invalidLoaderLocation

Examples of correct code for this rule:

@@ -935,7 +929,7 @@ export const ColorList = component$(() => {

unused-server

- Detect unused server$() functions + Detect unused server$() functions.

unusedServer

Examples of correct code for this rule:

@@ -1036,5 +1030,15 @@ import Image from '~/media/image.png';
+ +
+

jsx-a

+ For a perfect SEO score, always provide href attribute for <a> elements. + +

noHref

+ + +
+
\ No newline at end of file diff --git a/packages/eslint-plugin-qwik/qwik.unit.ts b/packages/eslint-plugin-qwik/qwik.unit.ts index 330412010d7..522c93c2ac8 100644 --- a/packages/eslint-plugin-qwik/qwik.unit.ts +++ b/packages/eslint-plugin-qwik/qwik.unit.ts @@ -31,164 +31,95 @@ const ruleTester = new RuleTester(testConfig as any); ruleTester.run('use-method-usage', rules['use-method-usage'] as any, { valid: [ ` -export function useSession1() { - useContext(); -} -export function useSession2() { - return useContext(); -} -export function useSession3() { - return useContext().value; -} -`, + export function useSession1() { + useContext(); + } + export function useSession2() { + return useContext(); + } + export function useSession3() { + return useContext().value; + } + `, ` -export const useSession1 = () => { - useContext(); -} + export const useSession1 = () => { + useContext(); + } -export const useSession2 = () => { - return useContext(); -} + export const useSession2 = () => { + return useContext(); + } -export const useSession3 = () => useContext(); + export const useSession3 = () => useContext(); -export const useSession4 = () => useContext().value; + export const useSession4 = () => useContext().value; -export const useSession5 = () => useContext().value + 10; + export const useSession5 = () => useContext().value + 10; -`, + `, ` -export const useSession1 = () => { - useContext()?.value; -} - -export const useSession2 = () => { - return useContext()?.value; -} + export const useSession1 = () => { + useContext()?.value; + } -export const useSession3 = () => useContext()?.value; + export const useSession2 = () => { + return useContext()?.value; + } -export const useSession4 = () => useContext()?.value; + export const useSession3 = () => useContext()?.value; -export const useSession5 = () => useContext()?.value; + 10; - -`, - ` -export const HelloWorld = component$(async () => { - const [todoForm, { Form, Field, FieldArray }] = useForm({ - loader: useFormLoader(), - action: useFormAction(), - validate: zodForm$(todoSchema), - }); + export const useSession4 = () => useContext()?.value; - }); - `, - ` - export const HelloWorld = component$(async () => { - useMethod(); - await something(); - let a; - a = 2; - return $(() => { - return
{a}
- }); - }); - const A = () => { console.log('A') }; - export const B = () => { - A(); - } - `, - `export const HelloWorld = component$(async () => { - useMethod(); - await something(); - await stuff(); - return $(() => { - return
- }); - });`, - `export const HelloWorld = component$(async () => { - const test = useFunction() as string; + export const useSession5 = () => useContext()?.value; + 10; - }); - `, + `, `export const InsideTask = component$(() => { - const mySig = useSignal(0); - useTask$(async function initTask(){ - if (isServer){ - await fetch('/url'); - } - }) + const mySig = useSignal(0); + useTask$(async function initTask(){ + if (isServer){ + await fetch('/url'); + } + }) - useTask$(({track})=>{ - track(()=> mySig.value); - }) - return
; - });`, + useTask$(({track})=>{ + track(()=> mySig.value); + }) + return
; + });`, ], invalid: [ - { - code: `export const HelloWorld = component$(async () => { - await something(); - useMethod(); - return $(() => { - return ( -
- {prop} -
- ); - }); - });`, - errors: [{ messageId: 'use-after-await' }], - }, - { - code: `export const HelloWorld = component$(async () => { - if (stuff) { - await something(); - } - useMethod(); - return $(() => { - return ( -
- {prop} -
- ); - }); - });`, - errors: [{ messageId: 'use-after-await' }], - }, - { code: `export function noUseSession() { useContext(); }`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, { code: `export const noUseSession = () => { useContext(); }`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, { code: `export const noUseSession = () => { return useContext(); }`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, { code: `export const noUseSession = () => useContext();`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, { code: `export const noUseSession = () => useContext().value;`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, { code: `export const noUseSession = () => { return useContext(); }`, - errors: [{ messageId: 'use-wrong-function' }], + errors: [{ messageId: 'useWrongFunction' }], }, ], }); diff --git a/packages/eslint-plugin-qwik/src/jsxKey.ts b/packages/eslint-plugin-qwik/src/jsxKey.ts index 3595e99436a..22a8a050894 100644 --- a/packages/eslint-plugin-qwik/src/jsxKey.ts +++ b/packages/eslint-plugin-qwik/src/jsxKey.ts @@ -16,7 +16,7 @@ const defaultOptions = { }; const messages = { - missingIterKey: 'Missing "key" prop for element in iterator', + missingIterKey: 'Missing "key" prop for element in iterator.', missingIterKeyUsePrag: 'Missing "key" prop for element in iterator. The key prop allows for improved rendering performance. Shorthand fragment syntax does not support providing keys. Use instead', missingArrayKey: @@ -60,8 +60,7 @@ export const jsxKey = { }, create(context) { - const modifyJsxSource = context - .getSourceCode() + const modifyJsxSource = context.sourceCode .getAllComments() .some((c) => c.value.includes('@jsxImportSource')); if (modifyJsxSource) { diff --git a/packages/eslint-plugin-qwik/src/loaderLocation.ts b/packages/eslint-plugin-qwik/src/loaderLocation.ts index 1424c46d1ab..4f36e981d60 100644 --- a/packages/eslint-plugin-qwik/src/loaderLocation.ts +++ b/packages/eslint-plugin-qwik/src/loaderLocation.ts @@ -19,7 +19,7 @@ export const loaderLocation: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Detect declaration location of loader$', + description: 'Detect declaration location of loader$.', recommended: true, url: 'https://qwik.builder.io/docs/advanced/eslint/#loader-location', }, diff --git a/packages/eslint-plugin-qwik/src/noReactProps.ts b/packages/eslint-plugin-qwik/src/noReactProps.ts index fc35ecae9b7..21b258cc5f8 100644 --- a/packages/eslint-plugin-qwik/src/noReactProps.ts +++ b/packages/eslint-plugin-qwik/src/noReactProps.ts @@ -25,8 +25,7 @@ export const noReactProps = { }, }, create(context) { - const modifyJsxSource = context - .getSourceCode() + const modifyJsxSource = context.sourceCode .getAllComments() .some((c) => c.value.includes('@jsxImportSource')); if (modifyJsxSource) { diff --git a/packages/eslint-plugin-qwik/src/preferClasslist.ts b/packages/eslint-plugin-qwik/src/preferClasslist.ts index 7d9aba68518..642e85e98e1 100644 --- a/packages/eslint-plugin-qwik/src/preferClasslist.ts +++ b/packages/eslint-plugin-qwik/src/preferClasslist.ts @@ -39,8 +39,7 @@ export const preferClasslist = { }, }, create(context) { - const modifyJsxSource = context - .getSourceCode() + const modifyJsxSource = context.sourceCode .getAllComments() .some((c) => c.value.includes('@jsxImportSource')); if (modifyJsxSource) { diff --git a/packages/eslint-plugin-qwik/src/unusedServer.ts b/packages/eslint-plugin-qwik/src/unusedServer.ts index 8a3aacca3ed..414bbfd07d5 100644 --- a/packages/eslint-plugin-qwik/src/unusedServer.ts +++ b/packages/eslint-plugin-qwik/src/unusedServer.ts @@ -6,7 +6,7 @@ export const unusedServer: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Detect unused server$() functions', + description: 'Detect unused server$() functions.', recommended: true, url: 'https://qwik.builder.io/docs/advanced/eslint/#unused-server', }, diff --git a/packages/eslint-plugin-qwik/src/useMethodUsage.ts b/packages/eslint-plugin-qwik/src/useMethodUsage.ts index 524865e7475..4fd687d696a 100644 --- a/packages/eslint-plugin-qwik/src/useMethodUsage.ts +++ b/packages/eslint-plugin-qwik/src/useMethodUsage.ts @@ -7,53 +7,24 @@ export const useMethodUsage: Rule.RuleModule = { meta: { type: 'problem', docs: { - description: 'Object destructuring is not recommended for component$', + description: 'Detect invalid use of use hooks.', category: 'Variables', recommended: true, url: 'https://qwik.builder.io/docs/advanced/eslint/#use-method-usage', }, messages: { - 'use-after-await': 'Calling use* methods after await is not safe.', - 'use-wrong-function': 'Calling use* methods in wrong function.', - 'use-not-root': 'Calling use* methods in non-root component.', + useWrongFunction: 'Calling use* methods in wrong function.', }, }, create(context) { - const modifyJsxSource = context - .getSourceCode() + const modifyJsxSource = context.sourceCode .getAllComments() .some((c) => c.value.includes('@jsxImportSource')); if (modifyJsxSource) { return {}; } - const stack: { await: boolean }[] = []; return { - ArrowFunctionExpression() { - stack.push({ await: false }); - }, - 'ArrowFunctionExpression:exit'(d) { - stack.pop(); - }, - FunctionExpression() { - stack.push({ await: false }); - }, - 'FunctionExpression:exit'(d) { - stack.pop(); - }, - AwaitExpression() { - const last = stack[stack.length - 1]; - if (last) { - last.await = true; - } - }, 'CallExpression[callee.name=/^use[A-Z]/]'(node: CallExpression & Rule.NodeParentExtension) { - const last = stack[stack.length - 1]; - if (last && last.await) { - context.report({ - node, - messageId: 'use-after-await', - }); - } let parent = node as Rule.Node; while ((parent = parent.parent)) { const type = parent.type; @@ -92,21 +63,21 @@ export const useMethodUsage: Rule.RuleModule = { } context.report({ node, - messageId: 'use-wrong-function', + messageId: 'useWrongFunction', }); return; case 'FunctionDeclaration': if (!parent.id?.name.startsWith('use')) { context.report({ node, - messageId: 'use-wrong-function', + messageId: 'useWrongFunction', }); } return; default: context.report({ node, - messageId: 'use-not-root', + messageId: 'useWrongFunction', }); return; // ERROR @@ -123,60 +94,37 @@ export const Counter = component$(() => { }); `.trim(); -const useWrongFunctionBad = ` -export const Counter = (() => { +const useWrongFunctionGood2 = ` +export const useCounter = () => { const count = useSignal(0); -}); + return count; +}; `.trim(); -const useWrongFunctionBad2 = ` +const useWrongFunctionBad = ` export const Counter = (() => { const count = useSignal(0); }); `.trim(); -const useNotRootGood = useWrongFunctionGood; -const useNotRootBad = useWrongFunctionBad; -const useNotRootBad2 = useWrongFunctionBad2; - export const useMethodUsageExamples: QwikEslintExamples = { - 'use-wrong-function': { + useWrongFunction: { good: [ { codeHighlight: '{2} /component$/#a', code: useWrongFunctionGood, }, - ], - bad: [ { codeHighlight: '{2} /component$/#a', - code: useWrongFunctionBad, - description: '`use*` methods can only be used in `component$` functions.', - }, - { - codeHighlight: '{2} /component$/#a', - code: useWrongFunctionBad2, - description: '`use*` methods can only be used in `component$` functions.', - }, - ], - }, - 'use-not-root': { - good: [ - { - codeHighlight: '{2} /component$/#a', - code: useNotRootGood, + code: useWrongFunctionGood2, }, ], bad: [ { codeHighlight: '{2} /component$/#a', - code: useNotRootBad, - description: '`use*` methods can only be used in `component$` functions.', - }, - { - codeHighlight: '{2} /component$/#a', - code: useNotRootBad2, - description: '`use*` methods can only be used in `component$` functions.', + code: useWrongFunctionBad, + description: + '`use*` methods can only be used in `component$` functions or inside `use*` hooks (e.g. `useCounter`).', }, ], }, diff --git a/packages/eslint-plugin-qwik/src/validLexicalScope.ts b/packages/eslint-plugin-qwik/src/validLexicalScope.ts index a110786d0c1..b7a4061b318 100644 --- a/packages/eslint-plugin-qwik/src/validLexicalScope.ts +++ b/packages/eslint-plugin-qwik/src/validLexicalScope.ts @@ -57,7 +57,7 @@ export const validLexicalScope = createRule({ const opts: DetectorOptions = { allowAny, }; - const scopeManager = context.getSourceCode().scopeManager!; + const scopeManager = context.sourceCode.scopeManager!; const services = ESLintUtils.getParserServices(context); const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap; const typeChecker = services.program.getTypeChecker(); @@ -199,7 +199,7 @@ export const validLexicalScope = createRule({ varName: firstArg.expression.name, solution: `const ${firstArg.expression.name} = $(\n${getContent( type.symbol, - context.getSourceCode().text + context.sourceCode.text )}\n);\n`, }, }); @@ -345,7 +345,7 @@ function _isTypeCapturable( if (level === 0 && ts.isIdentifier(node)) { const solution = `const ${node.text} = $(\n${getContent( type.symbol, - context.getSourceCode().text + context.sourceCode.text )}\n);`; reason += `.\nDid you mean to wrap it in \`$()\`?\n\n${solution}\n`; }