@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,60 +1,60 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
3
- import { isPyreonImport } from "../../utils/imports"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
+ import { isPyreonImport } from '../../utils/imports'
4
4
 
5
- type PackageCategory = "core" | "fundamentals" | "tools" | "ui-system"
5
+ type PackageCategory = 'core' | 'fundamentals' | 'tools' | 'ui-system'
6
6
 
7
7
  const CORE_PACKAGES = new Set([
8
- "@pyreon/reactivity",
9
- "@pyreon/core",
10
- "@pyreon/compiler",
11
- "@pyreon/runtime-dom",
12
- "@pyreon/runtime-server",
13
- "@pyreon/router",
14
- "@pyreon/head",
15
- "@pyreon/server",
8
+ '@pyreon/reactivity',
9
+ '@pyreon/core',
10
+ '@pyreon/compiler',
11
+ '@pyreon/runtime-dom',
12
+ '@pyreon/runtime-server',
13
+ '@pyreon/router',
14
+ '@pyreon/head',
15
+ '@pyreon/server',
16
16
  ])
17
17
 
18
18
  const UI_PACKAGES = new Set([
19
- "@pyreon/ui-core",
20
- "@pyreon/styler",
21
- "@pyreon/unistyle",
22
- "@pyreon/elements",
23
- "@pyreon/attrs",
24
- "@pyreon/rocketstyle",
25
- "@pyreon/coolgrid",
26
- "@pyreon/kinetic",
27
- "@pyreon/kinetic-presets",
28
- "@pyreon/connector-document",
29
- "@pyreon/document-primitives",
19
+ '@pyreon/ui-core',
20
+ '@pyreon/styler',
21
+ '@pyreon/unistyle',
22
+ '@pyreon/elements',
23
+ '@pyreon/attrs',
24
+ '@pyreon/rocketstyle',
25
+ '@pyreon/coolgrid',
26
+ '@pyreon/kinetic',
27
+ '@pyreon/kinetic-presets',
28
+ '@pyreon/connector-document',
29
+ '@pyreon/document-primitives',
30
30
  ])
31
31
 
32
32
  function getImportCategory(source: string): PackageCategory | null {
33
- if (CORE_PACKAGES.has(source)) return "core"
34
- if (UI_PACKAGES.has(source)) return "ui-system"
33
+ if (CORE_PACKAGES.has(source)) return 'core'
34
+ if (UI_PACKAGES.has(source)) return 'ui-system'
35
35
  return null
36
36
  }
37
37
 
38
38
  function getFileCategory(filePath: string): PackageCategory | null {
39
- if (filePath.includes("/packages/core/")) return "core"
40
- if (filePath.includes("/packages/ui-system/")) return "ui-system"
41
- if (filePath.includes("/packages/fundamentals/")) return "fundamentals"
42
- if (filePath.includes("/packages/tools/")) return "tools"
39
+ if (filePath.includes('/packages/core/')) return 'core'
40
+ if (filePath.includes('/packages/ui-system/')) return 'ui-system'
41
+ if (filePath.includes('/packages/fundamentals/')) return 'fundamentals'
42
+ if (filePath.includes('/packages/tools/')) return 'tools'
43
43
  return null
44
44
  }
45
45
 
