@codyswann/lisa 1.55.2 → 1.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/deletions.json +0 -1
- package/cdk/copy-overwrite/tsconfig.json +3 -0
- package/expo/copy-overwrite/tsconfig.json +7 -0
- package/expo/deletions.json +1 -0
- package/nestjs/copy-overwrite/tsconfig.json +6 -0
- package/package.json +6 -3
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/skills/reduce-complexity/SKILL.md +251 -0
- package/plugins/lisa-expo/skills/reduce-complexity/references/extraction-strategies.md +456 -0
- package/plugins/lisa-expo/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/src/expo/skills/reduce-complexity/SKILL.md +251 -0
- package/plugins/src/expo/skills/reduce-complexity/references/extraction-strategies.md +456 -0
- package/plugins/src/expo/skills/reduce-complexity/references/refactoring-patterns.md +557 -0
- package/tsconfig/base.json +1 -2
- package/tsconfig/expo.json +0 -5
- package/tsconfig/nestjs.json +1 -5
- package/typescript/copy-overwrite/tsconfig.json +3 -0
- package/scripts/strip-workspaces-for-pack.sh +0 -20
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
# Refactoring Patterns
|
|
2
|
+
|
|
3
|
+
This reference provides complete before/after examples for common complexity refactoring scenarios.
|
|
4
|
+
|
|
5
|
+
## Pattern 1: Flatten Nested Conditionals
|
|
6
|
+
|
|
7
|
+
### Problem
|
|
8
|
+
|
|
9
|
+
Nested conditional rendering creates exponential complexity:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// Complexity: 6+ (each && and ternary adds complexity, nesting multiplies)
|
|
13
|
+
const BadView = ({ isLoading, hasError, data, isEmpty }: Props) => (
|
|
14
|
+
<Box>
|
|
15
|
+
{isLoading ? (
|
|
16
|
+
<Spinner />
|
|
17
|
+
) : hasError ? (
|
|
18
|
+
<ErrorState />
|
|
19
|
+
) : isEmpty ? (
|
|
20
|
+
<EmptyState />
|
|
21
|
+
) : (
|
|
22
|
+
<Box>
|
|
23
|
+
{data.sections.map(section => (
|
|
24
|
+
<Box key={section.id}>
|
|
25
|
+
{section.isExpanded ? (
|
|
26
|
+
<ExpandedContent items={section.items} />
|
|
27
|
+
) : (
|
|
28
|
+
<CollapsedHeader title={section.title} />
|
|
29
|
+
)}
|
|
30
|
+
</Box>
|
|
31
|
+
))}
|
|
32
|
+
</Box>
|
|
33
|
+
)}
|
|
34
|
+
</Box>
|
|
35
|
+
);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Solution: Pre-compute State + Flatten
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Container
|
|
42
|
+
type ViewState = "loading" | "error" | "empty" | "ready";
|
|
43
|
+
|
|
44
|
+
const ContainerComponent = () => {
|
|
45
|
+
const { data, loading, error } = useQuery();
|
|
46
|
+
|
|
47
|
+
const viewState = useMemo((): ViewState => {
|
|
48
|
+
if (loading) return "loading";
|
|
49
|
+
if (error) return "error";
|
|
50
|
+
if (!data?.sections?.length) return "empty";
|
|
51
|
+
return "ready";
|
|
52
|
+
}, [loading, error, data?.sections?.length]);
|
|
53
|
+
|
|
54
|
+
const sections = useMemo(() => data?.sections ?? [], [data?.sections]);
|
|
55
|
+
|
|
56
|
+
return <ViewComponent viewState={viewState} sections={sections} />;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// View - Complexity reduced to ~3
|
|
60
|
+
const ViewComponent = ({ viewState, sections }: Props) => (
|
|
61
|
+
<Box>
|
|
62
|
+
{viewState === "loading" && <Spinner />}
|
|
63
|
+
{viewState === "error" && <ErrorState />}
|
|
64
|
+
{viewState === "empty" && <EmptyState />}
|
|
65
|
+
{viewState === "ready" && <SectionList sections={sections} />}
|
|
66
|
+
</Box>
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Pattern 2: Extract Repeated Map Patterns
|
|
71
|
+
|
|
72
|
+
### Problem
|
|
73
|
+
|
|
74
|
+
Same rendering pattern repeated with different data:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// Complexity: 20+ (each map, conditional, and ternary adds up)
|
|
78
|
+
const FilterModalView = ({ filters, positions, tags, statuses }: Props) => (
|
|
79
|
+
<Box>
|
|
80
|
+
{/* Positions - Pattern A */}
|
|
81
|
+
{positions.length > 0 && (
|
|
82
|
+
<VStack>
|
|
83
|
+
<Text>Positions</Text>
|
|
84
|
+
<HStack>
|
|
85
|
+
{positions.map(p => (
|
|
86
|
+
<Chip
|
|
87
|
+
key={p}
|
|
88
|
+
label={p}
|
|
89
|
+
selected={filters.positions.includes(p)}
|
|
90
|
+
onPress={() => onToggle("position", p)}
|
|
91
|
+
/>
|
|
92
|
+
))}
|
|
93
|
+
</HStack>
|
|
94
|
+
</VStack>
|
|
95
|
+
)}
|
|
96
|
+
|
|
97
|
+
{/* Tags - Pattern A (repeated) */}
|
|
98
|
+
{tags.length > 0 && (
|
|
99
|
+
<VStack>
|
|
100
|
+
<Text>Tags</Text>
|
|
101
|
+
<HStack>
|
|
102
|
+
{tags.map(t => (
|
|
103
|
+
<Chip
|
|
104
|
+
key={t.id}
|
|
105
|
+
label={t.name}
|
|
106
|
+
selected={filters.tags.includes(t.id)}
|
|
107
|
+
onPress={() => onToggle("tag", t.id)}
|
|
108
|
+
/>
|
|
109
|
+
))}
|
|
110
|
+
</HStack>
|
|
111
|
+
</VStack>
|
|
112
|
+
)}
|
|
113
|
+
|
|
114
|
+
{/* Statuses - Pattern A (repeated again) */}
|
|
115
|
+
{statuses.length > 0 && (
|
|
116
|
+
<VStack>
|
|
117
|
+
<Text>Status</Text>
|
|
118
|
+
<HStack>
|
|
119
|
+
{statuses.map(s => (
|
|
120
|
+
<Chip
|
|
121
|
+
key={s}
|
|
122
|
+
label={s}
|
|
123
|
+
selected={filters.statuses.includes(s)}
|
|
124
|
+
onPress={() => onToggle("status", s)}
|
|
125
|
+
/>
|
|
126
|
+
))}
|
|
127
|
+
</HStack>
|
|
128
|
+
</VStack>
|
|
129
|
+
)}
|
|
130
|
+
</Box>
|
|
131
|
+
);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Solution: Extract Reusable Component
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// New component: FilterSection/FilterSectionContainer.tsx
|
|
138
|
+
interface FilterSectionProps {
|
|
139
|
+
readonly title: string;
|
|
140
|
+
readonly items: readonly { id: string; label: string }[];
|
|
141
|
+
readonly selectedIds: readonly string[];
|
|
142
|
+
readonly onToggle: (id: string) => void;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const FilterSectionContainer = ({
|
|
146
|
+
title,
|
|
147
|
+
items,
|
|
148
|
+
selectedIds,
|
|
149
|
+
onToggle,
|
|
150
|
+
}: FilterSectionProps) => {
|
|
151
|
+
const itemsWithSelection = useMemo(
|
|
152
|
+
() =>
|
|
153
|
+
items.map(item => ({
|
|
154
|
+
...item,
|
|
155
|
+
isSelected: selectedIds.includes(item.id),
|
|
156
|
+
})),
|
|
157
|
+
[items, selectedIds]
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const handleToggle = useCallback(
|
|
161
|
+
(id: string) => () => onToggle(id),
|
|
162
|
+
[onToggle]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (items.length === 0) return null;
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<FilterSectionView
|
|
169
|
+
title={title}
|
|
170
|
+
items={itemsWithSelection}
|
|
171
|
+
onToggle={handleToggle}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// FilterSection/FilterSectionView.tsx
|
|
177
|
+
const FilterSectionView = ({ title, items, onToggle }: ViewProps) => (
|
|
178
|
+
<VStack>
|
|
179
|
+
<Text>{title}</Text>
|
|
180
|
+
<HStack>
|
|
181
|
+
{items.map(({ id, label, isSelected }) => (
|
|
182
|
+
<Chip
|
|
183
|
+
key={id}
|
|
184
|
+
label={label}
|
|
185
|
+
selected={isSelected}
|
|
186
|
+
onPress={onToggle(id)}
|
|
187
|
+
/>
|
|
188
|
+
))}
|
|
189
|
+
</HStack>
|
|
190
|
+
</VStack>
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
FilterSectionView.displayName = "FilterSectionView";
|
|
194
|
+
export default memo(FilterSectionView);
|
|
195
|
+
|
|
196
|
+
// Updated parent - Complexity: ~3
|
|
197
|
+
const FilterModalView = ({ positionItems, tagItems, statusItems }: Props) => (
|
|
198
|
+
<Box>
|
|
199
|
+
<FilterSection
|
|
200
|
+
title="Positions"
|
|
201
|
+
items={positionItems}
|
|
202
|
+
selectedIds={filters.positions}
|
|
203
|
+
onToggle={onPositionToggle}
|
|
204
|
+
/>
|
|
205
|
+
<FilterSection
|
|
206
|
+
title="Tags"
|
|
207
|
+
items={tagItems}
|
|
208
|
+
selectedIds={filters.tags}
|
|
209
|
+
onToggle={onTagToggle}
|
|
210
|
+
/>
|
|
211
|
+
<FilterSection
|
|
212
|
+
title="Status"
|
|
213
|
+
items={statusItems}
|
|
214
|
+
selectedIds={filters.statuses}
|
|
215
|
+
onToggle={onStatusToggle}
|
|
216
|
+
/>
|
|
217
|
+
</Box>
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Pattern 3: Simplify Conditional Styles
|
|
222
|
+
|
|
223
|
+
### Problem
|
|
224
|
+
|
|
225
|
+
Repeated conditional checks for styling:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// Complexity: 12+ (each includes() and ternary)
|
|
229
|
+
const ChipView = ({ item, selectedItems, colors }: Props) => (
|
|
230
|
+
<Pressable
|
|
231
|
+
style={{
|
|
232
|
+
backgroundColor: selectedItems.includes(item.id)
|
|
233
|
+
? colors.primary
|
|
234
|
+
: colors.cardBackground,
|
|
235
|
+
borderColor: selectedItems.includes(item.id)
|
|
236
|
+
? colors.primary
|
|
237
|
+
: colors.border,
|
|
238
|
+
borderWidth: 1,
|
|
239
|
+
paddingHorizontal: 10,
|
|
240
|
+
paddingVertical: 5,
|
|
241
|
+
borderRadius: 6,
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<Text
|
|
245
|
+
style={{
|
|
246
|
+
color: selectedItems.includes(item.id)
|
|
247
|
+
? "#FFFFFF"
|
|
248
|
+
: colors.textSecondary,
|
|
249
|
+
fontWeight: selectedItems.includes(item.id) ? "600" : "400",
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
{item.label}
|
|
253
|
+
</Text>
|
|
254
|
+
</Pressable>
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Solution: Pre-compute and Use Style Objects
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
// Container - compute selection state once
|
|
262
|
+
const ChipListContainer = ({ items, selectedIds }: Props) => {
|
|
263
|
+
const itemsWithState = useMemo(
|
|
264
|
+
() =>
|
|
265
|
+
items.map(item => ({
|
|
266
|
+
...item,
|
|
267
|
+
isSelected: selectedIds.includes(item.id),
|
|
268
|
+
})),
|
|
269
|
+
[items, selectedIds]
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return <ChipListView items={itemsWithState} />;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// View - use pre-computed isSelected
|
|
276
|
+
const ChipView = ({ item, isSelected, colors }: Props) => (
|
|
277
|
+
<Pressable
|
|
278
|
+
style={isSelected ? styles.selected(colors) : styles.default(colors)}
|
|
279
|
+
>
|
|
280
|
+
<Text style={isSelected ? styles.selectedText : styles.defaultText(colors)}>
|
|
281
|
+
{item.label}
|
|
282
|
+
</Text>
|
|
283
|
+
</Pressable>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
// Style helpers (defined outside component)
|
|
287
|
+
const styles = {
|
|
288
|
+
selected: (colors: Colors) => ({
|
|
289
|
+
backgroundColor: colors.primary,
|
|
290
|
+
borderColor: colors.primary,
|
|
291
|
+
borderWidth: 1,
|
|
292
|
+
paddingHorizontal: 10,
|
|
293
|
+
paddingVertical: 5,
|
|
294
|
+
borderRadius: 6,
|
|
295
|
+
}),
|
|
296
|
+
default: (colors: Colors) => ({
|
|
297
|
+
backgroundColor: colors.cardBackground,
|
|
298
|
+
borderColor: colors.border,
|
|
299
|
+
borderWidth: 1,
|
|
300
|
+
paddingHorizontal: 10,
|
|
301
|
+
paddingVertical: 5,
|
|
302
|
+
borderRadius: 6,
|
|
303
|
+
}),
|
|
304
|
+
selectedText: {
|
|
305
|
+
color: "#FFFFFF",
|
|
306
|
+
fontWeight: "600" as const,
|
|
307
|
+
},
|
|
308
|
+
defaultText: (colors: Colors) => ({
|
|
309
|
+
color: colors.textSecondary,
|
|
310
|
+
fontWeight: "400" as const,
|
|
311
|
+
}),
|
|
312
|
+
};
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Pattern 4: Replace Switch with Object Mapping
|
|
316
|
+
|
|
317
|
+
### Problem
|
|
318
|
+
|
|
319
|
+
Large switch statements or if-else chains:
|
|
320
|
+
|
|
321
|
+
```tsx
|
|
322
|
+
// Complexity: 8+ (each case adds complexity)
|
|
323
|
+
const getStatusIcon = (status: string) => {
|
|
324
|
+
switch (status) {
|
|
325
|
+
case "active":
|
|
326
|
+
return <CheckCircle color="green" />;
|
|
327
|
+
case "pending":
|
|
328
|
+
return <Clock color="yellow" />;
|
|
329
|
+
case "inactive":
|
|
330
|
+
return <XCircle color="gray" />;
|
|
331
|
+
case "error":
|
|
332
|
+
return <AlertCircle color="red" />;
|
|
333
|
+
default:
|
|
334
|
+
return <HelpCircle color="gray" />;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const StatusView = ({ status }: Props) => <Box>{getStatusIcon(status)}</Box>;
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Solution: Object Mapping
|
|
342
|
+
|
|
343
|
+
```tsx
|
|
344
|
+
// Define mapping outside component (no complexity cost)
|
|
345
|
+
const STATUS_ICONS: Record<string, ReactNode> = {
|
|
346
|
+
active: <CheckCircle color="green" />,
|
|
347
|
+
pending: <Clock color="yellow" />,
|
|
348
|
+
inactive: <XCircle color="gray" />,
|
|
349
|
+
error: <AlertCircle color="red" />,
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const DEFAULT_ICON = <HelpCircle color="gray" />;
|
|
353
|
+
|
|
354
|
+
// View - Complexity: 1
|
|
355
|
+
const StatusView = ({ status }: Props) => (
|
|
356
|
+
<Box>{STATUS_ICONS[status] ?? DEFAULT_ICON}</Box>
|
|
357
|
+
);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Pattern 5: Extract Complex Render Sections
|
|
361
|
+
|
|
362
|
+
### Problem
|
|
363
|
+
|
|
364
|
+
Long View with multiple distinct sections:
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
// Complexity: 25+ (multiple conditionals, maps, nested structures)
|
|
368
|
+
const DashboardView = ({ user, stats, notifications, activities }: Props) => (
|
|
369
|
+
<Box>
|
|
370
|
+
{/* Header section - 5 lines */}
|
|
371
|
+
<HStack>
|
|
372
|
+
<Avatar source={user.avatar} />
|
|
373
|
+
<VStack>
|
|
374
|
+
<Text>{user.name}</Text>
|
|
375
|
+
<Text>{user.role}</Text>
|
|
376
|
+
</VStack>
|
|
377
|
+
{user.isAdmin && <AdminBadge />}
|
|
378
|
+
</HStack>
|
|
379
|
+
|
|
380
|
+
{/* Stats section - 15 lines */}
|
|
381
|
+
<HStack>
|
|
382
|
+
{stats.map(stat => (
|
|
383
|
+
<Box key={stat.id}>
|
|
384
|
+
<Text>{stat.value}</Text>
|
|
385
|
+
<Text>{stat.label}</Text>
|
|
386
|
+
{stat.trend > 0 ? <TrendUp /> : <TrendDown />}
|
|
387
|
+
</Box>
|
|
388
|
+
))}
|
|
389
|
+
</HStack>
|
|
390
|
+
|
|
391
|
+
{/* Notifications section - 20 lines */}
|
|
392
|
+
{notifications.length > 0 && (
|
|
393
|
+
<VStack>
|
|
394
|
+
<Text>Notifications ({notifications.length})</Text>
|
|
395
|
+
{notifications.slice(0, 5).map(n => (
|
|
396
|
+
<NotificationItem key={n.id} notification={n} />
|
|
397
|
+
))}
|
|
398
|
+
{notifications.length > 5 && (
|
|
399
|
+
<Text>+{notifications.length - 5} more</Text>
|
|
400
|
+
)}
|
|
401
|
+
</VStack>
|
|
402
|
+
)}
|
|
403
|
+
|
|
404
|
+
{/* Activities section - 15 lines */}
|
|
405
|
+
<VStack>
|
|
406
|
+
{activities.map(activity => (
|
|
407
|
+
<ActivityRow key={activity.id} activity={activity} />
|
|
408
|
+
))}
|
|
409
|
+
</VStack>
|
|
410
|
+
</Box>
|
|
411
|
+
);
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Solution: Extract Helper Functions
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
/**
|
|
418
|
+
* Renders the user header section.
|
|
419
|
+
* @param props - Section properties
|
|
420
|
+
* @param props.user - User data object
|
|
421
|
+
*/
|
|
422
|
+
function renderHeader(props: { readonly user: User }) {
|
|
423
|
+
const { user } = props;
|
|
424
|
+
return (
|
|
425
|
+
<HStack>
|
|
426
|
+
<Avatar source={user.avatar} />
|
|
427
|
+
<VStack>
|
|
428
|
+
<Text>{user.name}</Text>
|
|
429
|
+
<Text>{user.role}</Text>
|
|
430
|
+
</VStack>
|
|
431
|
+
{user.isAdmin && <AdminBadge />}
|
|
432
|
+
</HStack>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Renders the stats row section.
|
|
438
|
+
* @param props - Section properties
|
|
439
|
+
* @param props.stats - Array of stat objects
|
|
440
|
+
*/
|
|
441
|
+
function renderStats(props: { readonly stats: readonly Stat[] }) {
|
|
442
|
+
const { stats } = props;
|
|
443
|
+
return (
|
|
444
|
+
<HStack>
|
|
445
|
+
{stats.map(stat => (
|
|
446
|
+
<Box key={stat.id}>
|
|
447
|
+
<Text>{stat.value}</Text>
|
|
448
|
+
<Text>{stat.label}</Text>
|
|
449
|
+
{stat.trend > 0 ? <TrendUp /> : <TrendDown />}
|
|
450
|
+
</Box>
|
|
451
|
+
))}
|
|
452
|
+
</HStack>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Renders the notifications section.
|
|
458
|
+
* @param props - Section properties
|
|
459
|
+
* @param props.notifications - Array of notification objects
|
|
460
|
+
* @param props.maxVisible - Maximum notifications to show before truncating
|
|
461
|
+
*/
|
|
462
|
+
function renderNotifications(props: {
|
|
463
|
+
readonly notifications: readonly Notification[];
|
|
464
|
+
readonly maxVisible: number;
|
|
465
|
+
}) {
|
|
466
|
+
const { notifications, maxVisible } = props;
|
|
467
|
+
if (notifications.length === 0) return null;
|
|
468
|
+
|
|
469
|
+
const visible = notifications.slice(0, maxVisible);
|
|
470
|
+
const remaining = notifications.length - maxVisible;
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<VStack>
|
|
474
|
+
<Text>Notifications ({notifications.length})</Text>
|
|
475
|
+
{visible.map(n => (
|
|
476
|
+
<NotificationItem key={n.id} notification={n} />
|
|
477
|
+
))}
|
|
478
|
+
{remaining > 0 && <Text>+{remaining} more</Text>}
|
|
479
|
+
</VStack>
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Clean View - Complexity: ~5
|
|
484
|
+
const DashboardView = ({ user, stats, notifications, activities }: Props) => (
|
|
485
|
+
<Box>
|
|
486
|
+
{renderHeader({ user })}
|
|
487
|
+
{renderStats({ stats })}
|
|
488
|
+
{renderNotifications({ notifications, maxVisible: 5 })}
|
|
489
|
+
<ActivityList activities={activities} />
|
|
490
|
+
</Box>
|
|
491
|
+
);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Complexity Calculation Reference
|
|
495
|
+
|
|
496
|
+
Understanding how SonarJS calculates cognitive complexity:
|
|
497
|
+
|
|
498
|
+
| Construct | Base Cost | Nesting Penalty |
|
|
499
|
+
| ---------------------------- | --------- | --------------- |
|
|
500
|
+
| `if` / `else if` / `else` | +1 | +1 per level |
|
|
501
|
+
| `? :` (ternary) | +1 | +1 per level |
|
|
502
|
+
| `&&` / `\|\|` (logical) | +1 | +1 per level |
|
|
503
|
+
| `for` / `while` / `do-while` | +1 | +1 per level |
|
|
504
|
+
| `.map()` / `.filter()` etc. | +1 | +1 per level |
|
|
505
|
+
| `catch` | +1 | +1 per level |
|
|
506
|
+
| `switch` | +1 total | - |
|
|
507
|
+
| `case` (in switch) | +1 each | - |
|
|
508
|
+
| `break` / `continue` | +1 | - |
|
|
509
|
+
| Nested function | +1 | +1 per level |
|
|
510
|
+
|
|
511
|
+
### Example Calculation
|
|
512
|
+
|
|
513
|
+
```tsx
|
|
514
|
+
const Example = (
|
|
515
|
+
{ items, filter }: Props // Base: 0
|
|
516
|
+
) => (
|
|
517
|
+
<Box>
|
|
518
|
+
{items.length > 0 && ( // +1 (&&)
|
|
519
|
+
<VStack>
|
|
520
|
+
{items // +1 (map, nested in &&)
|
|
521
|
+
.filter(i => i.active) // +1 (filter, nested)
|
|
522
|
+
.map(item => (
|
|
523
|
+
<Box key={item.id}>
|
|
524
|
+
{item.isSpecial ? ( // +1 (ternary, deeply nested)
|
|
525
|
+
<SpecialItem item={item} />
|
|
526
|
+
) : (
|
|
527
|
+
<RegularItem item={item} />
|
|
528
|
+
)}
|
|
529
|
+
</Box>
|
|
530
|
+
))}
|
|
531
|
+
</VStack>
|
|
532
|
+
)}
|
|
533
|
+
</Box>
|
|
534
|
+
);
|
|
535
|
+
// Total: 4 + nesting penalties ≈ 8-10
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Testing After Refactoring
|
|
539
|
+
|
|
540
|
+
Always verify the refactoring didn't break functionality:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
# 1. Verify lint passes
|
|
544
|
+
bun run lint 2>&1 | grep "cognitive-complexity"
|
|
545
|
+
|
|
546
|
+
# 2. Run unit tests
|
|
547
|
+
bun run test:unit --watch --testPathPattern="ComponentName"
|
|
548
|
+
|
|
549
|
+
# 3. Run full test suite
|
|
550
|
+
bun run test:unit
|
|
551
|
+
|
|
552
|
+
# 4. Manual verification
|
|
553
|
+
bun run start:dev
|
|
554
|
+
# Navigate to the refactored component and test interactions
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
> **Note:** Replace `bun` with your project's package manager (`npm`, `yarn`, `pnpm`) as needed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-nestjs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "Claude Code governance plugin for NestJS/GraphQL projects — includes all universal skills, agents, hooks, and rules from Lisa plus NestJS-specific tooling (GraphQL, TypeORM, security scanning)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-rails",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "Claude Code governance plugin for Ruby on Rails projects — includes all universal skills, agents, hooks, and rules from Lisa plus Rails-specific tooling (ActionController, ActionView, ActiveRecord)",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-typescript",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "Claude Code governance plugin for TypeScript projects — includes all universal skills, agents, hooks, and rules from Lisa plus TypeScript-specific tooling",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|