@pyreon/lint 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +91 -91
  2. package/lib/analysis/cli.js.html +5406 -0
  3. package/lib/analysis/index.js.html +1 -1
  4. package/lib/cli.js +3290 -0
  5. package/lib/cli.js.map +1 -0
  6. package/lib/index.js +220 -29
  7. package/lib/index.js.map +1 -1
  8. package/lib/types/index.d.ts +30 -5
  9. package/lib/types/index.d.ts.map +1 -1
  10. package/package.json +19 -19
  11. package/src/cache.ts +1 -1
  12. package/src/cli.ts +39 -28
  13. package/src/config/ignore.ts +23 -23
  14. package/src/config/loader.ts +8 -8
  15. package/src/config/presets.ts +11 -11
  16. package/src/index.ts +14 -12
  17. package/src/lint.ts +19 -25
  18. package/src/lsp/index.ts +225 -0
  19. package/src/reporter.ts +17 -17
  20. package/src/rules/accessibility/dialog-a11y.ts +10 -10
  21. package/src/rules/accessibility/overlay-a11y.ts +11 -11
  22. package/src/rules/accessibility/toast-a11y.ts +11 -11
  23. package/src/rules/architecture/dev-guard-warnings.ts +19 -19
  24. package/src/rules/architecture/no-circular-import.ts +16 -16
  25. package/src/rules/architecture/no-cross-layer-import.ts +35 -35
  26. package/src/rules/architecture/no-deep-import.ts +7 -7
  27. package/src/rules/architecture/no-error-without-prefix.ts +20 -20
  28. package/src/rules/form/no-submit-without-validation.ts +13 -13
  29. package/src/rules/form/no-unregistered-field.ts +12 -12
  30. package/src/rules/form/prefer-field-array.ts +11 -11
  31. package/src/rules/hooks/no-raw-addeventlistener.ts +9 -9
  32. package/src/rules/hooks/no-raw-localstorage.ts +11 -11
  33. package/src/rules/hooks/no-raw-setinterval.ts +11 -11
  34. package/src/rules/index.ts +60 -57
  35. package/src/rules/jsx/no-and-conditional.ts +8 -8
  36. package/src/rules/jsx/no-children-access.ts +12 -12
  37. package/src/rules/jsx/no-classname.ts +10 -10
  38. package/src/rules/jsx/no-htmlfor.ts +10 -10
  39. package/src/rules/jsx/no-index-as-by.ts +17 -17
  40. package/src/rules/jsx/no-map-in-jsx.ts +9 -9
  41. package/src/rules/jsx/no-missing-for-by.ts +9 -9
  42. package/src/rules/jsx/no-onchange.ts +12 -12
  43. package/src/rules/jsx/no-props-destructure.ts +11 -11
  44. package/src/rules/jsx/no-ternary-conditional.ts +8 -8
  45. package/src/rules/jsx/use-by-not-key.ts +12 -12
  46. package/src/rules/lifecycle/no-dom-in-setup.ts +18 -18
  47. package/src/rules/lifecycle/no-effect-in-mount.ts +11 -11
  48. package/src/rules/lifecycle/no-missing-cleanup.ts +19 -19
  49. package/src/rules/lifecycle/no-mount-in-effect.ts +11 -11
  50. package/src/rules/performance/no-eager-import.ts +7 -7
  51. package/src/rules/performance/no-effect-in-for.ts +10 -10
  52. package/src/rules/performance/no-large-for-without-by.ts +9 -9
  53. package/src/rules/performance/prefer-show-over-display.ts +16 -16
  54. package/src/rules/reactivity/no-bare-signal-in-jsx.ts +10 -10
  55. package/src/rules/reactivity/no-context-destructure.ts +45 -0
  56. package/src/rules/reactivity/no-effect-assignment.ts +16 -16
  57. package/src/rules/reactivity/no-nested-effect.ts +10 -10
  58. package/src/rules/reactivity/no-peek-in-tracked.ts +10 -10
  59. package/src/rules/reactivity/no-signal-in-loop.ts +13 -13
  60. package/src/rules/reactivity/no-signal-leak.ts +9 -9
  61. package/src/rules/reactivity/no-unbatched-updates.ts +12 -12
  62. package/src/rules/reactivity/prefer-computed.ts +13 -13
  63. package/src/rules/router/index.ts +4 -4
  64. package/src/rules/router/no-href-navigation.ts +14 -14
  65. package/src/rules/router/no-imperative-navigate-in-render.ts +19 -19
  66. package/src/rules/router/no-missing-fallback.ts +16 -16
  67. package/src/rules/router/prefer-use-is-active.ts +11 -11
  68. package/src/rules/ssr/no-mismatch-risk.ts +11 -11
  69. package/src/rules/ssr/no-window-in-ssr.ts +22 -22
  70. package/src/rules/ssr/prefer-request-context.ts +14 -14
  71. package/src/rules/store/no-duplicate-store-id.ts +9 -9
  72. package/src/rules/store/no-mutate-store-state.ts +11 -11
  73. package/src/rules/store/no-store-outside-provider.ts +15 -15
  74. package/src/rules/styling/no-dynamic-styled.ts +13 -13
  75. package/src/rules/styling/no-inline-style-object.ts +10 -10
  76. package/src/rules/styling/no-theme-outside-provider.ts +11 -11
  77. package/src/rules/styling/prefer-cx.ts +12 -12
  78. package/src/runner.ts +13 -14
  79. package/src/tests/lsp.test.ts +88 -0
  80. package/src/tests/runner.test.ts +325 -325
  81. package/src/types.ts +15 -15
  82. package/src/utils/ast.ts +50 -50
  83. package/src/utils/imports.ts +53 -53
  84. package/src/utils/index.ts +12 -3
  85. package/src/utils/source.ts +2 -2
  86. package/src/watcher.ts +19 -25
