@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.
Files changed (86) hide show
  1. package/README.md +91 -91
  2. package/lib/analysis/cli.js.html +1 -1
  3. package/lib/analysis/index.js.html +1 -1
  4. package/lib/cli.js +214 -1
  5. package/lib/cli.js.map +1 -1
  6. package/lib/index.js +207 -1
  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 +15 -15
  11. package/src/cache.ts +1 -1
  12. package/src/cli.ts +38 -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 -19
  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 -13
  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 +5 -5
  85. package/src/utils/source.ts +2 -2
  86. package/src/watcher.ts +19 -19
@@ -1,15 +1,15 @@
1
- import { AstCache } from "../cache"
2
- import { createIgnoreFilter } from "../config/ignore"
3
- import { loadConfig } from "../config/loader"
4
- import { getPreset } from "../config/presets"
5
- import { allRules } from "../rules/index"
6
- import { applyFixes, lintFile } from "../runner"
7
- import type { LintConfig, Rule } from "../types"
8
- import { LineIndex } from "../utils/source"
1
+ import { AstCache } from '../cache'
2
+ import { createIgnoreFilter } from '../config/ignore'
3
+ import { loadConfig } from '../config/loader'
4
+ import { getPreset } from '../config/presets'
5
+ import { allRules } from '../rules/index'
6
+ import { applyFixes, lintFile } from '../runner'
7
+ import type { LintConfig, Rule } from '../types'
8
+ import { LineIndex } from '../utils/source'
9
9
 
10
10
  // Helper to create a config that enables all rules at default severity
11
11
  function defaultConfig(): LintConfig {
12
- return getPreset("recommended")
12
+ return getPreset('recommended')
13
13
  }
14
14
 
15
15
  // Helper to lint a string with specific rules
@@ -18,7 +18,7 @@ function lintSource(
18
18
  rules?: Rule[],
19
19
  filePath?: string,
20
20
  ): ReturnType<typeof lintFile> {
21
- return lintFile(filePath ?? "test.tsx", source, rules ?? allRules, defaultConfig())
21
+ return lintFile(filePath ?? 'test.tsx', source, rules ?? allRules, defaultConfig())
22
22
  }
23
23
 
24
24
  // Helper to find diagnostics by rule ID