46
46
  export const noCrossLayerImport: Rule = {
47
47
  meta: {
48
- id: "pyreon/no-cross-layer-import",
49
- category: "architecture",
50
- description: "Prevent core packages from importing ui-system packages.",
51
- severity: "error",
48
+ id: 'pyreon/no-cross-layer-import',
49
+ category: 'architecture',
50
+ description: 'Prevent core packages from importing ui-system packages.',
51
+ severity: 'error',
52
52
  fixable: false,
53
53
  },
54
54
  create(context) {
55
55
  const filePath = context.getFilePath()
56
56
  const fileCategory = getFileCategory(filePath)
57
- if (fileCategory !== "core") return {}
57
+ if (fileCategory !== 'core') return {}
58
58
 
59
59
  const callbacks: VisitorCallbacks = {
60
60
  ImportDeclaration(node: any) {
@@ -62,7 +62,7 @@ export const noCrossLayerImport: Rule = {
62
62
  if (!source || !isPyreonImport(source)) return
63
63
 
64
64
  const importCategory = getImportCategory(source)
65
- if (importCategory === "ui-system") {
65
+ if (importCategory === 'ui-system') {
66
66
  context.report({
67
67
  message: `Core package importing ui-system package \`${source}\` — core packages must not depend on ui-system.`,
68
68
  span: getSpan(node),
@@ -1,16 +1,16 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan } from "../../utils/ast"
3
- import { isPyreonImport } from "../../utils/imports"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan } from '../../utils/ast'
3
+ import { isPyreonImport } from '../../utils/imports'
4
4
 
5
5
  const DEEP_IMPORT_PATTERN = /@pyreon\/[^/]+\/(src|dist|lib)\//
6
6
 
7
7
  export const noDeepImport: Rule = {
8
8
  meta: {
9
- id: "pyreon/no-deep-import",
10
- category: "architecture",
9
+ id: 'pyreon/no-deep-import',
10
+ category: 'architecture',
11
11
  description:
12
- "Disallow importing from @pyreon/*/src/, /dist/, or /lib/ — use public exports instead.",
13
- severity: "warn",
12
+ 'Disallow importing from @pyreon/*/src/, /dist/, or /lib/ — use public exports instead.',
13
+ severity: 'warn',
14
14
  fixable: false,
15
15
  },
16
16
  create(context) {
@@ -1,22 +1,22 @@
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 noErrorWithoutPrefix: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-error-without-prefix",
7
- category: "architecture",
8
- description: "Require error messages to be prefixed with [Pyreon].",
9
- severity: "warn",
6
+ id: 'pyreon/no-error-without-prefix',
7
+ category: 'architecture',
8
+ description: 'Require error messages to be prefixed with [Pyreon].',
9
+ severity: 'warn',
10
10
  fixable: true,
11
11
  },
12
12
  create(context) {
13
13
  const filePath = context.getFilePath()
14
14
  // Skip test files
15
15
  if (
16
- filePath.includes("/tests/") ||
17
- filePath.includes("/test/") ||
18
- filePath.includes(".test.") ||
19
- filePath.includes(".spec.")
16
+ filePath.includes('/tests/') ||
17
+ filePath.includes('/test/') ||
18
+ filePath.includes('.test.') ||
19
+ filePath.includes('.spec.')
20
20
  ) {
21
21
  return {}
22
22
  }
@@ -24,9 +24,9 @@ export const noErrorWithoutPrefix: Rule = {
24
24
  const callbacks: VisitorCallbacks = {
25
25
  ThrowStatement(node: any) {
26
26
  const arg = node.argument
27
- if (!arg || arg.type !== "NewExpression") return
27
+ if (!arg || arg.type !== 'NewExpression') return
28
28
  const callee = arg.callee
29
- if (!callee || callee.type !== "Identifier" || callee.name !== "Error") return
29
+ if (!callee || callee.type !== 'Identifier' || callee.name !== 'Error') return
30
30
 
31
31
  const args = arg.arguments
32
32
  if (!args || args.length === 0) return
@@ -34,34 +34,34 @@ export const noErrorWithoutPrefix: Rule = {
34
34
  const firstArg = args[0]
35
35
  if (!firstArg) return
36
36
 
37
- if (firstArg.type === "Literal" || firstArg.type === "StringLiteral") {
37
+ if (firstArg.type === 'Literal' || firstArg.type === 'StringLiteral') {
38
38
  const value = firstArg.value as string
39
- if (typeof value === "string" && !value.startsWith("[Pyreon]")) {
39
+ if (typeof value === 'string' && !value.startsWith('[Pyreon]')) {
40
40
  const argSpan = getSpan(firstArg)
41
41
  // Fix: add [Pyreon] prefix
42
42
  const quote = context.getSourceText()[argSpan.start]
43
43
  const fixedValue = `${quote}[Pyreon] ${value}${quote}`
44
44
  context.report({
45
45
  message:
46
- "Error message missing `[Pyreon]` prefix — all framework errors should be prefixed for identification.",
46
+ 'Error message missing `[Pyreon]` prefix — all framework errors should be prefixed for identification.',
47
47
  span: getSpan(node),
48
48
  fix: { span: argSpan, replacement: fixedValue },
49
49
  })
50
50
  }
51
51
  }
52
52
 
53
- if (firstArg.type === "TemplateLiteral") {
53
+ if (firstArg.type === 'TemplateLiteral') {
54
54
  const quasis = firstArg.quasis
55
55
  if (quasis && quasis.length > 0) {
56
56
  const first = quasis[0]
57
- const raw = first.value?.raw ?? first.value?.cooked ?? ""
58
- if (!raw.startsWith("[Pyreon]")) {
57
+ const raw = first.value?.raw ?? first.value?.cooked ?? ''
58
+ if (!raw.startsWith('[Pyreon]')) {
59
59
  const argSpan = getSpan(firstArg)
60
60
  const source = context.getSourceText().slice(argSpan.start, argSpan.end)
61
- const fixed = source.replace(/^`/, "`[Pyreon] ")
61
+ const fixed = source.replace(/^`/, '`[Pyreon] ')
62
62
  context.report({
63
63
  message:
64
- "Error message missing `[Pyreon]` prefix — all framework errors should be prefixed for identification.",
64
+ 'Error message missing `[Pyreon]` prefix — all framework errors should be prefixed for identification.',
65
65
  span: getSpan(node),
66
66
  fix: { span: argSpan, replacement: fixed },
67
67
  })
@@ -1,40 +1,40 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isCallTo } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isCallTo } from '../../utils/ast'
3
3
 
4
4
  export const noSubmitWithoutValidation: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-submit-without-validation",
7
- category: "form",
8
- description: "Warn when useForm() has onSubmit but no validators or schema.",
9
- severity: "warn",
6
+ id: 'pyreon/no-submit-without-validation',
7
+ category: 'form',
8
+ description: 'Warn when useForm() has onSubmit but no validators or schema.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  CallExpression(node: any) {
15
- if (!isCallTo(node, "useForm")) return
15
+ if (!isCallTo(node, 'useForm')) return
16
16
  const args = node.arguments
17
17
  if (!args || args.length === 0) return
18
18
 
19
19
  const options = args[0]
20
- if (!options || options.type !== "ObjectExpression") return
20
+ if (!options || options.type !== 'ObjectExpression') return
21
21
 
22
22
  let hasOnSubmit = false
23
23
  let hasValidation = false
24
24
 
25
25
  for (const prop of options.properties ?? []) {
26
- if (prop.type !== "Property") continue
26
+ if (prop.type !== 'Property') continue
27
27
  const key = prop.key
28
28
  if (!key) continue
29
- const name = key.type === "Identifier" ? key.name : null
30
- if (name === "onSubmit") hasOnSubmit = true
31
- if (name === "validators" || name === "schema") hasValidation = true
29
+ const name = key.type === 'Identifier' ? key.name : null
30
+ if (name === 'onSubmit') hasOnSubmit = true
31
+ if (name === 'validators' || name === 'schema') hasValidation = true
32
32
  }
33
33
 
34
34
  if (hasOnSubmit && !hasValidation) {
35
35
  context.report({
36
36
  message:
37
- "`useForm()` has `onSubmit` without `validators` or `schema` — consider adding validation for data integrity.",
37
+ '`useForm()` has `onSubmit` without `validators` or `schema` — consider adding validation for data integrity.',
38
38
  span: getSpan(node),
39
39
  })
40
40
  }
@@ -1,12 +1,12 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isCallTo } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isCallTo } from '../../utils/ast'
3
3
 
4
4
  export const noUnregisteredField: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-unregistered-field",
7
- category: "form",
8
- description: "Warn when useField() is called without a corresponding register() call.",
9
- severity: "warn",
6
+ id: 'pyreon/no-unregistered-field',
7
+ category: 'form',
8
+ description: 'Warn when useField() is called without a corresponding register() call.',
9
+ severity: 'warn',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
@@ -16,20 +16,20 @@ export const noUnregisteredField: Rule = {
16
16
  const callbacks: VisitorCallbacks = {
17
17
  VariableDeclarator(node: any) {
18
18
  const init = node.init
19
- if (!init || !isCallTo(init, "useField")) return
19
+ if (!init || !isCallTo(init, 'useField')) return
20
20
  const id = node.id
21
- if (!id || id.type !== "Identifier") return
21
+ if (!id || id.type !== 'Identifier') return
22
22
  fieldDecls.set(id.name, { span: getSpan(node) })
23
23
  },
24
24
  CallExpression(node: any) {
25
25
  const callee = node.callee
26
- if (!callee || callee.type !== "MemberExpression") return
27
- if (callee.property?.type !== "Identifier" || callee.property.name !== "register") return
28
- if (callee.object?.type === "Identifier") {
26
+ if (!callee || callee.type !== 'MemberExpression') return
27
+ if (callee.property?.type !== 'Identifier' || callee.property.name !== 'register') return
28
+ if (callee.object?.type === 'Identifier') {
29
29
  registeredNames.add(callee.object.name)
30
30
  }
31
31
  },
32
- "Program:exit"() {
32
+ 'Program:exit'() {
33
33
  for (const [name, { span }] of fieldDecls) {
34
34
  if (!registeredNames.has(name)) {
35
35
  context.report({
@@ -1,13 +1,13 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isCallTo } from "../../utils/ast"
3
- import { extractImportInfo } from "../../utils/imports"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isCallTo } from '../../utils/ast'
3
+ import { extractImportInfo } from '../../utils/imports'
4
4
 
5
5
  export const preferFieldArray: Rule = {
6
6
  meta: {
7
- id: "pyreon/prefer-field-array",
8
- category: "form",
9
- description: "Suggest useFieldArray() instead of signal([]) in files that import @pyreon/form.",
10
- severity: "info",
7
+ id: 'pyreon/prefer-field-array',
8
+ category: 'form',
9
+ description: 'Suggest useFieldArray() instead of signal([]) in files that import @pyreon/form.',
10
+ severity: 'info',
11
11
  fixable: false,
12
12
  },
13
13
  create(context) {
@@ -16,21 +16,21 @@ export const preferFieldArray: Rule = {
16
16
  const callbacks: VisitorCallbacks = {
17
17
  ImportDeclaration(node: any) {
18
18
  const info = extractImportInfo(node)
19
- if (info && info.source === "@pyreon/form") {
19
+ if (info && info.source === '@pyreon/form') {
20
20
  importsForm = true
21
21
  }
22
22
  },
23
23
  CallExpression(node: any) {
24
24
  if (!importsForm) return
25
- if (!isCallTo(node, "signal")) return
25
+ if (!isCallTo(node, 'signal')) return
26
26
 
27
27
  const args = node.arguments
28
28
  if (!args || args.length === 0) return
29
29
  const firstArg = args[0]
30
- if (firstArg?.type === "ArrayExpression") {
30
+ if (firstArg?.type === 'ArrayExpression') {
31
31
  context.report({
32
32
  message:
33
- "`signal([])` in a form file — consider using `useFieldArray()` for dynamic array fields with stable keys.",
33
+ '`signal([])` in a form file — consider using `useFieldArray()` for dynamic array fields with stable keys.',
34
34
  span: getSpan(node),
35
35
  })
36
36
  }
@@ -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 noRawAddEventListener: Rule = {
5
5
  meta: {
6
- id: "pyreon/no-raw-addeventlistener",
7
- category: "hooks",
8
- description: "Suggest useEventListener() instead of raw .addEventListener() calls.",
9
- severity: "info",
6
+ id: 'pyreon/no-raw-addeventlistener',
7
+ category: 'hooks',
8
+ description: 'Suggest useEventListener() instead of raw .addEventListener() calls.',
9
+ severity: 'info',
10
10
  fixable: false,
11
11
  },
12
12
  create(context) {
13
13
  const callbacks: VisitorCallbacks = {
14
14
  CallExpression(node: any) {
15
15
  const callee = node.callee
16
- if (!callee || callee.type !== "MemberExpression") return
17
- if (callee.property?.type !== "Identifier" || callee.property.name !== "addEventListener")
16
+ if (!callee || callee.type !== 'MemberExpression') return
17
+ if (callee.property?.type !== 'Identifier' || callee.property.name !== 'addEventListener')
18
18
  return
19
19
  context.report({
20
20
  message:
21
- "Raw `.addEventListener()` — consider using `useEventListener()` from `@pyreon/hooks` for auto-cleanup on unmount.",
21
+ 'Raw `.addEventListener()` — consider using `useEventListener()` from `@pyreon/hooks` for auto-cleanup on unmount.',
22
22
  span: getSpan(node),
23
23
  })
24
24
  },
@@ -1,26 +1,26 @@
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 STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"])
5
- const STORAGE_METHODS = new Set(["getItem", "setItem", "removeItem"])
4
+ const STORAGE_OBJECTS = new Set(['localStorage', 'sessionStorage'])
5
+ const STORAGE_METHODS = new Set(['getItem', 'setItem', 'removeItem'])
6
6
 
7
7
  export const noRawLocalStorage: Rule = {
8
8
  meta: {
9
- id: "pyreon/no-raw-localstorage",
10
- category: "hooks",
11
- description: "Suggest useStorage() instead of raw localStorage/sessionStorage access.",
12
- severity: "info",
9
+ id: 'pyreon/no-raw-localstorage',
10
+ category: 'hooks',
11
+ description: 'Suggest useStorage() instead of raw localStorage/sessionStorage access.',
12
+ severity: 'info',
13
13
  fixable: false,
14
14
  },
15
15
  create(context) {
16
16
  const callbacks: VisitorCallbacks = {
17
17
  CallExpression(node: any) {
18
18
  const callee = node.callee
19
- if (!callee || callee.type !== "MemberExpression") return
19
+ if (!callee || callee.type !== 'MemberExpression') return
20
20
  if (
21
- callee.object?.type === "Identifier" &&
21
+ callee.object?.type === 'Identifier' &&
22
22
  STORAGE_OBJECTS.has(callee.object.name) &&
23
- callee.property?.type === "Identifier" &&
23
+ callee.property?.type === 'Identifier' &&
24
24
  STORAGE_METHODS.has(callee.property.name)
25
25
  ) {
26
26
  context.report({
@@ -1,28 +1,28 @@
1
- import type { Rule, VisitorCallbacks } from "../../types"
2
- import { getSpan, isCallTo } from "../../utils/ast"
1
+ import type { Rule, VisitorCallbacks } from '../../types'
2
+ import { getSpan, isCallTo } from '../../utils/ast'
3
3
 
4
- const TIMER_FNS = new Set(["setInterval", "setTimeout"])
4
+ const TIMER_FNS = new Set(['setInterval', 'setTimeout'])
5
5
 
6
6
  export const noRawSetInterval: Rule = {
7
7
  meta: {
8
- id: "pyreon/no-raw-setinterval",
9
- category: "hooks",
10
- description: "Suggest wrapping setInterval/setTimeout in onMount for automatic cleanup.",
11
- severity: "info",
8
+ id: 'pyreon/no-raw-setinterval',
9
+ category: 'hooks',
10
+ description: 'Suggest wrapping setInterval/setTimeout in onMount for automatic cleanup.',
11
+ severity: 'info',
12
12
  fixable: false,
13
13
  },
14
14
  create(context) {
15
15
  let mountDepth = 0
16
16
  const callbacks: VisitorCallbacks = {
17
17
  CallExpression(node: any) {
18
- if (isCallTo(node, "onMount")) {
18
+ if (isCallTo(node, 'onMount')) {
19
19
  mountDepth++
20
20
  }
21
21
 
22
22
  if (mountDepth > 0) return
23
23
 
24
24
  const callee = node.callee
25
- if (!callee || callee.type !== "Identifier") return
25
+ if (!callee || callee.type !== 'Identifier') return
26
26
  if (TIMER_FNS.has(callee.name)) {
27
27
  context.report({
28
28
  message: `\`${callee.name}()\` outside \`onMount\` — wrap in \`onMount(() => { ... return () => clear... })\` for automatic cleanup.`,
@@ -30,8 +30,8 @@ export const noRawSetInterval: Rule = {
30
30
  })
31
31
  }
32
32
  },
33
- "CallExpression:exit"(node: any) {
34
- if (isCallTo(node, "onMount")) {
33
+ 'CallExpression:exit'(node: any) {
34
+ if (isCallTo(node, 'onMount')) {
35
35
  mountDepth--
36
36
  }
37
37
  },