@@ -1,75 +1,77 @@
1
- import type { Rule } from "../types"
2
- import { dialogA11y } from "./accessibility/dialog-a11y"
3
- import { overlayA11y } from "./accessibility/overlay-a11y"
1
+ import type { Rule } from '../types'
2
+ import { dialogA11y } from './accessibility/dialog-a11y'
3
+ import { overlayA11y } from './accessibility/overlay-a11y'
4
4
  // Accessibility
5
- import { toastA11y } from "./accessibility/toast-a11y"
6
- import { devGuardWarnings } from "./architecture/dev-guard-warnings"
5
+ import { toastA11y } from './accessibility/toast-a11y'
6
+ import { devGuardWarnings } from './architecture/dev-guard-warnings'
7
7
  // Architecture
8
- import { noCircularImport } from "./architecture/no-circular-import"
9
- import { noCrossLayerImport } from "./architecture/no-cross-layer-import"
10
- import { noDeepImport } from "./architecture/no-deep-import"
11
- import { noErrorWithoutPrefix } from "./architecture/no-error-without-prefix"
12
- import { noSubmitWithoutValidation } from "./form/no-submit-without-validation"
8
+ import { noCircularImport } from './architecture/no-circular-import'
9
+ import { noCrossLayerImport } from './architecture/no-cross-layer-import'
10
+ import { noDeepImport } from './architecture/no-deep-import'
11
+ import { noErrorWithoutPrefix } from './architecture/no-error-without-prefix'
12
+ import { noSubmitWithoutValidation } from './form/no-submit-without-validation'
13
13
  // Form
14
- import { noUnregisteredField } from "./form/no-unregistered-field"
15
- import { preferFieldArray } from "./form/prefer-field-array"
14
+ import { noUnregisteredField } from './form/no-unregistered-field'
15
+ import { preferFieldArray } from './form/prefer-field-array'
16
16
  // Hooks