@@ -30,54 +30,54 @@ function findByRule(result: ReturnType<typeof lintFile>, ruleId: string) {
30
30
  function lintWith(ruleId: string, source: string, filePath?: string) {
31
31
  const rule = allRules.find((r) => r.meta.id === ruleId)
32
32
  if (!rule) throw new Error(`Rule not found: ${ruleId}`)
33
- return lintFile(filePath ?? "test.tsx", source, [rule], defaultConfig())
33
+ return lintFile(filePath ?? 'test.tsx', source, [rule], defaultConfig())
34
34
  }
35
35
 
36
36
  // ── Rule Metadata ───────────────────────────────────────────────────────────
37
37
 
38
- describe("Rule metadata", () => {
39
- it("should have 55 rules", () => {
40
- expect(allRules.length).toBe(55)
38
+ describe('Rule metadata', () => {
39
+ it('should have 56 rules', () => {
40
+ expect(allRules.length).toBe(56)
41
41
  })
42
42
 
43
- it("should have unique rule IDs", () => {
43
+ it('should have unique rule IDs', () => {
44
44
  const ids = allRules.map((r) => r.meta.id)
45
45
  const unique = new Set(ids)
46
46
  expect(unique.size).toBe(ids.length)
47
47
  })
48
48
 
49
- it("all rule IDs should start with pyreon/", () => {
49
+ it('all rule IDs should start with pyreon/', () => {
50
50
  for (const rule of allRules) {
51
51
  expect(rule.meta.id).toMatch(/^pyreon\//)
52
52
  }
53
53
  })
54
54
 
55
- it("all rules should have valid categories", () => {
55
+ it('all rules should have valid categories', () => {
56
56
  const validCategories = new Set([
57
- "reactivity",
58
- "jsx",
59
- "lifecycle",
60
- "performance",
61
- "ssr",
62
- "architecture",
63
- "store",
64
- "form",
65
- "styling",
66
- "hooks",
67
- "accessibility",
68
- "router",
57
+ 'reactivity',
58
+ 'jsx',
59
+ 'lifecycle',
60
+ 'performance',
61
+ 'ssr',
62
+ 'architecture',
63
+ 'store',
64
+ 'form',
65
+ 'styling',
66
+ 'hooks',
67
+ 'accessibility',
68
+ 'router',
69
69
  ])
70
70
  for (const rule of allRules) {
71
71
  expect(validCategories.has(rule.meta.category)).toBe(true)
72
72
  }
73
73
  })
74
74
 
75
- it("should have correct category counts", () => {
75
+ it('should have correct category counts', () => {
76
76
  const counts: Record<string, number> = {}
77
77
  for (const rule of allRules) {
78
78
  counts[rule.meta.category] = (counts[rule.meta.category] ?? 0) + 1
79
79
  }
80
- expect(counts.reactivity).toBe(8)
80
+ expect(counts.reactivity).toBe(9)
81
81
  expect(counts.jsx).toBe(11)
82
82
  expect(counts.lifecycle).toBe(4)
83
83
  expect(counts.performance).toBe(4)
@@ -94,25 +94,25 @@ describe("Rule metadata", () => {
94
94
 
95
95
  // ── Runner Basics ───────────────────────────────────────────────────────────
96
96
 
97
- describe("Runner", () => {
98
- it("should parse valid TypeScript/JSX", () => {
97
+ describe('Runner', () => {
98
+ it('should parse valid TypeScript/JSX', () => {
99
99
  const result = lintSource(`const x = 1`)
100
100
  expect(result.diagnostics).toBeDefined()
101
101
  })
102
102
 
103
- it("should skip non-JS files", () => {
104
- const result = lintFile("test.css", "body { color: red }", allRules, defaultConfig())
103
+ it('should skip non-JS files', () => {
104
+ const result = lintFile('test.css', 'body { color: red }', allRules, defaultConfig())
105
105
  expect(result.diagnostics.length).toBe(0)
106
106
  })
107
107
 
108
- it("should sort diagnostics by position", () => {
108
+ it('should sort diagnostics by position', () => {
109
109
  const source = `
110
110
  const a = signal(0)
111
111
  const b = signal(0)
112
112
  // These are just declared, not used
113
113
  `
114
- const rule = allRules.find((r) => r.meta.id === "pyreon/no-signal-leak")
115
- if (!rule) throw new Error("Rule not found")
114
+ const rule = allRules.find((r) => r.meta.id === 'pyreon/no-signal-leak')
115
+ if (!rule) throw new Error('Rule not found')
116
116
  const result = lintSource(source, [rule])
117
117
  if (result.diagnostics.length >= 2) {
118
118
  const first = result.diagnostics[0]
@@ -123,11 +123,11 @@ const b = signal(0)
123
123
  }
124
124
  })
125
125
 
126
- it("should apply fixes in reverse order", () => {
126
+ it('should apply fixes in reverse order', () => {
127
127
  const source = `<div className="a" htmlFor="b" />`
128
128
  const result = lintSource(source)
129
- const classnameDiags = findByRule(result, "pyreon/no-classname")
130
- const htmlforDiags = findByRule(result, "pyreon/no-htmlfor")
129
+ const classnameDiags = findByRule(result, 'pyreon/no-classname')
130
+ const htmlforDiags = findByRule(result, 'pyreon/no-htmlfor')
131
131
 
132
132
  // Both should be fixable
133
133
  expect(classnameDiags.length).toBeGreaterThanOrEqual(1)
@@ -136,292 +136,292 @@ const b = signal(0)
136
136
  expect(htmlforDiags[0]?.fix).toBeDefined()
137
137
 
138
138
  const fixed = applyFixes(source, result.diagnostics)
139
- expect(fixed).toContain("class=")
140
- expect(fixed).toContain("for=")
141
- expect(fixed).not.toContain("className")
142
- expect(fixed).not.toContain("htmlFor")
139
+ expect(fixed).toContain('class=')
140
+ expect(fixed).toContain('for=')
141
+ expect(fixed).not.toContain('className')
142
+ expect(fixed).not.toContain('htmlFor')
143
143
  })
144
144
 
145
- it("should use AST cache when provided", () => {
145
+ it('should use AST cache when provided', () => {
146
146
  const cache = new AstCache()
147
147
  const source = `const x = 1`
148
148
  const config = defaultConfig()
149
149
 
150
150
  // First lint populates cache
151
- lintFile("test.ts", source, allRules, config, cache)
151
+ lintFile('test.ts', source, allRules, config, cache)
152
152
  expect(cache.size).toBe(1)
153
153
 
154
154
  // Second lint reuses cache
155
- lintFile("test.ts", source, allRules, config, cache)
155
+ lintFile('test.ts', source, allRules, config, cache)
156
156
  expect(cache.size).toBe(1)
157
157
  })
158
158
  })
159
159
 
160
160
  // ── Source Utilities ─────────────────────────────────────────────────────────
161
161
 
162
- describe("LineIndex", () => {
163
- it("should compute line/column for single-line source", () => {
164
- const idx = new LineIndex("hello world")
162
+ describe('LineIndex', () => {
163
+ it('should compute line/column for single-line source', () => {
164
+ const idx = new LineIndex('hello world')
165
165
  expect(idx.locate(0)).toEqual({ line: 1, column: 0 })
166
166
  expect(idx.locate(5)).toEqual({ line: 1, column: 5 })
167
167
  })
168
168
 
169
- it("should compute line/column for multi-line source", () => {
170
- const idx = new LineIndex("line1\nline2\nline3")
169
+ it('should compute line/column for multi-line source', () => {
170
+ const idx = new LineIndex('line1\nline2\nline3')
171
171
  expect(idx.locate(0)).toEqual({ line: 1, column: 0 })
172
172
  expect(idx.locate(6)).toEqual({ line: 2, column: 0 })
173
173
  expect(idx.locate(12)).toEqual({ line: 3, column: 0 })
174
174
  expect(idx.locate(8)).toEqual({ line: 2, column: 2 })
175
175
  })
176
176
 
177
- it("should handle empty source", () => {
178
- const idx = new LineIndex("")
177
+ it('should handle empty source', () => {
178
+ const idx = new LineIndex('')
179
179
  expect(idx.locate(0)).toEqual({ line: 1, column: 0 })
180
180
  })
181
181
  })
182
182
 
183
183
  // ── AST Cache ──────────────────────────────────────────────────────────────
184
184
 
185
- describe("AstCache", () => {
186
- it("should store and retrieve entries", () => {
185
+ describe('AstCache', () => {
186
+ it('should store and retrieve entries', () => {
187
187
  const cache = new AstCache()
188
- const lineIndex = new LineIndex("test")
189
- const program = { type: "Program" }
188
+ const lineIndex = new LineIndex('test')
189
+ const program = { type: 'Program' }
190
190
 
191
- cache.set("test", { program, lineIndex })
191
+ cache.set('test', { program, lineIndex })
192
192
  expect(cache.size).toBe(1)
193
193
 
194
- const result = cache.get("test")
194
+ const result = cache.get('test')
195
195
  expect(result).toBeDefined()
196
196
  expect(result!.program).toBe(program)
197
197
  })
198
198
 
199
- it("should return undefined for missing entries", () => {
199
+ it('should return undefined for missing entries', () => {
200
200
  const cache = new AstCache()
201
- expect(cache.get("missing")).toBeUndefined()
201
+ expect(cache.get('missing')).toBeUndefined()
202
202
  })
203
203
 
204
- it("should clear all entries", () => {
204
+ it('should clear all entries', () => {
205
205
  const cache = new AstCache()
206
- const lineIndex = new LineIndex("a")
207
- cache.set("a", { program: {}, lineIndex })
208
- cache.set("b", { program: {}, lineIndex })
206
+ const lineIndex = new LineIndex('a')
207
+ cache.set('a', { program: {}, lineIndex })
208
+ cache.set('b', { program: {}, lineIndex })
209
209
  expect(cache.size).toBe(2)
210
210
 
211
211
  cache.clear()
212
212
  expect(cache.size).toBe(0)
213
213
  })
214
214
 
215
- it("should use content-based keys (different content = different entry)", () => {
215
+ it('should use content-based keys (different content = different entry)', () => {
216
216
  const cache = new AstCache()
217
- const lineIndex = new LineIndex("a")
218
- cache.set("content1", { program: { id: 1 }, lineIndex })
219
- cache.set("content2", { program: { id: 2 }, lineIndex })
217
+ const lineIndex = new LineIndex('a')
218
+ cache.set('content1', { program: { id: 1 }, lineIndex })
219
+ cache.set('content2', { program: { id: 2 }, lineIndex })
220
220
  expect(cache.size).toBe(2)
221
221
  })
222
222
  })
223
223
 
224
224
  // ── Reactivity Rules ────────────────────────────────────────────────────────
225
225
 
226
- describe("Reactivity rules", () => {
227
- it("pyreon/no-bare-signal-in-jsx: flags {count()} in JSX", () => {
226
+ describe('Reactivity rules', () => {
227
+ it('pyreon/no-bare-signal-in-jsx: flags {count()} in JSX', () => {
228
228
  const source = `const App = () => <div>{count()}</div>`
229
229
  const result = lintSource(source)
230
- const diags = findByRule(result, "pyreon/no-bare-signal-in-jsx")
230
+ const diags = findByRule(result, 'pyreon/no-bare-signal-in-jsx')
231
231
  expect(diags.length).toBe(1)
232
232
  expect(diags[0]?.fix).toBeDefined()
233
233
  })
234
234
 
235
- it("pyreon/no-bare-signal-in-jsx: skips PascalCase and use* calls", () => {
235
+ it('pyreon/no-bare-signal-in-jsx: skips PascalCase and use* calls', () => {
236
236
  const source = `const App = () => <div>{MyComponent()}{useTheme()}</div>`
237
237
  const result = lintSource(source)
238
- const diags = findByRule(result, "pyreon/no-bare-signal-in-jsx")
238
+ const diags = findByRule(result, 'pyreon/no-bare-signal-in-jsx')
239
239
  expect(diags.length).toBe(0)
240
240
  })
241
241
 
242
- it("pyreon/no-signal-in-loop: flags signal inside for loop", () => {
242
+ it('pyreon/no-signal-in-loop: flags signal inside for loop', () => {
243
243
  const source = `for (let i = 0; i < 10; i++) { const s = signal(0) }`
244
244
  const result = lintSource(source)
245
- const diags = findByRule(result, "pyreon/no-signal-in-loop")
245
+ const diags = findByRule(result, 'pyreon/no-signal-in-loop')
246
246
  expect(diags.length).toBe(1)
247
247
  })
248
248
 
249
- it("pyreon/no-signal-in-loop: clean when outside loop", () => {
249
+ it('pyreon/no-signal-in-loop: clean when outside loop', () => {
250
250
  const source = `const s = signal(0)`
251
251
  const result = lintSource(source)
252
- const diags = findByRule(result, "pyreon/no-signal-in-loop")
252
+ const diags = findByRule(result, 'pyreon/no-signal-in-loop')
253
253
  expect(diags.length).toBe(0)
254
254
  })
255
255
 
256
- it("pyreon/no-nested-effect: flags effect inside effect", () => {
256
+ it('pyreon/no-nested-effect: flags effect inside effect', () => {
257
257
  const source = `effect(() => { effect(() => {}) })`
258
258
  const result = lintSource(source)
259
- const diags = findByRule(result, "pyreon/no-nested-effect")
259
+ const diags = findByRule(result, 'pyreon/no-nested-effect')
260
260
  expect(diags.length).toBe(1)
261
261
  })
262
262
 
263
- it("pyreon/no-peek-in-tracked: flags .peek() inside effect", () => {
263
+ it('pyreon/no-peek-in-tracked: flags .peek() inside effect', () => {
264
264
  const source = `effect(() => { const v = x.peek() })`
265
265
  const result = lintSource(source)
266
- const diags = findByRule(result, "pyreon/no-peek-in-tracked")
266
+ const diags = findByRule(result, 'pyreon/no-peek-in-tracked')
267
267
  expect(diags.length).toBe(1)
268
268
  })
269
269
 
270
- it("pyreon/no-unbatched-updates: flags 3+ .set() without batch", () => {
270
+ it('pyreon/no-unbatched-updates: flags 3+ .set() without batch', () => {
271
271
  const source = `function update() { a.set(1); b.set(2); c.set(3) }`
272
272
  const result = lintSource(source)
273
- const diags = findByRule(result, "pyreon/no-unbatched-updates")
273
+ const diags = findByRule(result, 'pyreon/no-unbatched-updates')
274
274
  expect(diags.length).toBe(1)
275
275
  })
276
276
 
277
- it("pyreon/no-unbatched-updates: clean with batch", () => {
277
+ it('pyreon/no-unbatched-updates: clean with batch', () => {
278
278
  const source = `function update() { batch(() => { a.set(1); b.set(2); c.set(3) }) }`
279
279
  const result = lintSource(source)
280
280
  // The outer function has batch, so no warning on it
281
281
  // The inner arrow gets checked too but has no .set() calls directly
282
- const diags = findByRule(result, "pyreon/no-unbatched-updates")
282
+ const diags = findByRule(result, 'pyreon/no-unbatched-updates')
283
283
  expect(diags.length).toBe(0)
284
284
  })
285
285
 
286
- it("pyreon/prefer-computed: flags effect with single .set()", () => {
286
+ it('pyreon/prefer-computed: flags effect with single .set()', () => {
287
287
  const source = `effect(() => { x.set(a() + b()) })`
288
288
  const result = lintSource(source)
289
- const diags = findByRule(result, "pyreon/prefer-computed")
289
+ const diags = findByRule(result, 'pyreon/prefer-computed')
290
290
  expect(diags.length).toBe(1)
291
291
  })
292
292
 
293
- it("pyreon/no-effect-assignment: flags effect with single .update()", () => {
293
+ it('pyreon/no-effect-assignment: flags effect with single .update()', () => {
294
294
  const source = `effect(() => { x.update(v => v + 1) })`
295
295
  const result = lintSource(source)
296
- const diags = findByRule(result, "pyreon/no-effect-assignment")
296
+ const diags = findByRule(result, 'pyreon/no-effect-assignment')
297
297
  expect(diags.length).toBe(1)
298
298
  })
299
299
 
300
- it("pyreon/no-signal-leak: flags unused signal declarations", () => {
300
+ it('pyreon/no-signal-leak: flags unused signal declarations', () => {
301
301
  const source = `const unused = signal(0)`
302
302
  const result = lintSource(source)
303
- const diags = findByRule(result, "pyreon/no-signal-leak")
303
+ const diags = findByRule(result, 'pyreon/no-signal-leak')
304
304
  expect(diags.length).toBe(1)
305
305
  })
306
306
 
307
- it("pyreon/no-signal-leak: clean when signal is used", () => {
307
+ it('pyreon/no-signal-leak: clean when signal is used', () => {
308
308
  const source = `const count = signal(0)\nconst double = computed(() => count() * 2)`
309
309
  const result = lintSource(source)
310
- const diags = findByRule(result, "pyreon/no-signal-leak")
310
+ const diags = findByRule(result, 'pyreon/no-signal-leak')
311
311
  expect(diags.length).toBe(0)
312
312
  })
313
313
  })
314
314
 
315
315
  // ── JSX Rules ───────────────────────────────────────────────────────────────
316
316
 
317
- describe("JSX rules", () => {
318
- it("pyreon/no-map-in-jsx: flags .map() in JSX", () => {
317
+ describe('JSX rules', () => {
318
+ it('pyreon/no-map-in-jsx: flags .map() in JSX', () => {
319
319
  const source = `const App = () => <ul>{items.map(i => <li>{i}</li>)}</ul>`
320
320
  const result = lintSource(source)
321
- const diags = findByRule(result, "pyreon/no-map-in-jsx")
321
+ const diags = findByRule(result, 'pyreon/no-map-in-jsx')
322
322
  expect(diags.length).toBe(1)
323
323
  })
324
324
 
325
- it("pyreon/use-by-not-key: flags key on <For>", () => {
325
+ it('pyreon/use-by-not-key: flags key on <For>', () => {
326
326
  const source = `const App = () => <For each={items} key={r => r.id}>{r => <li />}</For>`
327
327
  const result = lintSource(source)
328
- const diags = findByRule(result, "pyreon/use-by-not-key")
328
+ const diags = findByRule(result, 'pyreon/use-by-not-key')
329
329
  expect(diags.length).toBe(1)
330
330
  expect(diags[0]?.fix).toBeDefined()
331
331
  })
332
332
 
333
- it("pyreon/no-classname: flags className and fixes to class", () => {
333
+ it('pyreon/no-classname: flags className and fixes to class', () => {
334
334
  const source = `const App = () => <div className="foo" />`
335
335
  const result = lintSource(source)
336
- const diags = findByRule(result, "pyreon/no-classname")
336
+ const diags = findByRule(result, 'pyreon/no-classname')
337
337
  expect(diags.length).toBe(1)
338
338
  expect(diags[0]?.fix).toBeDefined()
339
339
  })
340
340
 
341
- it("pyreon/no-htmlfor: flags htmlFor and fixes to for", () => {
341
+ it('pyreon/no-htmlfor: flags htmlFor and fixes to for', () => {
342
342
  const source = `const App = () => <label htmlFor="name" />`
343
343
  const result = lintSource(source)
344
- const diags = findByRule(result, "pyreon/no-htmlfor")
344
+ const diags = findByRule(result, 'pyreon/no-htmlfor')
345
345
  expect(diags.length).toBe(1)
346
346
  })
347
347
 
348
- it("pyreon/no-onchange: flags onChange on input", () => {
348
+ it('pyreon/no-onchange: flags onChange on input', () => {
349
349
  const source = `const App = () => <input onChange={handler} />`
350
350
  const result = lintSource(source)
351
- const diags = findByRule(result, "pyreon/no-onchange")
351
+ const diags = findByRule(result, 'pyreon/no-onchange')
352
352
  expect(diags.length).toBe(1)
353
353
  })
354
354
 
355
- it("pyreon/no-onchange: clean on non-input elements", () => {
355
+ it('pyreon/no-onchange: clean on non-input elements', () => {
356
356
  const source = `const App = () => <div onChange={handler} />`
357
357
  const result = lintSource(source)
358
- const diags = findByRule(result, "pyreon/no-onchange")
358
+ const diags = findByRule(result, 'pyreon/no-onchange')
359
359
  expect(diags.length).toBe(0)
360
360
  })
361
361
 
362
- it("pyreon/no-ternary-conditional: flags ternary with JSX", () => {
362
+ it('pyreon/no-ternary-conditional: flags ternary with JSX', () => {
363
363
  const source = `const App = () => <div>{flag ? <span>a</span> : <span>b</span>}</div>`
364
364
  const result = lintSource(source)
365
- const diags = findByRule(result, "pyreon/no-ternary-conditional")
365
+ const diags = findByRule(result, 'pyreon/no-ternary-conditional')
366
366
  expect(diags.length).toBe(1)
367
367
  })
368
368
 
369
- it("pyreon/no-and-conditional: flags && with JSX", () => {
369
+ it('pyreon/no-and-conditional: flags && with JSX', () => {
370
370
  const source = `const App = () => <div>{flag && <span>yes</span>}</div>`
371
371
  const result = lintSource(source)
372
- const diags = findByRule(result, "pyreon/no-and-conditional")
372
+ const diags = findByRule(result, 'pyreon/no-and-conditional')
373
373
  expect(diags.length).toBe(1)
374
374
  })
375
375
 
376
- it("pyreon/no-missing-for-by: flags <For> without by", () => {
376
+ it('pyreon/no-missing-for-by: flags <For> without by', () => {
377
377
  const source = `const App = () => <For each={items}>{r => <li />}</For>`
378
378
  const result = lintSource(source)
379
- const diags = findByRule(result, "pyreon/no-missing-for-by")
379
+ const diags = findByRule(result, 'pyreon/no-missing-for-by')
380
380
  expect(diags.length).toBe(1)
381
381
  })
382
382
 
383
- it("pyreon/no-missing-for-by: clean when by is present", () => {
383
+ it('pyreon/no-missing-for-by: clean when by is present', () => {
384
384
  const source = `const App = () => <For each={items} by={r => r.id}>{r => <li />}</For>`
385
385
  const result = lintSource(source)
386
- const diags = findByRule(result, "pyreon/no-missing-for-by")
386
+ const diags = findByRule(result, 'pyreon/no-missing-for-by')
387
387
  expect(diags.length).toBe(0)
388
388
  })
389
389
 
390
- it("pyreon/no-props-destructure: flags destructured props in component", () => {
390
+ it('pyreon/no-props-destructure: flags destructured props in component', () => {
391
391
  const source = `const App = ({ name }) => <div>{name}</div>`
392
392
  const result = lintSource(source)
393
- const diags = findByRule(result, "pyreon/no-props-destructure")
393
+ const diags = findByRule(result, 'pyreon/no-props-destructure')
394
394
  expect(diags.length).toBe(1)
395
395
  })
396
396
 
397
- it("pyreon/no-props-destructure: clean for non-component functions", () => {
397
+ it('pyreon/no-props-destructure: clean for non-component functions', () => {
398
398
  const source = `const fn = ({ a, b }) => a + b`
399
399
  const result = lintSource(source)
400
- const diags = findByRule(result, "pyreon/no-props-destructure")
400
+ const diags = findByRule(result, 'pyreon/no-props-destructure')
401
401
  expect(diags.length).toBe(0)
402
402
  })
403
403
 
404
- it("pyreon/no-index-as-by: flags by={(_, i) => i}", () => {
404
+ it('pyreon/no-index-as-by: flags by={(_, i) => i}', () => {
405
405
  const source = `const App = () => <For each={items} by={(_, i) => i}>{r => <li />}</For>`
406
406
  const result = lintSource(source)
407
- const diags = findByRule(result, "pyreon/no-index-as-by")
407
+ const diags = findByRule(result, 'pyreon/no-index-as-by')
408
408
  expect(diags.length).toBe(1)
409
409
  })
410
410
 
411
- it("pyreon/no-children-access: flags props.children in renderer file", () => {
411
+ it('pyreon/no-children-access: flags props.children in renderer file', () => {
412
412
  const result = lintWith(
413
- "pyreon/no-children-access",
413
+ 'pyreon/no-children-access',
414
414
  `import { renderToString } from "@pyreon/runtime-server"\nconst c = props.children`,
415
- "renderer.ts",
415
+ 'renderer.ts',
416
416
  )
417
417
  expect(result.diagnostics.length).toBe(1)
418
418
  })
419
419
 
420
- it("pyreon/no-children-access: clean in non-renderer file", () => {
420
+ it('pyreon/no-children-access: clean in non-renderer file', () => {
421
421
  const result = lintWith(
422
- "pyreon/no-children-access",
422
+ 'pyreon/no-children-access',
423
423
  `const c = props.children`,
424
- "component.tsx",
424
+ 'component.tsx',
425
425
  )
426
426
  expect(result.diagnostics.length).toBe(0)
427
427
  })
@@ -429,110 +429,110 @@ describe("JSX rules", () => {
429
429
 
430
430
  // ── Lifecycle Rules ─────────────────────────────────────────────────────────
431
431
 
432
- describe("Lifecycle rules", () => {
433
- it("pyreon/no-missing-cleanup: flags onMount with setInterval and no return", () => {
432
+ describe('Lifecycle rules', () => {
433
+ it('pyreon/no-missing-cleanup: flags onMount with setInterval and no return', () => {
434
434
  const source = `onMount(() => { setInterval(() => {}, 1000) })`
435
435
  const result = lintSource(source)
436
- const diags = findByRule(result, "pyreon/no-missing-cleanup")
436
+ const diags = findByRule(result, 'pyreon/no-missing-cleanup')
437
437
  expect(diags.length).toBe(1)
438
438
  })
439
439
 
440
- it("pyreon/no-missing-cleanup: clean when onMount returns cleanup", () => {
440
+ it('pyreon/no-missing-cleanup: clean when onMount returns cleanup', () => {
441
441
  const source = `onMount(() => { const id = setInterval(() => {}, 1000); return () => clearInterval(id) })`
442
442
  const result = lintSource(source)
443
- const diags = findByRule(result, "pyreon/no-missing-cleanup")
443
+ const diags = findByRule(result, 'pyreon/no-missing-cleanup')
444
444
  expect(diags.length).toBe(0)
445
445
  })
446
446
 
447
- it("pyreon/no-mount-in-effect: flags onMount inside effect", () => {
447
+ it('pyreon/no-mount-in-effect: flags onMount inside effect', () => {
448
448
  const source = `effect(() => { onMount(() => {}) })`
449
449
  const result = lintSource(source)
450
- const diags = findByRule(result, "pyreon/no-mount-in-effect")
450
+ const diags = findByRule(result, 'pyreon/no-mount-in-effect')
451
451
  expect(diags.length).toBe(1)
452
452
  })
453
453
 
454
- it("pyreon/no-effect-in-mount: flags effect inside onMount", () => {
454
+ it('pyreon/no-effect-in-mount: flags effect inside onMount', () => {
455
455
  const source = `onMount(() => { effect(() => {}) })`
456
456
  const result = lintSource(source)
457
- const diags = findByRule(result, "pyreon/no-effect-in-mount")
457
+ const diags = findByRule(result, 'pyreon/no-effect-in-mount')
458
458
  expect(diags.length).toBe(1)
459
459
  })
460
460
 
461
- it("pyreon/no-dom-in-setup: flags document.querySelector outside onMount", () => {
461
+ it('pyreon/no-dom-in-setup: flags document.querySelector outside onMount', () => {
462
462
  const source = `const el = document.querySelector(".app")`
463
463
  const result = lintSource(source)
464
- const diags = findByRule(result, "pyreon/no-dom-in-setup")
464
+ const diags = findByRule(result, 'pyreon/no-dom-in-setup')
465
465
  expect(diags.length).toBe(1)
466
466
  })
467
467
 
468
- it("pyreon/no-dom-in-setup: clean inside onMount", () => {
468
+ it('pyreon/no-dom-in-setup: clean inside onMount', () => {
469
469
  const source = `onMount(() => { document.querySelector(".app") })`
470
470
  const result = lintSource(source)
471
- const diags = findByRule(result, "pyreon/no-dom-in-setup")
471
+ const diags = findByRule(result, 'pyreon/no-dom-in-setup')
472
472
  expect(diags.length).toBe(0)
473
473
  })
474
474
  })
475
475
 
476
476
  // ── Performance Rules ───────────────────────────────────────────────────────
477
477
 
478
- describe("Performance rules", () => {
479
- it("pyreon/no-eager-import: flags static import of heavy packages", () => {
478
+ describe('Performance rules', () => {
479
+ it('pyreon/no-eager-import: flags static import of heavy packages', () => {
480
480
  const source = `import { Chart } from "@pyreon/charts"`
481
481
  const result = lintSource(source)
482
- const diags = findByRule(result, "pyreon/no-eager-import")
482
+ const diags = findByRule(result, 'pyreon/no-eager-import')
483
483
  expect(diags.length).toBe(1)
484
484
  })
485
485
 
486
- it("pyreon/no-eager-import: clean for lightweight packages", () => {
486
+ it('pyreon/no-eager-import: clean for lightweight packages', () => {
487
487
  const source = `import { signal } from "@pyreon/reactivity"`
488
488
  const result = lintSource(source)
489
- const diags = findByRule(result, "pyreon/no-eager-import")
489
+ const diags = findByRule(result, 'pyreon/no-eager-import')
490
490
  expect(diags.length).toBe(0)
491
491
  })
492
492
 
493
- it("pyreon/no-effect-in-for: flags effect() inside <For>", () => {
493
+ it('pyreon/no-effect-in-for: flags effect() inside <For>', () => {
494
494
  const result = lintWith(
495
- "pyreon/no-effect-in-for",
495
+ 'pyreon/no-effect-in-for',
496
496
  `const App = () => <For each={items}>{r => { effect(() => {}); return <li /> }}</For>`,
497
497
  )
498
498
  expect(result.diagnostics.length).toBe(1)
499
499
  })
500
500
 
501
- it("pyreon/no-effect-in-for: clean when effect is outside <For>", () => {
501
+ it('pyreon/no-effect-in-for: clean when effect is outside <For>', () => {
502
502
  const result = lintWith(
503
- "pyreon/no-effect-in-for",
503
+ 'pyreon/no-effect-in-for',
504
504
  `effect(() => {})\nconst App = () => <For each={items}>{r => <li />}</For>`,
505
505
  )
506
506
  expect(result.diagnostics.length).toBe(0)
507
507
  })
508
508
 
509
- it("pyreon/no-large-for-without-by: flags <For> without by prop", () => {
509
+ it('pyreon/no-large-for-without-by: flags <For> without by prop', () => {
510
510
  const result = lintWith(
511
- "pyreon/no-large-for-without-by",
511
+ 'pyreon/no-large-for-without-by',
512
512
  `const App = () => <For each={items}>{r => <li />}</For>`,
513
513
  )
514
514
  expect(result.diagnostics.length).toBe(1)
515
515
  })
516
516
 
517
- it("pyreon/no-large-for-without-by: clean with by prop", () => {
517
+ it('pyreon/no-large-for-without-by: clean with by prop', () => {
518
518
  const result = lintWith(
519
- "pyreon/no-large-for-without-by",
519
+ 'pyreon/no-large-for-without-by',
520
520
  `const App = () => <For each={items} by={r => r.id}>{r => <li />}</For>`,
521
521
  )
522
522
  expect(result.diagnostics.length).toBe(0)
523
523
  })
524
524
 
525
- it("pyreon/prefer-show-over-display: flags conditional display style", () => {
525
+ it('pyreon/prefer-show-over-display: flags conditional display style', () => {
526
526
  const result = lintWith(
527
- "pyreon/prefer-show-over-display",
527
+ 'pyreon/prefer-show-over-display',
528
528
  `const App = () => <div style={{ display: visible ? "block" : "none" }} />`,
529
529
  )
530
530
  expect(result.diagnostics.length).toBe(1)
531
531
  })
532
532
 
533
- it("pyreon/prefer-show-over-display: clean with static display", () => {
533
+ it('pyreon/prefer-show-over-display: clean with static display', () => {
534
534
  const result = lintWith(
535
- "pyreon/prefer-show-over-display",
535
+ 'pyreon/prefer-show-over-display',
536
536
  `const App = () => <div style={{ display: "block" }} />`,
537
537
  )
538
538
  expect(result.diagnostics.length).toBe(0)
@@ -541,248 +541,248 @@ describe("Performance rules", () => {
541
541
 
542
542
  // ── SSR Rules ───────────────────────────────────────────────────────────────
543
543
 
544
- describe("SSR rules", () => {
545
- it("pyreon/no-window-in-ssr: flags window outside onMount", () => {
544
+ describe('SSR rules', () => {
545
+ it('pyreon/no-window-in-ssr: flags window outside onMount', () => {
546
546
  const source = `const w = window.innerWidth`
547
547
  const result = lintSource(source)
548
- const diags = findByRule(result, "pyreon/no-window-in-ssr")
548
+ const diags = findByRule(result, 'pyreon/no-window-in-ssr')
549
549
  expect(diags.length).toBeGreaterThanOrEqual(1)
550
550
  })
551
551
 
552
- it("pyreon/no-window-in-ssr: clean inside onMount", () => {
552
+ it('pyreon/no-window-in-ssr: clean inside onMount', () => {
553
553
  const source = `onMount(() => { const w = window.innerWidth })`
554
554
  const result = lintSource(source)
555
- const diags = findByRule(result, "pyreon/no-window-in-ssr")
555
+ const diags = findByRule(result, 'pyreon/no-window-in-ssr')
556
556
  expect(diags.length).toBe(0)
557
557
  })
558
558
 
559
- it("pyreon/no-window-in-ssr: clean with typeof guard", () => {
559
+ it('pyreon/no-window-in-ssr: clean with typeof guard', () => {
560
560
  const source = `if (typeof window !== "undefined") { const w = window.innerWidth }`
561
561
  const result = lintSource(source)
562
- const diags = findByRule(result, "pyreon/no-window-in-ssr")
562
+ const diags = findByRule(result, 'pyreon/no-window-in-ssr')
563
563
  expect(diags.length).toBe(0)
564
564
  })
565
565
 
566
- it("pyreon/no-mismatch-risk: flags Date.now() in JSX", () => {
566
+ it('pyreon/no-mismatch-risk: flags Date.now() in JSX', () => {
567
567
  const source = `const App = () => <div>{Date.now()}</div>`
568
568
  const result = lintSource(source)
569
- const diags = findByRule(result, "pyreon/no-mismatch-risk")
569
+ const diags = findByRule(result, 'pyreon/no-mismatch-risk')
570
570
  expect(diags.length).toBe(1)
571
571
  })
572
572
 
573
- it("pyreon/prefer-request-context: flags module-level signal in server file", () => {
573
+ it('pyreon/prefer-request-context: flags module-level signal in server file', () => {
574
574
  const source = `const state = signal(0)`
575
- const result = lintFile("app.server.ts", source, allRules, defaultConfig())
576
- const diags = findByRule(result, "pyreon/prefer-request-context")
575
+ const result = lintFile('app.server.ts', source, allRules, defaultConfig())
576
+ const diags = findByRule(result, 'pyreon/prefer-request-context')
577
577
  expect(diags.length).toBe(1)
578
578
  })
579
579
  })
580
580
 
581
581
  // ── Architecture Rules ──────────────────────────────────────────────────────
582
582
 
583
- describe("Architecture rules", () => {
584
- it("pyreon/no-deep-import: flags @pyreon/*/src/ imports", () => {
583
+ describe('Architecture rules', () => {
584
+ it('pyreon/no-deep-import: flags @pyreon/*/src/ imports', () => {
585
585
  const source = `import { something } from "@pyreon/core/src/signal"`
586
586
  const result = lintSource(source)
587
- const diags = findByRule(result, "pyreon/no-deep-import")
587
+ const diags = findByRule(result, 'pyreon/no-deep-import')
588
588
  expect(diags.length).toBe(1)
589
589
  })
590
590
 
591
- it("pyreon/no-deep-import: clean for normal imports", () => {
591
+ it('pyreon/no-deep-import: clean for normal imports', () => {
592
592
  const source = `import { signal } from "@pyreon/reactivity"`
593
593
  const result = lintSource(source)
594
- const diags = findByRule(result, "pyreon/no-deep-import")
594
+ const diags = findByRule(result, 'pyreon/no-deep-import')
595
595
  expect(diags.length).toBe(0)
596
596
  })
597
597
 
598
- it("pyreon/dev-guard-warnings: flags console.warn without __DEV__", () => {
598
+ it('pyreon/dev-guard-warnings: flags console.warn without __DEV__', () => {
599
599
  const source = `console.warn("something")`
600
600
  const result = lintSource(source)
601
- const diags = findByRule(result, "pyreon/dev-guard-warnings")
601
+ const diags = findByRule(result, 'pyreon/dev-guard-warnings')
602
602
  expect(diags.length).toBe(1)
603
603
  })
604
604
 
605
- it("pyreon/dev-guard-warnings: clean inside __DEV__ guard", () => {
605
+ it('pyreon/dev-guard-warnings: clean inside __DEV__ guard', () => {
606
606
  const source = `if (__DEV__) { console.warn("something") }`
607
607
  const result = lintSource(source)
608
- const diags = findByRule(result, "pyreon/dev-guard-warnings")
608
+ const diags = findByRule(result, 'pyreon/dev-guard-warnings')
609
609
  expect(diags.length).toBe(0)
610
610
  })
611
611
 
612
- it("pyreon/dev-guard-warnings: clean in test files", () => {
612
+ it('pyreon/dev-guard-warnings: clean in test files', () => {
613
613
  const source = `console.warn("test warning")`
614
- const result = lintFile("src/tests/foo.test.ts", source, allRules, defaultConfig())
615
- const diags = findByRule(result, "pyreon/dev-guard-warnings")
614
+ const result = lintFile('src/tests/foo.test.ts', source, allRules, defaultConfig())
615
+ const diags = findByRule(result, 'pyreon/dev-guard-warnings')
616
616
  expect(diags.length).toBe(0)
617
617
  })
618
618
 
619
- it("pyreon/no-error-without-prefix: flags throw without [Pyreon]", () => {
619
+ it('pyreon/no-error-without-prefix: flags throw without [Pyreon]', () => {
620
620
  const source = `throw new Error("something went wrong")`
621
621
  const result = lintSource(source)
622
- const diags = findByRule(result, "pyreon/no-error-without-prefix")
622
+ const diags = findByRule(result, 'pyreon/no-error-without-prefix')
623
623
  expect(diags.length).toBe(1)
624
624
  expect(diags[0]?.fix).toBeDefined()
625
625
  })
626
626
 
627
- it("pyreon/no-error-without-prefix: clean with [Pyreon] prefix", () => {
627
+ it('pyreon/no-error-without-prefix: clean with [Pyreon] prefix', () => {
628
628
  const source = `throw new Error("[Pyreon] something went wrong")`
629
629
  const result = lintSource(source)
630
- const diags = findByRule(result, "pyreon/no-error-without-prefix")
630
+ const diags = findByRule(result, 'pyreon/no-error-without-prefix')
631
631
  expect(diags.length).toBe(0)
632
632
  })
633
633
  })
634
634
 
635
635
  // ── Store Rules ─────────────────────────────────────────────────────────────
636
636
 
637
- describe("Store rules", () => {
638
- it("pyreon/no-duplicate-store-id: flags duplicate IDs", () => {
637
+ describe('Store rules', () => {
638
+ it('pyreon/no-duplicate-store-id: flags duplicate IDs', () => {
639
639
  const source = `
640
640
  defineStore("counter", () => {})
641
641
  defineStore("counter", () => {})
642
642
  `
643
643
  const result = lintSource(source)
644
- const diags = findByRule(result, "pyreon/no-duplicate-store-id")
644
+ const diags = findByRule(result, 'pyreon/no-duplicate-store-id')
645
645
  expect(diags.length).toBe(1)
646
646
  })
647
647
 
648
- it("pyreon/no-duplicate-store-id: clean with unique IDs", () => {
648
+ it('pyreon/no-duplicate-store-id: clean with unique IDs', () => {
649
649
  const source = `
650
650
  defineStore("counter", () => {})
651
651
  defineStore("user", () => {})
652
652
  `
653
653
  const result = lintSource(source)
654
- const diags = findByRule(result, "pyreon/no-duplicate-store-id")
654
+ const diags = findByRule(result, 'pyreon/no-duplicate-store-id')
655
655
  expect(diags.length).toBe(0)
656
656
  })
657
657
 
658
- it("pyreon/no-mutate-store-state: flags store.signal.set()", () => {
659
- const result = lintWith("pyreon/no-mutate-store-state", `userStore.count.set(5)`)
658
+ it('pyreon/no-mutate-store-state: flags store.signal.set()', () => {
659
+ const result = lintWith('pyreon/no-mutate-store-state', `userStore.count.set(5)`)
660
660
  expect(result.diagnostics.length).toBe(1)
661
661
  })
662
662
 
663
- it("pyreon/no-mutate-store-state: clean for non-store .set()", () => {
664
- const result = lintWith("pyreon/no-mutate-store-state", `count.set(5)`)
663
+ it('pyreon/no-mutate-store-state: clean for non-store .set()', () => {
664
+ const result = lintWith('pyreon/no-mutate-store-state', `count.set(5)`)
665
665
  expect(result.diagnostics.length).toBe(0)
666
666
  })
667
667
 
668
- it("pyreon/no-store-outside-provider: flags store hooks in server files without provider", () => {
668
+ it('pyreon/no-store-outside-provider: flags store hooks in server files without provider', () => {
669
669
  const result = lintWith(
670
- "pyreon/no-store-outside-provider",
670
+ 'pyreon/no-store-outside-provider',
671
671
  `useCounterStore()`,
672
- "app.server.ts",
672
+ 'app.server.ts',
673
673
  )
674
674
  expect(result.diagnostics.length).toBe(1)
675
675
  })
676
676
 
677
- it("pyreon/no-store-outside-provider: clean when provider is imported", () => {
677
+ it('pyreon/no-store-outside-provider: clean when provider is imported', () => {
678
678
  const result = lintWith(
679
- "pyreon/no-store-outside-provider",
679
+ 'pyreon/no-store-outside-provider',
680
680
  `import { runWithRequestContext } from "@pyreon/reactivity"\nuseCounterStore()`,
681
- "app.server.ts",
681
+ 'app.server.ts',
682
682
  )
683
683
  expect(result.diagnostics.length).toBe(0)
684
684
  })
685
685
 
686
- it("pyreon/no-store-outside-provider: clean in non-server files", () => {
687
- const result = lintWith("pyreon/no-store-outside-provider", `useCounterStore()`, "app.tsx")
686
+ it('pyreon/no-store-outside-provider: clean in non-server files', () => {
687
+ const result = lintWith('pyreon/no-store-outside-provider', `useCounterStore()`, 'app.tsx')
688
688
  expect(result.diagnostics.length).toBe(0)
689
689
  })
690
690
  })
691
691
 
692
692
  // ── Form Rules ──────────────────────────────────────────────────────────────
693
693
 
694
- describe("Form rules", () => {
695
- it("pyreon/no-submit-without-validation: flags useForm with onSubmit but no validators", () => {
694
+ describe('Form rules', () => {
695
+ it('pyreon/no-submit-without-validation: flags useForm with onSubmit but no validators', () => {
696
696
  const source = `const form = useForm({ initialValues: {}, onSubmit: () => {} })`
697
697
  const result = lintSource(source)
698
- const diags = findByRule(result, "pyreon/no-submit-without-validation")
698
+ const diags = findByRule(result, 'pyreon/no-submit-without-validation')
699
699
  expect(diags.length).toBe(1)
700
700
  })
701
701
 
702
- it("pyreon/no-submit-without-validation: clean with validators", () => {
702
+ it('pyreon/no-submit-without-validation: clean with validators', () => {
703
703
  const source = `const form = useForm({ initialValues: {}, onSubmit: () => {}, validators: {} })`
704
704
  const result = lintSource(source)
705
- const diags = findByRule(result, "pyreon/no-submit-without-validation")
705
+ const diags = findByRule(result, 'pyreon/no-submit-without-validation')
706
706
  expect(diags.length).toBe(0)
707
707
  })
708
708
 
709
- it("pyreon/no-unregistered-field: flags useField without register()", () => {
710
- const result = lintWith("pyreon/no-unregistered-field", `const name = useField(form, "name")`)
709
+ it('pyreon/no-unregistered-field: flags useField without register()', () => {
710
+ const result = lintWith('pyreon/no-unregistered-field', `const name = useField(form, "name")`)
711
711
  expect(result.diagnostics.length).toBe(1)
712
- expect(result.diagnostics[0]!.message).toContain("register")
712
+ expect(result.diagnostics[0]!.message).toContain('register')
713
713
  })
714
714
 
715
- it("pyreon/no-unregistered-field: clean when register is called", () => {
715
+ it('pyreon/no-unregistered-field: clean when register is called', () => {
716
716
  const result = lintWith(
717
- "pyreon/no-unregistered-field",
717
+ 'pyreon/no-unregistered-field',
718
718
  `const name = useField(form, "name")\nname.register()`,
719
719
  )
720
720
  expect(result.diagnostics.length).toBe(0)
721
721
  })
722
722
 
723
- it("pyreon/prefer-field-array: flags signal([]) in form files", () => {
723
+ it('pyreon/prefer-field-array: flags signal([]) in form files', () => {
724
724
  const result = lintWith(
725
- "pyreon/prefer-field-array",
725
+ 'pyreon/prefer-field-array',
726
726
  `import { useForm } from "@pyreon/form"\nconst items = signal([])`,
727
727
  )
728
728
  expect(result.diagnostics.length).toBe(1)
729
- expect(result.diagnostics[0]!.message).toContain("useFieldArray")
729
+ expect(result.diagnostics[0]!.message).toContain('useFieldArray')
730
730
  })
731
731
 
732
- it("pyreon/prefer-field-array: clean when not in form file", () => {
733
- const result = lintWith("pyreon/prefer-field-array", `const items = signal([])`)
732
+ it('pyreon/prefer-field-array: clean when not in form file', () => {
733
+ const result = lintWith('pyreon/prefer-field-array', `const items = signal([])`)
734
734
  expect(result.diagnostics.length).toBe(0)
735
735
  })
736
736
  })
737
737
 
738
738
  // ── Styling Rules ───────────────────────────────────────────────────────────
739
739
 
740
- describe("Styling rules", () => {
741
- it("pyreon/no-inline-style-object: flags style={{...}} in JSX", () => {
740
+ describe('Styling rules', () => {
741
+ it('pyreon/no-inline-style-object: flags style={{...}} in JSX', () => {
742
742
  const source = `const App = () => <div style={{ color: "red" }} />`
743
743
  const result = lintSource(source)
744
- const diags = findByRule(result, "pyreon/no-inline-style-object")
744
+ const diags = findByRule(result, 'pyreon/no-inline-style-object')
745
745
  expect(diags.length).toBe(1)
746
746
  })
747
747
 
748
- it("pyreon/no-dynamic-styled: flags styled() inside function", () => {
748
+ it('pyreon/no-dynamic-styled: flags styled() inside function', () => {
749
749
  const source = `function App() { const Box = styled("div"); return null }`
750
750
  const result = lintSource(source)
751
- const diags = findByRule(result, "pyreon/no-dynamic-styled")
751
+ const diags = findByRule(result, 'pyreon/no-dynamic-styled')
752
752
  expect(diags.length).toBe(1)
753
753
  })
754
754
 
755
- it("pyreon/no-dynamic-styled: clean at module level", () => {
755
+ it('pyreon/no-dynamic-styled: clean at module level', () => {
756
756
  const source = `const Box = styled("div")`
757
757
  const result = lintSource(source)
758
- const diags = findByRule(result, "pyreon/no-dynamic-styled")
758
+ const diags = findByRule(result, 'pyreon/no-dynamic-styled')
759
759
  expect(diags.length).toBe(0)
760
760
  })
761
761
 
762
- it("pyreon/prefer-cx: flags string concatenation in class attribute", () => {
763
- const result = lintWith("pyreon/prefer-cx", `const App = () => <div class={"foo " + bar} />`)
762
+ it('pyreon/prefer-cx: flags string concatenation in class attribute', () => {
763
+ const result = lintWith('pyreon/prefer-cx', `const App = () => <div class={"foo " + bar} />`)
764
764
  expect(result.diagnostics.length).toBe(1)
765
- expect(result.diagnostics[0]!.message).toContain("cx()")
765
+ expect(result.diagnostics[0]!.message).toContain('cx()')
766
766
  })
767
767
 
768
- it("pyreon/prefer-cx: flags template literal in class attribute", () => {
769
- const result = lintWith("pyreon/prefer-cx", "const App = () => <div class={`foo ${bar}`} />")
768
+ it('pyreon/prefer-cx: flags template literal in class attribute', () => {
769
+ const result = lintWith('pyreon/prefer-cx', 'const App = () => <div class={`foo ${bar}`} />')
770
770
  expect(result.diagnostics.length).toBe(1)
771
771
  })
772
772
 
773
- it("pyreon/prefer-cx: clean with plain string class", () => {
774
- const result = lintWith("pyreon/prefer-cx", `const App = () => <div class="foo bar" />`)
773
+ it('pyreon/prefer-cx: clean with plain string class', () => {
774
+ const result = lintWith('pyreon/prefer-cx', `const App = () => <div class="foo bar" />`)
775
775
  expect(result.diagnostics.length).toBe(0)
776
776
  })
777
777
 
778
- it("pyreon/no-theme-outside-provider: flags useTheme() without provider import", () => {
779
- const result = lintWith("pyreon/no-theme-outside-provider", `const theme = useTheme()`)
778
+ it('pyreon/no-theme-outside-provider: flags useTheme() without provider import', () => {
779
+ const result = lintWith('pyreon/no-theme-outside-provider', `const theme = useTheme()`)
780
780
  expect(result.diagnostics.length).toBe(1)
781
781
  })
782
782
 
783
- it("pyreon/no-theme-outside-provider: clean when PyreonUI is imported", () => {
783
+ it('pyreon/no-theme-outside-provider: clean when PyreonUI is imported', () => {
784
784
  const result = lintWith(
785
- "pyreon/no-theme-outside-provider",
785
+ 'pyreon/no-theme-outside-provider',
786
786
  `import { PyreonUI } from "@pyreon/ui-core"\nconst theme = useTheme()`,
787
787
  )
788
788
  expect(result.diagnostics.length).toBe(0)
@@ -791,253 +791,253 @@ describe("Styling rules", () => {
791
791
 
792
792
  // ── Hooks Rules ─────────────────────────────────────────────────────────────
793
793
 
794
- describe("Hooks rules", () => {
795
- it("pyreon/no-raw-addeventlistener: flags .addEventListener()", () => {
794
+ describe('Hooks rules', () => {
795
+ it('pyreon/no-raw-addeventlistener: flags .addEventListener()', () => {
796
796
  const source = `el.addEventListener("click", handler)`
797
797
  const result = lintSource(source)
798
- const diags = findByRule(result, "pyreon/no-raw-addeventlistener")
798
+ const diags = findByRule(result, 'pyreon/no-raw-addeventlistener')
799
799
  expect(diags.length).toBe(1)
800
800
  })
801
801
 
802
- it("pyreon/no-raw-setinterval: flags setInterval outside onMount", () => {
802
+ it('pyreon/no-raw-setinterval: flags setInterval outside onMount', () => {
803
803
  const source = `setInterval(() => {}, 1000)`
804
804
  const result = lintSource(source)
805
- const diags = findByRule(result, "pyreon/no-raw-setinterval")
805
+ const diags = findByRule(result, 'pyreon/no-raw-setinterval')
806
806
  expect(diags.length).toBe(1)
807
807
  })
808
808
 
809
- it("pyreon/no-raw-localstorage: flags localStorage.getItem()", () => {
809
+ it('pyreon/no-raw-localstorage: flags localStorage.getItem()', () => {
810
810
  const source = `const v = localStorage.getItem("key")`
811
811
  const result = lintSource(source)
812
- const diags = findByRule(result, "pyreon/no-raw-localstorage")
812
+ const diags = findByRule(result, 'pyreon/no-raw-localstorage')
813
813
  expect(diags.length).toBe(1)
814
814
  })
815
815
  })
816
816
 
817
817
  // ── Accessibility Rules ─────────────────────────────────────────────────────
818
818
 
819
- describe("Accessibility rules", () => {
820
- it("pyreon/dialog-a11y: flags <dialog> without aria-label", () => {
819
+ describe('Accessibility rules', () => {
820
+ it('pyreon/dialog-a11y: flags <dialog> without aria-label', () => {
821
821
  const source = `const App = () => <dialog><p>Hello</p></dialog>`
822
822
  const result = lintSource(source)
823
- const diags = findByRule(result, "pyreon/dialog-a11y")
823
+ const diags = findByRule(result, 'pyreon/dialog-a11y')
824
824
  expect(diags.length).toBe(1)
825
825
  })
826
826
 
827
- it("pyreon/dialog-a11y: clean with aria-label", () => {
827
+ it('pyreon/dialog-a11y: clean with aria-label', () => {
828
828
  const source = `const App = () => <dialog aria-label="My dialog"><p>Hello</p></dialog>`
829
829
  const result = lintSource(source)
830
- const diags = findByRule(result, "pyreon/dialog-a11y")
830
+ const diags = findByRule(result, 'pyreon/dialog-a11y')
831
831
  expect(diags.length).toBe(0)
832
832
  })
833
833
 
834
- it("pyreon/overlay-a11y: flags <Overlay> without role", () => {
834
+ it('pyreon/overlay-a11y: flags <Overlay> without role', () => {
835
835
  const source = `const App = () => <Overlay><p>Hello</p></Overlay>`
836
836
  const result = lintSource(source)
837
- const diags = findByRule(result, "pyreon/overlay-a11y")
837
+ const diags = findByRule(result, 'pyreon/overlay-a11y')
838
838
  expect(diags.length).toBe(1)
839
839
  })
840
840
 
841
- it("pyreon/overlay-a11y: clean with role", () => {
841
+ it('pyreon/overlay-a11y: clean with role', () => {
842
842
  const source = `const App = () => <Overlay role="dialog"><p>Hello</p></Overlay>`
843
843
  const result = lintSource(source)
844
- const diags = findByRule(result, "pyreon/overlay-a11y")
844
+ const diags = findByRule(result, 'pyreon/overlay-a11y')
845
845
  expect(diags.length).toBe(0)
846
846
  })
847
847
 
848
- it("pyreon/toast-a11y: flags Toast component without role or aria-live", () => {
849
- const result = lintWith("pyreon/toast-a11y", `const App = () => <ToastItem message="hello" />`)
848
+ it('pyreon/toast-a11y: flags Toast component without role or aria-live', () => {
849
+ const result = lintWith('pyreon/toast-a11y', `const App = () => <ToastItem message="hello" />`)
850
850
  expect(result.diagnostics.length).toBe(1)
851
- expect(result.diagnostics[0]!.message).toContain("role")
851
+ expect(result.diagnostics[0]!.message).toContain('role')
852
852
  })
853
853
 
854
- it("pyreon/toast-a11y: clean with role attribute", () => {
854
+ it('pyreon/toast-a11y: clean with role attribute', () => {
855
855
  const result = lintWith(
856
- "pyreon/toast-a11y",
856
+ 'pyreon/toast-a11y',
857
857
  `const App = () => <ToastItem role="alert" message="hello" />`,
858
858
  )
859
859
  expect(result.diagnostics.length).toBe(0)
860
860
  })
861
861
 
862
- it("pyreon/toast-a11y: clean with aria-live attribute", () => {
862
+ it('pyreon/toast-a11y: clean with aria-live attribute', () => {
863
863
  const result = lintWith(
864
- "pyreon/toast-a11y",
864
+ 'pyreon/toast-a11y',
865
865
  `const App = () => <ToastItem aria-live="polite" message="hello" />`,
866
866
  )
867
867
  expect(result.diagnostics.length).toBe(0)
868
868
  })
869
869
 
870
- it("pyreon/toast-a11y: skips Toaster container", () => {
871
- const result = lintWith("pyreon/toast-a11y", `const App = () => <Toaster />`)
870
+ it('pyreon/toast-a11y: skips Toaster container', () => {
871
+ const result = lintWith('pyreon/toast-a11y', `const App = () => <Toaster />`)
872
872
  expect(result.diagnostics.length).toBe(0)
873
873
  })
874
874
 
875
- it("pyreon/toast-a11y: skips non-toast PascalCase components", () => {
876
- const result = lintWith("pyreon/toast-a11y", `const App = () => <Button />`)
875
+ it('pyreon/toast-a11y: skips non-toast PascalCase components', () => {
876
+ const result = lintWith('pyreon/toast-a11y', `const App = () => <Button />`)
877
877
  expect(result.diagnostics.length).toBe(0)
878
878
  })
879
879
  })
880
880
 
881
881
  // ── Router Rules ────────────────────────────────────────────────────────────
882
882
 
883
- describe("Router rules", () => {
884
- it("pyreon/no-href-navigation: flags <a href> in router file", () => {
883
+ describe('Router rules', () => {
884
+ it('pyreon/no-href-navigation: flags <a href> in router file', () => {
885
885
  const result = lintWith(
886
- "pyreon/no-href-navigation",
886
+ 'pyreon/no-href-navigation',
887
887
  `import { Link } from "@pyreon/router"\nconst App = () => <a href="/about">About</a>`,
888
888
  )
889
889
  expect(result.diagnostics.length).toBe(1)
890
- expect(result.diagnostics[0]!.message).toContain("<Link>")
890
+ expect(result.diagnostics[0]!.message).toContain('<Link>')
891
891
  })
892
892
 
893
- it("pyreon/no-href-navigation: clean for external URLs", () => {
893
+ it('pyreon/no-href-navigation: clean for external URLs', () => {
894
894
  const result = lintWith(
895
- "pyreon/no-href-navigation",
895
+ 'pyreon/no-href-navigation',
896
896
  `import { Link } from "@pyreon/router"\nconst App = () => <a href="https://example.com">External</a>`,
897
897
  )
898
898
  expect(result.diagnostics.length).toBe(0)
899
899
  })
900
900
 
901
- it("pyreon/no-href-navigation: clean for anchor links", () => {
901
+ it('pyreon/no-href-navigation: clean for anchor links', () => {
902
902
  const result = lintWith(
903
- "pyreon/no-href-navigation",
903
+ 'pyreon/no-href-navigation',
904
904
  `import { Link } from "@pyreon/router"\nconst App = () => <a href="#section">Jump</a>`,
905
905
  )
906
906
  expect(result.diagnostics.length).toBe(0)
907
907
  })
908
908
 
909
- it("pyreon/no-href-navigation: clean without router import", () => {
909
+ it('pyreon/no-href-navigation: clean without router import', () => {
910
910
  const result = lintWith(
911
- "pyreon/no-href-navigation",
911
+ 'pyreon/no-href-navigation',
912
912
  `const App = () => <a href="/about">About</a>`,
913
913
  )
914
914
  expect(result.diagnostics.length).toBe(0)
915
915
  })
916
916
 
917
- it("pyreon/no-imperative-navigate-in-render: flags navigate() in component body", () => {
917
+ it('pyreon/no-imperative-navigate-in-render: flags navigate() in component body', () => {
918
918
  const result = lintWith(
919
- "pyreon/no-imperative-navigate-in-render",
919
+ 'pyreon/no-imperative-navigate-in-render',
920
920
  `const App = () => { navigate("/home"); return <div /> }`,
921
921
  )
922
922
  expect(result.diagnostics.length).toBe(1)
923
- expect(result.diagnostics[0]!.message).toContain("infinite")
923
+ expect(result.diagnostics[0]!.message).toContain('infinite')
924
924
  })
925
925
 
926
- it("pyreon/no-imperative-navigate-in-render: clean inside onMount", () => {
926
+ it('pyreon/no-imperative-navigate-in-render: clean inside onMount', () => {
927
927
  const result = lintWith(
928
- "pyreon/no-imperative-navigate-in-render",
928
+ 'pyreon/no-imperative-navigate-in-render',
929
929
  `const App = () => { onMount(() => { navigate("/home") }); return <div /> }`,
930
930
  )
931
931
  expect(result.diagnostics.length).toBe(0)
932
932
  })
933
933
 
934
- it("pyreon/no-imperative-navigate-in-render: clean in non-component", () => {
934
+ it('pyreon/no-imperative-navigate-in-render: clean in non-component', () => {
935
935
  const result = lintWith(
936
- "pyreon/no-imperative-navigate-in-render",
936
+ 'pyreon/no-imperative-navigate-in-render',
937
937
  `const handle = () => { navigate("/home") }`,
938
938
  )
939
939
  expect(result.diagnostics.length).toBe(0)
940
940
  })
941
941
 
942
- it("pyreon/no-missing-fallback: flags route config without catch-all", () => {
942
+ it('pyreon/no-missing-fallback: flags route config without catch-all', () => {
943
943
  const result = lintWith(
944
- "pyreon/no-missing-fallback",
944
+ 'pyreon/no-missing-fallback',
945
945
  `import { Router } from "@pyreon/router"\nconst routes = [{ path: "/", component: Home }, { path: "/about", component: About }]`,
946
946
  )
947
947
  expect(result.diagnostics.length).toBe(1)
948
- expect(result.diagnostics[0]!.message).toContain("catch-all")
948
+ expect(result.diagnostics[0]!.message).toContain('catch-all')
949
949
  })
950
950
 
951
- it("pyreon/no-missing-fallback: clean with catch-all route", () => {
951
+ it('pyreon/no-missing-fallback: clean with catch-all route', () => {
952
952
  const result = lintWith(
953
- "pyreon/no-missing-fallback",
953
+ 'pyreon/no-missing-fallback',
954
954
  `import { Router } from "@pyreon/router"\nconst routes = [{ path: "/", component: Home }, { path: "*", component: NotFound }]`,
955
955
  )
956
956
  expect(result.diagnostics.length).toBe(0)
957
957
  })
958
958
 
959
- it("pyreon/no-missing-fallback: clean without router import", () => {
959
+ it('pyreon/no-missing-fallback: clean without router import', () => {
960
960
  const result = lintWith(
961
- "pyreon/no-missing-fallback",
961
+ 'pyreon/no-missing-fallback',
962
962
  `const routes = [{ path: "/", component: Home }]`,
963
963
  )
964
964
  expect(result.diagnostics.length).toBe(0)
965
965
  })
966
966
 
967
- it("pyreon/prefer-use-is-active: flags location.pathname === comparison", () => {
967
+ it('pyreon/prefer-use-is-active: flags location.pathname === comparison', () => {
968
968
  const result = lintWith(
969
- "pyreon/prefer-use-is-active",
969
+ 'pyreon/prefer-use-is-active',
970
970
  `const active = location.pathname === "/admin"`,
971
971
  )
972
972
  expect(result.diagnostics.length).toBe(1)
973
- expect(result.diagnostics[0]!.message).toContain("useIsActive")
973
+ expect(result.diagnostics[0]!.message).toContain('useIsActive')
974
974
  })
975
975
 
976
- it("pyreon/prefer-use-is-active: flags route.path === comparison", () => {
977
- const result = lintWith("pyreon/prefer-use-is-active", `const active = route.path === "/admin"`)
976
+ it('pyreon/prefer-use-is-active: flags route.path === comparison', () => {
977
+ const result = lintWith('pyreon/prefer-use-is-active', `const active = route.path === "/admin"`)
978
978
  expect(result.diagnostics.length).toBe(1)
979
979
  })
980
980
 
981
- it("pyreon/prefer-use-is-active: clean for unrelated comparisons", () => {
982
- const result = lintWith("pyreon/prefer-use-is-active", `const active = name === "admin"`)
981
+ it('pyreon/prefer-use-is-active: clean for unrelated comparisons', () => {
982
+ const result = lintWith('pyreon/prefer-use-is-active', `const active = name === "admin"`)
983
983
  expect(result.diagnostics.length).toBe(0)
984
984
  })
985
985
  })
986
986
 
987
987
  // ── Config Loading ──────────────────────────────────────────────────────────
988
988
 
989
- describe("Config loading", () => {
990
- it("loadConfig returns null when no config file exists", () => {
989
+ describe('Config loading', () => {
990
+ it('loadConfig returns null when no config file exists', () => {
991
991
  // Use a path where there's definitely no config
992
- const result = loadConfig("/tmp/nonexistent-pyreon-dir-12345")
992
+ const result = loadConfig('/tmp/nonexistent-pyreon-dir-12345')
993
993
  expect(result).toBeNull()
994
994
  })
995
995
  })
996
996
 
997
997
  // ── Ignore Filter ───────────────────────────────────────────────────────────
998
998
 
999
- describe("Ignore filter", () => {
1000
- it("createIgnoreFilter returns a function", () => {
1001
- const filter = createIgnoreFilter("/tmp/nonexistent-pyreon-dir-12345")
1002
- expect(typeof filter).toBe("function")
999
+ describe('Ignore filter', () => {
1000
+ it('createIgnoreFilter returns a function', () => {
1001
+ const filter = createIgnoreFilter('/tmp/nonexistent-pyreon-dir-12345')
1002
+ expect(typeof filter).toBe('function')
1003
1003
  })
1004
1004
 
1005
- it("filter returns false for paths when no ignore files exist", () => {
1006
- const filter = createIgnoreFilter("/tmp/nonexistent-pyreon-dir-12345")
1007
- expect(filter("/tmp/nonexistent-pyreon-dir-12345/src/app.ts")).toBe(false)
1005
+ it('filter returns false for paths when no ignore files exist', () => {
1006
+ const filter = createIgnoreFilter('/tmp/nonexistent-pyreon-dir-12345')
1007
+ expect(filter('/tmp/nonexistent-pyreon-dir-12345/src/app.ts')).toBe(false)
1008
1008
  })
1009
1009
  })
1010
1010
 
1011
1011
  // ── Presets ─────────────────────────────────────────────────────────────────
1012
1012
 
1013
- describe("Presets", () => {
1014
- it("recommended should include all rules", () => {
1015
- const config = getPreset("recommended")
1016
- expect(Object.keys(config.rules).length).toBe(55)
1013
+ describe('Presets', () => {
1014
+ it('recommended should include all rules', () => {
1015
+ const config = getPreset('recommended')
1016
+ expect(Object.keys(config.rules).length).toBe(56)
1017
1017
  })
1018
1018
 
1019
- it("strict should promote all warns to errors", () => {
1020
- const recommended = getPreset("recommended")
1021
- const strict = getPreset("strict")
1019
+ it('strict should promote all warns to errors', () => {
1020
+ const recommended = getPreset('recommended')
1021
+ const strict = getPreset('strict')
1022
1022
  for (const [id, sev] of Object.entries(recommended.rules)) {
1023
- if (sev === "warn") {
1024
- expect(strict.rules[id]).toBe("error")
1023
+ if (sev === 'warn') {
1024
+ expect(strict.rules[id]).toBe('error')
1025
1025
  }
1026
1026
  }
1027
1027
  })
1028
1028
 
1029
- it("app should disable library-specific rules", () => {
1030
- const app = getPreset("app")
1031
- expect(app.rules["pyreon/dev-guard-warnings"]).toBe("off")
1032
- expect(app.rules["pyreon/no-error-without-prefix"]).toBe("off")
1033
- expect(app.rules["pyreon/no-circular-import"]).toBe("off")
1034
- expect(app.rules["pyreon/no-cross-layer-import"]).toBe("off")
1029
+ it('app should disable library-specific rules', () => {
1030
+ const app = getPreset('app')
1031
+ expect(app.rules['pyreon/dev-guard-warnings']).toBe('off')
1032
+ expect(app.rules['pyreon/no-error-without-prefix']).toBe('off')
1033
+ expect(app.rules['pyreon/no-circular-import']).toBe('off')
1034
+ expect(app.rules['pyreon/no-cross-layer-import']).toBe('off')
1035
1035
  })
1036
1036
 
1037
- it("lib should have architecture rules as error", () => {
1038
- const lib = getPreset("lib")
1039
- expect(lib.rules["pyreon/no-circular-import"]).toBe("error")
1040
- expect(lib.rules["pyreon/no-cross-layer-import"]).toBe("error")
1041
- expect(lib.rules["pyreon/dev-guard-warnings"]).toBe("error")
1037
+ it('lib should have architecture rules as error', () => {
1038
+ const lib = getPreset('lib')
1039
+ expect(lib.rules['pyreon/no-circular-import']).toBe('error')
1040
+ expect(lib.rules['pyreon/no-cross-layer-import']).toBe('error')
1041
+ expect(lib.rules['pyreon/dev-guard-warnings']).toBe('error')
1042
1042
  })
1043
1043
  })