@pyreon/lint 0.11.5 → 0.11.7
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/README.md +91 -91
- package/lib/analysis/cli.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/cli.js +214 -1
- package/lib/cli.js.map +1 -1
- package/lib/index.js +207 -1
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +30 -5
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +15 -15
- package/src/cache.ts +1 -1
- package/src/cli.ts +38 -28
- package/src/config/ignore.ts +23 -23
- package/src/config/loader.ts +8 -8
- package/src/config/presets.ts +11 -11
- package/src/index.ts +14 -12
- package/src/lint.ts +19 -19
- package/src/lsp/index.ts +225 -0
- package/src/reporter.ts +17 -17
- package/src/rules/accessibility/dialog-a11y.ts +10 -10
- package/src/rules/accessibility/overlay-a11y.ts +11 -11
- package/src/rules/accessibility/toast-a11y.ts +11 -11
- package/src/rules/architecture/dev-guard-warnings.ts +19 -19
- package/src/rules/architecture/no-circular-import.ts +16 -16
- package/src/rules/architecture/no-cross-layer-import.ts +35 -35
- package/src/rules/architecture/no-deep-import.ts +7 -7
- package/src/rules/architecture/no-error-without-prefix.ts +20 -20
- package/src/rules/form/no-submit-without-validation.ts +13 -13
- package/src/rules/form/no-unregistered-field.ts +12 -12
- package/src/rules/form/prefer-field-array.ts +11 -11
- package/src/rules/hooks/no-raw-addeventlistener.ts +9 -9
- package/src/rules/hooks/no-raw-localstorage.ts +11 -11
- package/src/rules/hooks/no-raw-setinterval.ts +11 -11
- package/src/rules/index.ts +60 -57
- package/src/rules/jsx/no-and-conditional.ts +8 -8
- package/src/rules/jsx/no-children-access.ts +12 -12
- package/src/rules/jsx/no-classname.ts +10 -10
- package/src/rules/jsx/no-htmlfor.ts +10 -10
- package/src/rules/jsx/no-index-as-by.ts +17 -17
- package/src/rules/jsx/no-map-in-jsx.ts +9 -9
- package/src/rules/jsx/no-missing-for-by.ts +9 -9
- package/src/rules/jsx/no-onchange.ts +12 -12
- package/src/rules/jsx/no-props-destructure.ts +11 -11
- package/src/rules/jsx/no-ternary-conditional.ts +8 -8
- package/src/rules/jsx/use-by-not-key.ts +12 -12
- package/src/rules/lifecycle/no-dom-in-setup.ts +18 -18
- package/src/rules/lifecycle/no-effect-in-mount.ts +11 -11
- package/src/rules/lifecycle/no-missing-cleanup.ts +19 -19
- package/src/rules/lifecycle/no-mount-in-effect.ts +11 -11
- package/src/rules/performance/no-eager-import.ts +7 -7
- package/src/rules/performance/no-effect-in-for.ts +10 -10
- package/src/rules/performance/no-large-for-without-by.ts +9 -9
- package/src/rules/performance/prefer-show-over-display.ts +16 -16
- package/src/rules/reactivity/no-bare-signal-in-jsx.ts +10 -10
- package/src/rules/reactivity/no-context-destructure.ts +45 -0
- package/src/rules/reactivity/no-effect-assignment.ts +16 -16
- package/src/rules/reactivity/no-nested-effect.ts +10 -10
- package/src/rules/reactivity/no-peek-in-tracked.ts +10 -10
- package/src/rules/reactivity/no-signal-in-loop.ts +13 -13
- package/src/rules/reactivity/no-signal-leak.ts +9 -9
- package/src/rules/reactivity/no-unbatched-updates.ts +12 -12
- package/src/rules/reactivity/prefer-computed.ts +13 -13
- package/src/rules/router/index.ts +4 -4
- package/src/rules/router/no-href-navigation.ts +14 -14
- package/src/rules/router/no-imperative-navigate-in-render.ts +19 -19
- package/src/rules/router/no-missing-fallback.ts +16 -16
- package/src/rules/router/prefer-use-is-active.ts +11 -11
- package/src/rules/ssr/no-mismatch-risk.ts +11 -11
- package/src/rules/ssr/no-window-in-ssr.ts +22 -22
- package/src/rules/ssr/prefer-request-context.ts +14 -14
- package/src/rules/store/no-duplicate-store-id.ts +9 -9
- package/src/rules/store/no-mutate-store-state.ts +11 -11
- package/src/rules/store/no-store-outside-provider.ts +15 -15
- package/src/rules/styling/no-dynamic-styled.ts +13 -13
- package/src/rules/styling/no-inline-style-object.ts +10 -10
- package/src/rules/styling/no-theme-outside-provider.ts +11 -11
- package/src/rules/styling/prefer-cx.ts +12 -12
- package/src/runner.ts +13 -13
- package/src/tests/lsp.test.ts +88 -0
- package/src/tests/runner.test.ts +325 -325
- package/src/types.ts +15 -15
- package/src/utils/ast.ts +50 -50
- package/src/utils/imports.ts +53 -53
- package/src/utils/index.ts +5 -5
- package/src/utils/source.ts +2 -2
- package/src/watcher.ts +19 -19
package/src/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// ── Severity & Diagnostics ──────────────────────────────────────────────────
|
|
2
2
|
|
|
3
|
-
export type Severity =
|
|
3
|
+
export type Severity = 'error' | 'warn' | 'info' | 'off'
|
|
4
4
|
|
|
5
5
|
export interface SourceLocation {
|
|
6
6
|
line: number
|
|
@@ -29,18 +29,18 @@ export interface Diagnostic {
|
|
|
29
29
|
// ── Rule Metadata ───────────────────────────────────────────────────────────
|
|
30
30
|
|
|
31
31
|
export type RuleCategory =
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
32
|
+
| 'reactivity'
|
|
33
|
+
| 'jsx'
|
|
34
|
+
| 'lifecycle'
|
|
35
|
+
| 'performance'
|
|
36
|
+
| 'ssr'
|
|
37
|
+
| 'architecture'
|
|
38
|
+
| 'store'
|
|
39
|
+
| 'form'
|
|
40
|
+
| 'styling'
|
|
41
|
+
| 'hooks'
|
|
42
|
+
| 'accessibility'
|
|
43
|
+
| 'router'
|
|
44
44
|
|
|
45
45
|
export interface RuleMeta {
|
|
46
46
|
id: string
|
|
@@ -53,7 +53,7 @@ export interface RuleMeta {
|
|
|
53
53
|
// ── Rule Context & Visitor ──────────────────────────────────────────────────
|
|
54
54
|
|
|
55
55
|
export interface RuleContext {
|
|
56
|
-
report(diagnostic: Omit<Diagnostic,
|
|
56
|
+
report(diagnostic: Omit<Diagnostic, 'ruleId' | 'severity' | 'loc'>): void
|
|
57
57
|
getSourceText(): string
|
|
58
58
|
getFilePath(): string
|
|
59
59
|
}
|
|
@@ -86,7 +86,7 @@ export interface LintConfigFile {
|
|
|
86
86
|
exclude?: string[] | undefined
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
export type PresetName =
|
|
89
|
+
export type PresetName = 'recommended' | 'strict' | 'app' | 'lib'
|
|
90
90
|
|
|
91
91
|
// ── Results ─────────────────────────────────────────────────────────────────
|
|
92
92
|
|
package/src/utils/ast.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { Span } from
|
|
2
|
-
import { BROWSER_GLOBALS } from
|
|
1
|
+
import type { Span } from '../types'
|
|
2
|
+
import { BROWSER_GLOBALS } from './imports'
|
|
3
3
|
|
|
4
4
|
/** Check if a node is a call expression to a specific function name. */
|
|
5
5
|
export function isCallTo(node: any, name: string): boolean {
|
|
6
6
|
return (
|
|
7
|
-
node.type ===
|
|
8
|
-
node.callee?.type ===
|
|
7
|
+
node.type === 'CallExpression' &&
|
|
8
|
+
node.callee?.type === 'Identifier' &&
|
|
9
9
|
node.callee.name === name
|
|
10
10
|
)
|
|
11
11
|
}
|
|
@@ -13,8 +13,8 @@ export function isCallTo(node: any, name: string): boolean {
|
|
|
13
13
|
/** Check if a node is a call expression to any of the given function names. */
|
|
14
14
|
export function isCallToAny(node: any, names: Set<string>): boolean {
|
|
15
15
|
return (
|
|
16
|
-
node.type ===
|
|
17
|
-
node.callee?.type ===
|
|
16
|
+
node.type === 'CallExpression' &&
|
|
17
|
+
node.callee?.type === 'Identifier' &&
|
|
18
18
|
names.has(node.callee.name)
|
|
19
19
|
)
|
|
20
20
|
}
|
|
@@ -22,29 +22,29 @@ export function isCallToAny(node: any, names: Set<string>): boolean {
|
|
|
22
22
|
/** Check if a node is a member call like `obj.method()`. */
|
|
23
23
|
export function isMemberCallTo(node: any, objectName: string, methodName: string): boolean {
|
|
24
24
|
return (
|
|
25
|
-
node.type ===
|
|
26
|
-
node.callee?.type ===
|
|
27
|
-
node.callee.object?.type ===
|
|
25
|
+
node.type === 'CallExpression' &&
|
|
26
|
+
node.callee?.type === 'MemberExpression' &&
|
|
27
|
+
node.callee.object?.type === 'Identifier' &&
|
|
28
28
|
node.callee.object.name === objectName &&
|
|
29
|
-
node.callee.property?.type ===
|
|
29
|
+
node.callee.property?.type === 'Identifier' &&
|
|
30
30
|
node.callee.property.name === methodName
|
|
31
31
|
)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Check if a node is a JSX element (opening or self-closing). */
|
|
35
35
|
export function isJSXElement(node: any): boolean {
|
|
36
|
-
return node.type ===
|
|
36
|
+
return node.type === 'JSXElement' || node.type === 'JSXFragment'
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/** Get the tag name of a JSX element. */
|
|
40
40
|
export function getJSXTagName(node: any): string | null {
|
|
41
|
-
if (node.type ===
|
|
41
|
+
if (node.type === 'JSXElement') {
|
|
42
42
|
const opening = node.openingElement
|
|
43
43
|
if (!opening) return null
|
|
44
44
|
const name = opening.name
|
|
45
|
-
if (name?.type ===
|
|
46
|
-
if (name?.type ===
|
|
47
|
-
return `${name.object?.name ??
|
|
45
|
+
if (name?.type === 'JSXIdentifier') return name.name
|
|
46
|
+
if (name?.type === 'JSXMemberExpression') {
|
|
47
|
+
return `${name.object?.name ?? ''}.${name.property?.name ?? ''}`
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
return null
|
|
@@ -55,8 +55,8 @@ export function getJSXAttribute(openingElement: any, attrName: string): any | nu
|
|
|
55
55
|
const attrs = openingElement.attributes ?? []
|
|
56
56
|
for (const attr of attrs) {
|
|
57
57
|
if (
|
|
58
|
-
attr.type ===
|
|
59
|
-
attr.name?.type ===
|
|
58
|
+
attr.type === 'JSXAttribute' &&
|
|
59
|
+
attr.name?.type === 'JSXIdentifier' &&
|
|
60
60
|
attr.name.name === attrName
|
|
61
61
|
) {
|
|
62
62
|
return attr
|
|
@@ -74,90 +74,90 @@ export function hasJSXAttribute(openingElement: any, attrName: string): boolean
|
|
|
74
74
|
export function isInsideFunction(ancestors: any[]): boolean {
|
|
75
75
|
return ancestors.some(
|
|
76
76
|
(a) =>
|
|
77
|
-
a.type ===
|
|
78
|
-
a.type ===
|
|
79
|
-
a.type ===
|
|
77
|
+
a.type === 'FunctionDeclaration' ||
|
|
78
|
+
a.type === 'FunctionExpression' ||
|
|
79
|
+
a.type === 'ArrowFunctionExpression',
|
|
80
80
|
)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
/** Check if a node is inside JSX. */
|
|
84
84
|
export function isInsideJSX(ancestors: any[]): boolean {
|
|
85
|
-
return ancestors.some((a) => a.type ===
|
|
85
|
+
return ancestors.some((a) => a.type === 'JSXElement' || a.type === 'JSXFragment')
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/** Check if a node is an array .map() call. */
|
|
89
89
|
export function isArrayMapCall(node: any): boolean {
|
|
90
90
|
return (
|
|
91
|
-
node.type ===
|
|
92
|
-
node.callee?.type ===
|
|
93
|
-
node.callee.property?.type ===
|
|
94
|
-
node.callee.property.name ===
|
|
91
|
+
node.type === 'CallExpression' &&
|
|
92
|
+
node.callee?.type === 'MemberExpression' &&
|
|
93
|
+
node.callee.property?.type === 'Identifier' &&
|
|
94
|
+
node.callee.property.name === 'map'
|
|
95
95
|
)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** Check if a node is a function expression or arrow function. */
|
|
99
99
|
export function isFunction(node: any): boolean {
|
|
100
100
|
return (
|
|
101
|
-
node.type ===
|
|
102
|
-
node.type ===
|
|
103
|
-
node.type ===
|
|
101
|
+
node.type === 'FunctionDeclaration' ||
|
|
102
|
+
node.type === 'FunctionExpression' ||
|
|
103
|
+
node.type === 'ArrowFunctionExpression'
|
|
104
104
|
)
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/** Check if a node is a destructuring pattern. */
|
|
108
108
|
export function isDestructuring(node: any): boolean {
|
|
109
|
-
return node.type ===
|
|
109
|
+
return node.type === 'ObjectPattern' || node.type === 'ArrayPattern'
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/** Check if a node is a ternary with JSX in either branch. */
|
|
113
113
|
export function isTernaryWithJSX(node: any): boolean {
|
|
114
|
-
if (node.type !==
|
|
114
|
+
if (node.type !== 'ConditionalExpression') return false
|
|
115
115
|
return containsJSX(node.consequent) || containsJSX(node.alternate)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
/** Check if a node contains JSX anywhere. */
|
|
119
119
|
function containsJSX(node: any): boolean {
|
|
120
120
|
if (!node) return false
|
|
121
|
-
if (node.type ===
|
|
122
|
-
if (node.type ===
|
|
121
|
+
if (node.type === 'JSXElement' || node.type === 'JSXFragment') return true
|
|
122
|
+
if (node.type === 'ParenthesizedExpression') return containsJSX(node.expression)
|
|
123
123
|
return false
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
/** Check if a JSX element has JSX children. */
|
|
127
127
|
export function hasJSXChild(node: any): boolean {
|
|
128
|
-
if (node.type !==
|
|
129
|
-
return (node.children ?? []).some((c: any) => c.type ===
|
|
128
|
+
if (node.type !== 'JSXElement') return false
|
|
129
|
+
return (node.children ?? []).some((c: any) => c.type === 'JSXElement' || c.type === 'JSXFragment')
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
/** Check if a node is a logical AND with JSX. */
|
|
133
133
|
export function isLogicalAndWithJSX(node: any): boolean {
|
|
134
|
-
if (node.type !==
|
|
134
|
+
if (node.type !== 'LogicalExpression' || node.operator !== '&&') return false
|
|
135
135
|
return containsJSX(node.right)
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/** Check if a node is a .peek() call. */
|
|
139
139
|
export function isPeekCall(node: any): boolean {
|
|
140
140
|
return (
|
|
141
|
-
node.type ===
|
|
142
|
-
node.callee?.type ===
|
|
143
|
-
node.callee.property?.type ===
|
|
144
|
-
node.callee.property.name ===
|
|
141
|
+
node.type === 'CallExpression' &&
|
|
142
|
+
node.callee?.type === 'MemberExpression' &&
|
|
143
|
+
node.callee.property?.type === 'Identifier' &&
|
|
144
|
+
node.callee.property.name === 'peek'
|
|
145
145
|
)
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/** Check if a node is a .set() call. */
|
|
149
149
|
export function isSetCall(node: any): boolean {
|
|
150
150
|
return (
|
|
151
|
-
node.type ===
|
|
152
|
-
node.callee?.type ===
|
|
153
|
-
node.callee.property?.type ===
|
|
154
|
-
node.callee.property.name ===
|
|
151
|
+
node.type === 'CallExpression' &&
|
|
152
|
+
node.callee?.type === 'MemberExpression' &&
|
|
153
|
+
node.callee.property?.type === 'Identifier' &&
|
|
154
|
+
node.callee.property.name === 'set'
|
|
155
155
|
)
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/** Check if a node references a browser global. */
|
|
159
159
|
export function isBrowserGlobal(node: any): boolean {
|
|
160
|
-
return node.type ===
|
|
160
|
+
return node.type === 'Identifier' && BROWSER_GLOBALS.has(node.name)
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
/** Get the span (byte offsets) of a node. */
|
|
@@ -168,7 +168,7 @@ export function getSpan(node: any): Span {
|
|
|
168
168
|
/** Check if a node is inside a `if (__DEV__)` guard. */
|
|
169
169
|
export function isInsideDevGuard(ancestors: any[]): boolean {
|
|
170
170
|
return ancestors.some(
|
|
171
|
-
(a) => a.type ===
|
|
171
|
+
(a) => a.type === 'IfStatement' && a.test?.type === 'Identifier' && a.test.name === '__DEV__',
|
|
172
172
|
)
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -176,7 +176,7 @@ export function isInsideDevGuard(ancestors: any[]): boolean {
|
|
|
176
176
|
export function isInsideOnMount(ancestors: any[]): boolean {
|
|
177
177
|
return ancestors.some(
|
|
178
178
|
(a) =>
|
|
179
|
-
a.type ===
|
|
179
|
+
a.type === 'CallExpression' && a.callee?.type === 'Identifier' && a.callee.name === 'onMount',
|
|
180
180
|
)
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -184,9 +184,9 @@ export function isInsideOnMount(ancestors: any[]): boolean {
|
|
|
184
184
|
export function isInsideTypeofGuard(ancestors: any[]): boolean {
|
|
185
185
|
return ancestors.some(
|
|
186
186
|
(a) =>
|
|
187
|
-
a.type ===
|
|
188
|
-
a.test?.type ===
|
|
189
|
-
a.test.left?.type ===
|
|
190
|
-
a.test.left.operator ===
|
|
187
|
+
a.type === 'IfStatement' &&
|
|
188
|
+
a.test?.type === 'BinaryExpression' &&
|
|
189
|
+
a.test.left?.type === 'UnaryExpression' &&
|
|
190
|
+
a.test.left.operator === 'typeof',
|
|
191
191
|
)
|
|
192
192
|
}
|
package/src/utils/imports.ts
CHANGED
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import type { ImportInfo } from
|
|
1
|
+
import type { ImportInfo } from '../types'
|
|
2
2
|
|
|
3
3
|
export type { ImportInfo }
|
|
4
4
|
|
|
5
5
|
// ── Constants ───────────────────────────────────────────────────────────────
|
|
6
6
|
|
|
7
|
-
export const PYREON_PREFIX =
|
|
7
|
+
export const PYREON_PREFIX = '@pyreon/'
|
|
8
8
|
|
|
9
9
|
export const REACTIVITY_APIS = new Set([
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
'signal',
|
|
11
|
+
'computed',
|
|
12
|
+
'effect',
|
|
13
|
+
'batch',
|
|
14
|
+
'onCleanup',
|
|
15
|
+
'createSelector',
|
|
16
|
+
'createStore',
|
|
17
|
+
'untrack',
|
|
18
18
|
])
|
|
19
19
|
|
|
20
|
-
export const LIFECYCLE_APIS = new Set([
|
|
20
|
+
export const LIFECYCLE_APIS = new Set(['onMount', 'onUnmount'])
|
|
21
21
|
|
|
22
|
-
export const CONTEXT_APIS = new Set([
|
|
22
|
+
export const CONTEXT_APIS = new Set(['createContext', 'provide', 'pushContext', 'popContext'])
|
|
23
23
|
|
|
24
24
|
export const JSX_COMPONENTS = new Set([
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
'For',
|
|
26
|
+
'Show',
|
|
27
|
+
'Switch',
|
|
28
|
+
'Match',
|
|
29
|
+
'Dynamic',
|
|
30
|
+
'ErrorBoundary',
|
|
31
|
+
'Suspense',
|
|
32
|
+
'Portal',
|
|
33
33
|
])
|
|
34
34
|
|
|
35
35
|
export const HEAVY_PACKAGES = new Set([
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
'@pyreon/charts',
|
|
37
|
+
'@pyreon/code',
|
|
38
|
+
'@pyreon/document',
|
|
39
|
+
'@pyreon/flow',
|
|
40
40
|
])
|
|
41
41
|
|
|
42
42
|
export const BROWSER_GLOBALS = new Set([
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
'window',
|
|
44
|
+
'document',
|
|
45
|
+
'navigator',
|
|
46
|
+
'location',
|
|
47
|
+
'history',
|
|
48
|
+
'localStorage',
|
|
49
|
+
'sessionStorage',
|
|
50
|
+
'indexedDB',
|
|
51
|
+
'fetch',
|
|
52
|
+
'XMLHttpRequest',
|
|
53
|
+
'WebSocket',
|
|
54
|
+
'requestAnimationFrame',
|
|
55
|
+
'cancelAnimationFrame',
|
|
56
|
+
'IntersectionObserver',
|
|
57
|
+
'MutationObserver',
|
|
58
|
+
'ResizeObserver',
|
|
59
|
+
'matchMedia',
|
|
60
|
+
'getComputedStyle',
|
|
61
|
+
'addEventListener',
|
|
62
|
+
'removeEventListener',
|
|
63
63
|
])
|
|
64
64
|
|
|
65
65
|
// ── Functions ───────────────────────────────────────────────────────────────
|
|
@@ -73,26 +73,26 @@ export function isPyreonPackage(source: string): boolean {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export function extractImportInfo(node: any): ImportInfo | null {
|
|
76
|
-
if (node.type !==
|
|
76
|
+
if (node.type !== 'ImportDeclaration') return null
|
|
77
77
|
|
|
78
78
|
const source = node.source?.value as string
|
|
79
79
|
if (!source) return null
|
|
80
80
|
|
|
81
|
-
const specifiers: ImportInfo[
|
|
81
|
+
const specifiers: ImportInfo['specifiers'] = []
|
|
82
82
|
let isDefault = false
|
|
83
83
|
let isNamespace = false
|
|
84
84
|
|
|
85
85
|
for (const spec of node.specifiers ?? []) {
|
|
86
|
-
if (spec.type ===
|
|
86
|
+
if (spec.type === 'ImportDefaultSpecifier') {
|
|
87
87
|
isDefault = true
|
|
88
|
-
specifiers.push({ imported:
|
|
89
|
-
} else if (spec.type ===
|
|
88
|
+
specifiers.push({ imported: 'default', local: spec.local?.name ?? '' })
|
|
89
|
+
} else if (spec.type === 'ImportNamespaceSpecifier') {
|
|
90
90
|
isNamespace = true
|
|
91
|
-
specifiers.push({ imported:
|
|
92
|
-
} else if (spec.type ===
|
|
91
|
+
specifiers.push({ imported: '*', local: spec.local?.name ?? '' })
|
|
92
|
+
} else if (spec.type === 'ImportSpecifier') {
|
|
93
93
|
const imported =
|
|
94
|
-
spec.imported?.type ===
|
|
95
|
-
specifiers.push({ imported, local: spec.local?.name ??
|
|
94
|
+
spec.imported?.type === 'Identifier' ? spec.imported.name : (spec.imported?.value ?? '')
|
|
95
|
+
specifiers.push({ imported, local: spec.local?.name ?? '' })
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
package/src/utils/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export {
|
|
|
21
21
|
isPeekCall,
|
|
22
22
|
isSetCall,
|
|
23
23
|
isTernaryWithJSX,
|
|
24
|
-
} from
|
|
24
|
+
} from './ast'
|
|
25
25
|
export {
|
|
26
26
|
BROWSER_GLOBALS,
|
|
27
27
|
CONTEXT_APIS,
|
|
@@ -35,14 +35,14 @@ export {
|
|
|
35
35
|
LIFECYCLE_APIS,
|
|
36
36
|
PYREON_PREFIX,
|
|
37
37
|
REACTIVITY_APIS,
|
|
38
|
-
} from
|
|
39
|
-
export { LineIndex } from
|
|
38
|
+
} from './imports'
|
|
39
|
+
export { LineIndex } from './source'
|
|
40
40
|
|
|
41
41
|
/** Supported JS/TS file extensions for linting. */
|
|
42
|
-
export const JS_EXTENSIONS = new Set([
|
|
42
|
+
export const JS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs'])
|
|
43
43
|
|
|
44
44
|
/** Check if a file path has a supported JS/TS extension. */
|
|
45
45
|
export function hasJsExtension(filePath: string): boolean {
|
|
46
|
-
const ext = filePath.slice(filePath.lastIndexOf(
|
|
46
|
+
const ext = filePath.slice(filePath.lastIndexOf('.'))
|
|
47
47
|
return JS_EXTENSIONS.has(ext)
|
|
48
48
|
}
|
package/src/utils/source.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SourceLocation } from
|
|
1
|
+
import type { SourceLocation } from '../types'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Fast offset→line/column conversion using binary search over precomputed line starts.
|
|
@@ -9,7 +9,7 @@ export class LineIndex {
|
|
|
9
9
|
constructor(sourceText: string) {
|
|
10
10
|
this.lineStarts = [0]
|
|
11
11
|
for (let i = 0; i < sourceText.length; i++) {
|
|
12
|
-
if (sourceText[i] ===
|
|
12
|
+
if (sourceText[i] === '\n') {
|
|
13
13
|
this.lineStarts.push(i + 1)
|
|
14
14
|
}
|
|
15
15
|
}
|
package/src/watcher.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { readFileSync, watch } from
|
|
2
|
-
import { resolve } from
|
|
3
|
-
import { AstCache } from
|
|
4
|
-
import { createIgnoreFilter } from
|
|
5
|
-
import { getPreset } from
|
|
6
|
-
import { formatCompact, formatJSON, formatText } from
|
|
7
|
-
import { allRules } from
|
|
8
|
-
import { lintFile } from
|
|
9
|
-
import type { LintConfig, LintOptions, LintResult, Severity } from
|
|
10
|
-
import { hasJsExtension } from
|
|
1
|
+
import { readFileSync, watch } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import { AstCache } from './cache'
|
|
4
|
+
import { createIgnoreFilter } from './config/ignore'
|
|
5
|
+
import { getPreset } from './config/presets'
|
|
6
|
+
import { formatCompact, formatJSON, formatText } from './reporter'
|
|
7
|
+
import { allRules } from './rules/index'
|
|
8
|
+
import { lintFile } from './runner'
|
|
9
|
+
import type { LintConfig, LintOptions, LintResult, Severity } from './types'
|
|
10
|
+
import { hasJsExtension } from './utils/index'
|
|
11
11
|
|
|
12
12
|
function formatOutput(result: LintResult, format: string): string {
|
|
13
|
-
if (format ===
|
|
14
|
-
if (format ===
|
|
13
|
+
if (format === 'json') return formatJSON(result)
|
|
14
|
+
if (format === 'compact') return formatCompact(result)
|
|
15
15
|
return formatText(result)
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -30,12 +30,12 @@ function formatOutput(result: LintResult, format: string): string {
|
|
|
30
30
|
*/
|
|
31
31
|
export function watchAndLint(options: LintOptions & { format: string }): void {
|
|
32
32
|
const cache = new AstCache()
|
|
33
|
-
const preset = options.preset ??
|
|
33
|
+
const preset = options.preset ?? 'recommended'
|
|
34
34
|
const config = getPreset(preset)
|
|
35
35
|
|
|
36
36
|
applyOverrides(config, options.ruleOverrides)
|
|
37
37
|
|
|
38
|
-
const cwd = resolve(
|
|
38
|
+
const cwd = resolve('.')
|
|
39
39
|
const isIgnored = createIgnoreFilter(cwd, options.ignore)
|
|
40
40
|
|
|
41
41
|
// Debounce map: filePath -> timeout
|
|
@@ -84,7 +84,7 @@ function applyOverrides(
|
|
|
84
84
|
function relintFile(filePath: string, config: LintConfig, cache: AstCache, format: string): void {
|
|
85
85
|
let source: string
|
|
86
86
|
try {
|
|
87
|
-
source = readFileSync(filePath,
|
|
87
|
+
source = readFileSync(filePath, 'utf-8')
|
|
88
88
|
} catch {
|
|
89
89
|
return
|
|
90
90
|
}
|
|
@@ -101,12 +101,12 @@ function relintFile(filePath: string, config: LintConfig, cache: AstCache, forma
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
for (const d of fileResult.diagnostics) {
|
|
104
|
-
if (d.severity ===
|
|
105
|
-
else if (d.severity ===
|
|
106
|
-
else if (d.severity ===
|
|
104
|
+
if (d.severity === 'error') result.totalErrors++
|
|
105
|
+
else if (d.severity === 'warn') result.totalWarnings++
|
|
106
|
+
else if (d.severity === 'info') result.totalInfos++
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// Clear screen and print
|
|
110
|
-
process.stdout.write(
|
|
110
|
+
process.stdout.write('\x1b[2J\x1b[H')
|
|
111
111
|
console.log(formatOutput(result, format))
|
|
112
112
|
}
|