17
- import { noRawAddEventListener } from "./hooks/no-raw-addeventlistener"
18
- import { noRawLocalStorage } from "./hooks/no-raw-localstorage"
19
- import { noRawSetInterval } from "./hooks/no-raw-setinterval"
20
- import { noAndConditional } from "./jsx/no-and-conditional"
21
- import { noChildrenAccess } from "./jsx/no-children-access"
22
- import { noClassName } from "./jsx/no-classname"
23
- import { noHtmlFor } from "./jsx/no-htmlfor"
24
- import { noIndexAsBy } from "./jsx/no-index-as-by"
17
+ import { noRawAddEventListener } from './hooks/no-raw-addeventlistener'
18
+ import { noRawLocalStorage } from './hooks/no-raw-localstorage'
19
+ import { noRawSetInterval } from './hooks/no-raw-setinterval'
20
+ import { noAndConditional } from './jsx/no-and-conditional'
21
+ import { noChildrenAccess } from './jsx/no-children-access'
22
+ import { noClassName } from './jsx/no-classname'
23
+ import { noHtmlFor } from './jsx/no-htmlfor'
24
+ import { noIndexAsBy } from './jsx/no-index-as-by'
25
25
  // JSX
26
- import { noMapInJsx } from "./jsx/no-map-in-jsx"
27
- import { noMissingForBy } from "./jsx/no-missing-for-by"
28
- import { noOnChange } from "./jsx/no-onchange"
29
- import { noPropsDestructure } from "./jsx/no-props-destructure"
30
- import { noTernaryConditional } from "./jsx/no-ternary-conditional"
31
- import { useByNotKey } from "./jsx/use-by-not-key"
32
- import { noDomInSetup } from "./lifecycle/no-dom-in-setup"
33
- import { noEffectInMount } from "./lifecycle/no-effect-in-mount"
26
+ import { noMapInJsx } from './jsx/no-map-in-jsx'
27
+ import { noMissingForBy } from './jsx/no-missing-for-by'
28
+ import { noOnChange } from './jsx/no-onchange'
29
+ import { noPropsDestructure } from './jsx/no-props-destructure'
30
+ import { noTernaryConditional } from './jsx/no-ternary-conditional'
31
+ import { useByNotKey } from './jsx/use-by-not-key'
32
+ import { noDomInSetup } from './lifecycle/no-dom-in-setup'
33
+ import { noEffectInMount } from './lifecycle/no-effect-in-mount'
34
34
  // Lifecycle
35
- import { noMissingCleanup } from "./lifecycle/no-missing-cleanup"
36
- import { noMountInEffect } from "./lifecycle/no-mount-in-effect"
37
- import { noEagerImport } from "./performance/no-eager-import"
38
- import { noEffectInFor } from "./performance/no-effect-in-for"
35
+ import { noMissingCleanup } from './lifecycle/no-missing-cleanup'
36
+ import { noMountInEffect } from './lifecycle/no-mount-in-effect'
37
+ import { noEagerImport } from './performance/no-eager-import'
38
+ import { noEffectInFor } from './performance/no-effect-in-for'
39
39
  // Performance
40
- import { noLargeForWithoutBy } from "./performance/no-large-for-without-by"
41
- import { preferShowOverDisplay } from "./performance/prefer-show-over-display"
40
+ import { noLargeForWithoutBy } from './performance/no-large-for-without-by'
41
+ import { preferShowOverDisplay } from './performance/prefer-show-over-display'
42
42
  // Reactivity
43
- import { noBareSignalInJsx } from "./reactivity/no-bare-signal-in-jsx"
44
- import { noEffectAssignment } from "./reactivity/no-effect-assignment"
45
- import { noNestedEffect } from "./reactivity/no-nested-effect"
46
- import { noPeekInTracked } from "./reactivity/no-peek-in-tracked"
47
- import { noSignalInLoop } from "./reactivity/no-signal-in-loop"
48
- import { noSignalLeak } from "./reactivity/no-signal-leak"
49
- import { noUnbatchedUpdates } from "./reactivity/no-unbatched-updates"
50
- import { preferComputed } from "./reactivity/prefer-computed"
43
+ import { noBareSignalInJsx } from './reactivity/no-bare-signal-in-jsx'
44
+ import { noContextDestructure } from './reactivity/no-context-destructure'
45
+ import { noEffectAssignment } from './reactivity/no-effect-assignment'
46
+ import { noNestedEffect } from './reactivity/no-nested-effect'
47
+ import { noPeekInTracked } from './reactivity/no-peek-in-tracked'
48
+ import { noSignalInLoop } from './reactivity/no-signal-in-loop'
49
+ import { noSignalLeak } from './reactivity/no-signal-leak'
50
+ import { noUnbatchedUpdates } from './reactivity/no-unbatched-updates'
51
+ import { preferComputed } from './reactivity/prefer-computed'
51
52
  // Router
