@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/lib/analysis/cli.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/cli.js +42 -9
- package/lib/cli.js.map +1 -1
- package/lib/index.js +42 -9
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/rules/jsx/no-props-destructure.ts +50 -9
- package/src/watcher.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/lint",
|
|
3
|
-
"version": "0.
|
|
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 —
|
|
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
|
-
|
|
48
|
+
functionDepth++
|
|
49
|
+
checkFunction(node, context, functionDepth)
|
|
50
|
+
},
|
|
51
|
+
'ArrowFunctionExpression:exit'() {
|
|
52
|
+
functionDepth--
|
|
34
53
|
},
|
|
35
54
|
FunctionDeclaration(node: any) {
|
|
36
|
-
|
|
55
|
+
functionDepth++
|
|
56
|
+
checkFunction(node, context, functionDepth)
|
|
57
|
+
},
|
|
58
|
+
'FunctionDeclaration:exit'() {
|
|
59
|
+
functionDepth--
|
|
37
60
|
},
|
|
38
61
|
FunctionExpression(node: any) {
|
|
39
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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) {
|