@pyreon/lint 0.11.9 → 0.12.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/lint",
3
- "version": "0.11.9",
3
+ "version": "0.12.0",
4
4
  "description": "Pyreon-specific linter — 56 rules for signals, JSX, SSR, performance, router, and architecture",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/lint#readme",
6
6
  "bugs": {
@@ -3,11 +3,9 @@ import { getSpan, isDestructuring } from '../../utils/ast'
3
3
 
4
4
  function containsJSXReturn(node: any): boolean {
5
5
  if (!node) return false
6
- // Arrow with expression body returning JSX
7
6
  if (node.type === 'JSXElement' || node.type === 'JSXFragment') return true
8
7
  if (node.type === 'ParenthesizedExpression') return containsJSXReturn(node.expression)
9
8
 
10
- // Block body — look for return statements with JSX
11
9
  if (node.type === 'BlockStatement') {
12
10
  for (const stmt of node.body ?? []) {
13
11
  if (stmt.type === 'ReturnStatement' && containsJSXReturn(stmt.argument)) {
@@ -18,46 +16,89 @@ function containsJSXReturn(node: any): boolean {
18
16
  return false
19
17
  }
20
18
 
19
+ /**
20
+ * Extract destructured property names from an ObjectPattern.
21
+ * Returns names for the fix suggestion.
22
+ */
23
+ function getDestructuredNames(pattern: any): string[] {
24
+ if (pattern.type !== 'ObjectPattern') return []
25
+ const names: string[] = []
26
+ for (const prop of pattern.properties ?? []) {
27
+ if (prop.type === 'ObjectProperty' && prop.key?.type === 'Identifier') {
28
+ names.push(prop.key.name)
29
+ }
30
+ }
31
+ return names
32
+ }
33
+
21
34
  export const noPropsDestructure: Rule = {
22
35
  meta: {
23
36
  id: 'pyreon/no-props-destructure',
24
37
  category: 'jsx',
25
38
  description:
26
- 'Disallow destructuring props in component functions — it breaks signal reactivity.',
39
+ 'Disallow destructuring props in component functions — breaks reactive prop tracking. Use props.x or splitProps().',
27
40
  severity: 'error',
28
41
  fixable: false,
29
42
  },
30
43
  create(context) {
44
+ let functionDepth = 0
45
+
31
46
  const callbacks: VisitorCallbacks = {
32
47
  ArrowFunctionExpression(node: any) {
33
- checkFunction(node, context)
48
+ functionDepth++
49
+ checkFunction(node, context, functionDepth)
50
+ },
51
+ 'ArrowFunctionExpression:exit'() {
52
+ functionDepth--
34
53
  },
35
54
  FunctionDeclaration(node: any) {
36
- checkFunction(node, context)
55
+ functionDepth++
56
+ checkFunction(node, context, functionDepth)
57
+ },
58
+ 'FunctionDeclaration:exit'() {
59
+ functionDepth--
37
60
  },
38
61
  FunctionExpression(node: any) {
39
- checkFunction(node, context)
62
+ functionDepth++
63
+ checkFunction(node, context, functionDepth)
64
+ },
65
+ 'FunctionExpression:exit'() {
66
+ functionDepth--
40
67
  },
41
68
  }
42
69
  return callbacks
43
70
  },
44
71
  }
45
72
 
46
- function checkFunction(node: any, context: any) {
73
+ function checkFunction(node: any, context: any, depth: number) {
47
74
  const params = node.params
48
75
  if (!params || params.length === 0) return
49
76
 
50
77
  const firstParam = params[0]
51
78
  if (!isDestructuring(firstParam)) return
52
79
 
53
- // Check if this function returns JSX
80
+ // Skip HOC inner functions (depth > 1)
81
+ if (depth > 1) return
82
+
54
83
  const body = node.body
55
84
  if (!body) return
56
85
 
57
86
  if (containsJSXReturn(body)) {
87
+ const names = getDestructuredNames(firstParam)
88
+ const hasRest = (firstParam.properties ?? []).some((p: any) => p.type === 'RestElement')
89
+
90
+ let suggestion = 'Use `props.x` pattern for reactive prop access.'
91
+ if (names.length > 0) {
92
+ const propsAccess = names.map((n) => `props.${n}`).join(', ')
93
+ suggestion = `Use \`props\` parameter and access as ${propsAccess}.`
94
+ if (hasRest) {
95
+ suggestion += ` For rest props, use \`splitProps(props, [${names.map((n) => `'${n}'`).join(', ')}])\`.`
96
+ }
97
+ }
98
+
58
99
  context.report({
59
100
  message:
60
- 'Destructured props in a component function — this breaks signal reactivity. Use `props.x` or `splitProps()` instead.',
101
+ `Destructured props in component function — breaks reactive prop tracking. ${suggestion}`,
61
102
  span: getSpan(firstParam),
62
103
  })
63
104
  }
package/src/watcher.ts CHANGED
@@ -41,7 +41,7 @@ export function watchAndLint(options: LintOptions & { format: string }): void {
41
41
  // Debounce map: filePath -> timeout
42
42
  const pending = new Map<string, ReturnType<typeof setTimeout>>()
43
43
 
44
- // eslint-disable-next-line no-console
44
+ // oxlint-disable-next-line no-console
45
45
  console.log(`\x1b[2m[pyreon-lint] Watching for changes...\x1b[0m\n`)
46
46
 
47
47
  for (const p of options.paths) {