52
- import { noHrefNavigation } from "./router/no-href-navigation"
53
- import { noImperativeNavigateInRender } from "./router/no-imperative-navigate-in-render"
54
- import { noMissingFallback } from "./router/no-missing-fallback"
55
- import { preferUseIsActive } from "./router/prefer-use-is-active"
56
- import { noMismatchRisk } from "./ssr/no-mismatch-risk"
53
+ import { noHrefNavigation } from './router/no-href-navigation'
54
+ import { noImperativeNavigateInRender } from './router/no-imperative-navigate-in-render'
55
+ import { noMissingFallback } from './router/no-missing-fallback'
56
+ import { preferUseIsActive } from './router/prefer-use-is-active'
57
+ import { noMismatchRisk } from './ssr/no-mismatch-risk'
57
58
  // SSR
58
- import { noWindowInSsr } from "./ssr/no-window-in-ssr"
59
- import { preferRequestContext } from "./ssr/prefer-request-context"
60
- import { noDuplicateStoreId } from "./store/no-duplicate-store-id"
61
- import { noMutateStoreState } from "./store/no-mutate-store-state"
59
+ import { noWindowInSsr } from './ssr/no-window-in-ssr'
60
+ import { preferRequestContext } from './ssr/prefer-request-context'
61
+ import { noDuplicateStoreId } from './store/no-duplicate-store-id'
62
+ import { noMutateStoreState } from './store/no-mutate-store-state'
62
63
  // Store
63
- import { noStoreOutsideProvider } from "./store/no-store-outside-provider"
64
- import { noDynamicStyled } from "./styling/no-dynamic-styled"
64
+ import { noStoreOutsideProvider } from './store/no-store-outside-provider'
65
+ import { noDynamicStyled } from './styling/no-dynamic-styled'
65
66
  // Styling
66
- import { noInlineStyleObject } from "./styling/no-inline-style-object"
67
- import { noThemeOutsideProvider } from "./styling/no-theme-outside-provider"
68
- import { preferCx } from "./styling/prefer-cx"
67
+ import { noInlineStyleObject } from './styling/no-inline-style-object'
68
+ import { noThemeOutsideProvider } from './styling/no-theme-outside-provider'
69
+ import { preferCx } from './styling/prefer-cx'
69
70
 
70
71
  export const allRules: Rule[] = [
71
- // Reactivity (8)
72
+ // Reactivity (9)
72
73
  noBareSignalInJsx,
74
+ noContextDestructure,
73
75
  noSignalInLoop,
74
76
  noNestedEffect,
75
77
  noPeekInTracked,
@@ -144,6 +146,7 @@ export {
144
146
  noAndConditional,
145
147
  // Reactivity
146
148
  noBareSignalInJsx,
149
+ noContextDestructure,
147
150
  noChildrenAccess,
148
151
  // Architecture
149
152
  noCircularImport,
@@ -1,12 +1,12 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isLogicalAndWithJSX } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isLogicalAndWithJSX } from '../../utils/ast'
3
3
 
4
4
  export const noAndConditional: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-and-conditional",
7
- category: "jsx",
8
- description: "Prefer <Show> over `&&` with JSX in expression containers.",
9
- severity: "warn",
6
+ id: 'pyreon/no-and-conditional',
7
+ category: 'jsx',
8
+ description: 'Prefer <Show> over `&&` with JSX in expression containers.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
@@ -15,14 +15,14 @@ export const noAndConditional: Rule = {
15
15
  JSXExpressionContainer() {
16
16
  jsxExpressionDepth++
17
17
  },
18
- "JSXExpressionContainer:exit"() {
18
+ 'JSXExpressionContainer:exit'() {
19
19
  jsxExpressionDepth--
20
20
  },
21
21
  LogicalExpression(node: any) {
22
22
  if (jsxExpressionDepth === 0) return
23
23
  if (!isLogicalAndWithJSX(node)) return
24
24
  context.report({
25
- message: "`&&` with JSX — use `<Show>` for conditional rendering.",
25
+ message: '`&&` with JSX — use `<Show>` for conditional rendering.',
26
26
  span: getSpan(node),
27
27
  })
28
28
  },
@@ -1,13 +1,13 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
3
- import { extractImportInfo, type ImportInfo } from "../../utils/imports"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
+ import { extractImportInfo, type ImportInfo } from '../../utils/imports'
4
4
 
5
5
  export const noChildrenAccess: Rule = {
6
6
  meta: {
7
- id: "pyreon/no-children-access",
8
- category: "jsx",
9
- description: "Inform about direct props.children access in renderer files.",
10
- severity: "info",
7
+ id: 'pyreon/no-children-access',
8
+ category: 'jsx',
9
+ description: 'Inform about direct props.children access in renderer files.',
10
+ severity: 'info',
11
11
  fixable: false,
12
12
  },
13
13
  create(context) {
@@ -19,7 +19,7 @@ export const noChildrenAccess: Rule = {
19
19
  const info = extractImportInfo(node)
20
20
  if (info) {
21
21
  imports.push(info)
22
- if (info.source === "@pyreon/runtime-server" || info.source === "@pyreon/runtime-dom") {
22
+ if (info.source === '@pyreon/runtime-server' || info.source === '@pyreon/runtime-dom') {
23
23
  isRendererFile = true
24
24
  }
25
25
  }
@@ -27,13 +27,13 @@ export const noChildrenAccess: Rule = {
27
27
  MemberExpression(node: any) {
28
28
  if (!isRendererFile) return
29
29
  if (
30
- node.object?.type === "Identifier" &&
31
- node.property?.type === "Identifier" &&
32
- node.property.name === "children"
30
+ node.object?.type === 'Identifier' &&
31
+ node.property?.type === 'Identifier' &&
32
+ node.property.name === 'children'
33
33
  ) {
34
34
  context.report({
35
35
  message:
36
- "Direct `props.children` access in a renderer file — children are already merged via `mergeChildrenIntoProps`.",
36
+ 'Direct `props.children` access in a renderer file — children are already merged via `mergeChildrenIntoProps`.',
37
37
  span: getSpan(node),
38
38
  })
39
39
  }
@@ -1,24 +1,24 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
3
 
4
4
  export const noClassName: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-classname",
7
- category: "jsx",
8
- description: "Use `class` instead of `className` — Pyreon uses standard HTML attributes.",
9
- severity: "error",
6
+ id: 'pyreon/no-classname',
7
+ category: 'jsx',
8
+ description: 'Use `class` instead of `className` — Pyreon uses standard HTML attributes.',
9
+ severity: 'error',
10
10
  fixable: true,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  JSXAttribute(node: any) {
15
- if (node.name?.type !== "JSXIdentifier") return
16
- if (node.name.name !== "className") return
15
+ if (node.name?.type !== 'JSXIdentifier') return
16
+ if (node.name.name !== 'className') return
17
17
  const nameSpan = getSpan(node.name)
18
18
  context.report({
19
- message: "Use `class` instead of `className` — Pyreon uses standard HTML attributes.",
19
+ message: 'Use `class` instead of `className` — Pyreon uses standard HTML attributes.',
20
20
  span: getSpan(node),
21
- fix: { span: nameSpan, replacement: "class" },
21
+ fix: { span: nameSpan, replacement: 'class' },
22
22
  })
23
23
  },
24
24
  }
@@ -1,24 +1,24 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
3
 
4
4
  export const noHtmlFor: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-htmlfor",
7
- category: "jsx",
8
- description: "Use `for` instead of `htmlFor` — Pyreon uses standard HTML attributes.",
9
- severity: "error",
6
+ id: 'pyreon/no-htmlfor',
7
+ category: 'jsx',
8
+ description: 'Use `for` instead of `htmlFor` — Pyreon uses standard HTML attributes.',
9
+ severity: 'error',
10
10
  fixable: true,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  JSXAttribute(node: any) {
15
- if (node.name?.type !== "JSXIdentifier") return
16
- if (node.name.name !== "htmlFor") return
15
+ if (node.name?.type !== 'JSXIdentifier') return
16
+ if (node.name.name !== 'htmlFor') return
17
17
  const nameSpan = getSpan(node.name)
18
18
  context.report({
19
- message: "Use `for` instead of `htmlFor` — Pyreon uses standard HTML attributes.",
19
+ message: 'Use `for` instead of `htmlFor` — Pyreon uses standard HTML attributes.',
20
20
  span: getSpan(node),
21
- fix: { span: nameSpan, replacement: "for" },
21
+ fix: { span: nameSpan, replacement: 'for' },
22
22
  })
23
23
  },
24
24
  }
@@ -1,62 +1,62 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getJSXAttribute, getSpan } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getJSXAttribute, getSpan } from '../../utils/ast'
3
3
 
4
4
  export const noIndexAsBy: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-index-as-by",
7
- category: "jsx",
8
- description: "Disallow using index as `by` prop on <For> — use a unique key instead.",
9
- severity: "warn",
6
+ id: 'pyreon/no-index-as-by',
7
+ category: 'jsx',
8
+ description: 'Disallow using index as `by` prop on <For> — use a unique key instead.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  JSXOpeningElement(node: any) {
15
15
  const name = node.name
16
- if (!name || name.type !== "JSXIdentifier" || name.name !== "For") return
16
+ if (!name || name.type !== 'JSXIdentifier' || name.name !== 'For') return
17
17
 
18
- const byAttr = getJSXAttribute(node, "by")
18
+ const byAttr = getJSXAttribute(node, 'by')
19
19
  if (!byAttr) return
20
20
 
21
21
  const value = byAttr.value
22
- if (!value || value.type !== "JSXExpressionContainer") return
22
+ if (!value || value.type !== 'JSXExpressionContainer') return
23
23
 
24
24
  const expr = value.expression
25
25
  if (!expr) return
26
26
 
27
27
  // Detect: by={(_, i) => i} or by={(item, index) => index}
28
- if (expr.type === "ArrowFunctionExpression" || expr.type === "FunctionExpression") {
28
+ if (expr.type === 'ArrowFunctionExpression' || expr.type === 'FunctionExpression') {
29
29
  const params = expr.params
30
30
  if (!params || params.length < 2) return
31
31
 
32
32
  const secondParam = params[1]
33
- if (!secondParam || secondParam.type !== "Identifier") return
33
+ if (!secondParam || secondParam.type !== 'Identifier') return
34
34
 
35
35
  const indexName = secondParam.name
36
36
  const body = expr.body
37
37
 
38
38
  // Arrow expression body: (_, i) => i
39
- if (body?.type === "Identifier" && body.name === indexName) {
39
+ if (body?.type === 'Identifier' && body.name === indexName) {
40
40
  context.report({
41
41
  message:
42
- "Using index as `by` prop on `<For>` — use a unique key from the data instead.",
42
+ 'Using index as `by` prop on `<For>` — use a unique key from the data instead.',
43
43
  span: getSpan(byAttr),
44
44
  })
45
45
  }
46
46
 
47
47
  // Block body: (_, i) => { return i }
48
- if (body?.type === "BlockStatement") {
48
+ if (body?.type === 'BlockStatement') {
49
49
  const stmts = body.body
50
50
  if (stmts?.length === 1) {
51
51
  const stmt = stmts[0]
52
52
  if (
53
- stmt.type === "ReturnStatement" &&
54
- stmt.argument?.type === "Identifier" &&
53
+ stmt.type === 'ReturnStatement' &&
54
+ stmt.argument?.type === 'Identifier' &&
55
55
  stmt.argument.name === indexName
56
56
  ) {
57
57
  context.report({
58
58
  message:
59
- "Using index as `by` prop on `<For>` — use a unique key from the data instead.",
59
+ 'Using index as `by` prop on `<For>` — use a unique key from the data instead.',
60
60
  span: getSpan(byAttr),
61
61
  })
62
62
  }
@@ -1,12 +1,12 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isArrayMapCall } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isArrayMapCall } from '../../utils/ast'
3
3
 
4
4
  export const noMapInJsx: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-map-in-jsx",
7
- category: "jsx",
8
- description: "Prefer <For> over .map() inside JSX for reactive list rendering.",
9
- severity: "warn",
6
+ id: 'pyreon/no-map-in-jsx',
7
+ category: 'jsx',
8
+ description: 'Prefer <For> over .map() inside JSX for reactive list rendering.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
@@ -15,13 +15,13 @@ export const noMapInJsx: Rule = {
15
15
  JSXElement() {
16
16
  jsxDepth++
17
17
  },
18
- "JSXElement:exit"() {
18
+ 'JSXElement:exit'() {
19
19
  jsxDepth--
20
20
  },
21
21
  JSXFragment() {
22
22
  jsxDepth++
23
23
  },
24
- "JSXFragment:exit"() {
24
+ 'JSXFragment:exit'() {
25
25
  jsxDepth--
26
26
  },
27
27
  CallExpression(node: any) {
@@ -33,7 +33,7 @@ export const noMapInJsx: Rule = {
33
33
  const callback = args[0]
34
34
  if (!callback) return
35
35
  context.report({
36
- message: "`.map()` in JSX — use `<For>` for reactive list rendering instead.",
36
+ message: '`.map()` in JSX — use `<For>` for reactive list rendering instead.',
37
37
  span: getSpan(node),
38
38
  })
39
39
  },
@@ -1,23 +1,23 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, hasJSXAttribute } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, hasJSXAttribute } from '../../utils/ast'
3
3
 
4
4
  export const noMissingForBy: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-missing-for-by",
7
- category: "jsx",
8
- description: "Warn when <For> is used without a `by` prop.",
9
- severity: "warn",
6
+ id: 'pyreon/no-missing-for-by',
7
+ category: 'jsx',
8
+ description: 'Warn when <For> is used without a `by` prop.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  JSXOpeningElement(node: any) {
15
15
  const name = node.name
16
- if (!name || name.type !== "JSXIdentifier" || name.name !== "For") return
17
- if (hasJSXAttribute(node, "by")) return
16
+ if (!name || name.type !== 'JSXIdentifier' || name.name !== 'For') return
17
+ if (hasJSXAttribute(node, 'by')) return
18
18
  context.report({
19
19
  message:
20
- "`<For>` without `by` prop — provide a key function for efficient reconciliation.",
20
+ '`<For>` without `by` prop — provide a key function for efficient reconciliation.',
21
21
  span: getSpan(node),
22
22
  })
23
23
  },
@@ -1,15 +1,15 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
3
 
4
- const INPUT_TAGS = new Set(["input", "textarea", "select"])
4
+ const INPUT_TAGS = new Set(['input', 'textarea', 'select'])
5
5
 
6
6
  export const noOnChange: Rule = {
7
7
  meta: {
8
- id: "pyreon/no-onchange",
9
- category: "jsx",
8
+ id: 'pyreon/no-onchange',
9
+ category: 'jsx',
10
10
  description:
11
- "Prefer `onInput` over `onChange` on input elements for keypress-by-keypress updates.",
12
- severity: "warn",
11
+ 'Prefer `onInput` over `onChange` on input elements for keypress-by-keypress updates.',
12
+ severity: 'warn',
13
13
  fixable: true,
14
14
  },
15
15
  create(context) {
@@ -17,7 +17,7 @@ export const noOnChange: Rule = {
17
17
  const callbacks: VisitorCallbacks = {
18
18
  JSXOpeningElement(node: any) {
19
19
  const name = node.name
20
- if (name?.type === "JSXIdentifier" && INPUT_TAGS.has(name.name)) {
20
+ if (name?.type === 'JSXIdentifier' && INPUT_TAGS.has(name.name)) {
21
21
  currentTag = name.name
22
22
  } else {
23
23
  currentTag = null
@@ -27,15 +27,15 @@ export const noOnChange: Rule = {
27
27
  const attrs = node.attributes ?? []
28
28
  for (const attr of attrs) {
29
29
  if (
30
- attr.type === "JSXAttribute" &&
31
- attr.name?.type === "JSXIdentifier" &&
32
- attr.name.name === "onChange"
30
+ attr.type === 'JSXAttribute' &&
31
+ attr.name?.type === 'JSXIdentifier' &&
32
+ attr.name.name === 'onChange'
33
33
  ) {
34
34
  const nameSpan = getSpan(attr.name)
35
35
  context.report({
36
36
  message: `Use \`onInput\` instead of \`onChange\` on \`<${currentTag}>\` for keypress-by-keypress updates.`,
37
37
  span: getSpan(attr),
38
- fix: { span: nameSpan, replacement: "onInput" },
38
+ fix: { span: nameSpan, replacement: 'onInput' },
39
39
  })
40
40
  }
41
41
  }
@@ -1,16 +1,16 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isDestructuring } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isDestructuring } from '../../utils/ast'
3
3
 
4
4
  function containsJSXReturn(node: any): boolean {
5
5
  if (!node) return false
6
6
  // Arrow with expression body returning JSX
7
- if (node.type === "JSXElement" || node.type === "JSXFragment") return true
8
- if (node.type === "ParenthesizedExpression") return containsJSXReturn(node.expression)
7
+ if (node.type === 'JSXElement' || node.type === 'JSXFragment') return true
8
+ if (node.type === 'ParenthesizedExpression') return containsJSXReturn(node.expression)
9
9
 
10
10
  // Block body — look for return statements with JSX
11
- if (node.type === "BlockStatement") {
11
+ if (node.type === 'BlockStatement') {
12
12
  for (const stmt of node.body ?? []) {
13
- if (stmt.type === "ReturnStatement" && containsJSXReturn(stmt.argument)) {
13
+ if (stmt.type === 'ReturnStatement' && containsJSXReturn(stmt.argument)) {
14
14
  return true
15
15
  }
16
16
  }
@@ -20,11 +20,11 @@ function containsJSXReturn(node: any): boolean {
20
20
 
21
21
  export const noPropsDestructure: Rule = {
22
22
  meta: {
23
- id: "pyreon/no-props-destructure",
24
- category: "jsx",
23
+ id: 'pyreon/no-props-destructure',
24
+ category: 'jsx',
25
25
  description:
26
- "Disallow destructuring props in component functions — it breaks signal reactivity.",
27
- severity: "error",
26
+ 'Disallow destructuring props in component functions — it breaks signal reactivity.',
27
+ severity: 'error',
28
28
  fixable: false,
29
29
  },
30
30
  create(context) {
@@ -57,7 +57,7 @@ function checkFunction(node: any, context: any) {
57
57
  if (containsJSXReturn(body)) {
58
58
  context.report({
59
59
  message:
60
- "Destructured props in a component function — this breaks signal reactivity. Use `props.x` or `splitProps()` instead.",
60
+ 'Destructured props in a component function — this breaks signal reactivity. Use `props.x` or `splitProps()` instead.',
61
61
  span: getSpan(firstParam),
62
62
  })
63
63
  }
@@ -1,12 +1,12 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isTernaryWithJSX } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isTernaryWithJSX } from '../../utils/ast'
3
3
 
4
4
  export const noTernaryConditional: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-ternary-conditional",
7
- category: "jsx",
8
- description: "Prefer <Show> over ternary expressions with JSX branches.",
9
- severity: "warn",
6
+ id: 'pyreon/no-ternary-conditional',
7
+ category: 'jsx',
8
+ description: 'Prefer <Show> over ternary expressions with JSX branches.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
@@ -15,14 +15,14 @@ export const noTernaryConditional: Rule = {
15
15
  JSXExpressionContainer() {
16
16
  jsxExpressionDepth++
17
17
  },
18
- "JSXExpressionContainer:exit"() {
18
+ 'JSXExpressionContainer:exit'() {
19
19
  jsxExpressionDepth--
20
20
  },
21
21
  ConditionalExpression(node: any) {
22
22
  if (jsxExpressionDepth === 0) return
23
23
  if (!isTernaryWithJSX(node)) return
24
24
  context.report({
25
- message: "Ternary with JSX — use `<Show>` for more efficient conditional rendering.",
25
+ message: 'Ternary with JSX — use `<Show>` for more efficient conditional rendering.',
26
26
  span: getSpan(node),
27
27
  })
28
28
  },