pumuki-ast-hooks 5.3.17 → 5.3.19

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.
@@ -7,8 +7,6 @@ class iOSArchitectureRules {
7
7
  }
8
8
 
9
9
  runRules(files) {
10
- console.log(`[iOS Architecture] Detected pattern: ${this.pattern}`);
11
-
12
10
  switch (this.pattern) {
13
11
  case 'FEATURE_FIRST_CLEAN_DDD':
14
12
  this.checkFeatureFirstCleanDDDRules(files);
@@ -16,9 +14,6 @@ class iOSArchitectureRules {
16
14
  case 'MVVM':
17
15
  this.checkMVVMRules(files);
18
16
  break;
19
- case 'MVVM-C':
20
- this.checkMVVMCRules(files);
21
- break;
22
17
  case 'MVP':
23
18
  this.checkMVPRules(files);
24
19
  break;
@@ -38,7 +33,7 @@ class iOSArchitectureRules {
38
33
  this.checkMixedArchitectureRules(files);
39
34
  break;
40
35
  default:
41
- console.log(`[iOS Architecture] No specific rules for pattern: ${this.pattern}`);
36
+ break;
42
37
  }
43
38
  }
44
39
 
@@ -68,10 +63,10 @@ class iOSArchitectureRules {
68
63
  });
69
64
 
70
65
  if (file.includes('/domain/') && !file.includes('/entities/') &&
71
- !file.includes('/value-objects/') && !file.includes('/interfaces/')) {
66
+ !file.includes('/value-objects/') && !file.includes('/interfaces/')) {
72
67
  pushFinding(this.findings, {
73
68
  ruleId: 'ios.clean.domain_structure',
74
- severity: 'medium',
69
+ severity: 'critical',
75
70
  message: 'Archivo en domain/ sin estructura correcta. Usar entities/, value-objects/, interfaces/',
76
71
  filePath: file,
77
72
  line: 1
@@ -86,7 +81,7 @@ class iOSArchitectureRules {
86
81
  if (hasProperties > 3 && hasMethods === 0) {
87
82
  pushFinding(this.findings, {
88
83
  ruleId: 'ios.ddd.anemic_entity',
89
- severity: 'high',
84
+ severity: 'critical',
90
85
  message: 'Entity anémica (solo properties, sin comportamiento). Añadir métodos de negocio.',
91
86
  filePath: file,
92
87
  line: 1,
@@ -99,7 +94,7 @@ class iOSArchitectureRules {
99
94
  if (content.includes('var ') && !content.includes('private(set)')) {
100
95
  pushFinding(this.findings, {
101
96
  ruleId: 'ios.ddd.mutable_value_object',
102
- severity: 'high',
97
+ severity: 'critical',
103
98
  message: 'Value Object con properties mutables. VOs deben ser inmutables (usar let).',
104
99
  filePath: file,
105
100
  line: 1,
@@ -110,7 +105,7 @@ class iOSArchitectureRules {
110
105
  if (!content.includes('init(') || !content.includes('throw')) {
111
106
  pushFinding(this.findings, {
112
107
  ruleId: 'ios.ddd.value_object_no_validation',
113
- severity: 'medium',
108
+ severity: 'critical',
114
109
  message: 'Value Object sin validación en init(). VOs deben garantizar invariantes.',
115
110
  filePath: file,
116
111
  line: 1,
@@ -128,7 +123,7 @@ class iOSArchitectureRules {
128
123
  if (!content.includes('func execute(')) {
129
124
  pushFinding(this.findings, {
130
125
  ruleId: 'ios.ddd.usecase_missing_execute',
131
- severity: 'high',
126
+ severity: 'critical',
132
127
  message: 'Use Case sin método execute(). Convención: func execute(input: Input) async throws -> Output',
133
128
  filePath: file,
134
129
  line: 1
@@ -138,7 +133,7 @@ class iOSArchitectureRules {
138
133
  if (content.includes('UIKit') || content.includes('SwiftUI')) {
139
134
  pushFinding(this.findings, {
140
135
  ruleId: 'ios.clean.usecase_ui_dependency',
141
- severity: 'high',
136
+ severity: 'critical',
142
137
  message: 'Use Case depende de UI framework. Application layer debe ser UI-agnostic.',
143
138
  filePath: file,
144
139
  line: 1
@@ -150,7 +145,7 @@ class iOSArchitectureRules {
150
145
  if (!file.includes('/infrastructure/')) {
151
146
  pushFinding(this.findings, {
152
147
  ruleId: 'ios.clean.repository_wrong_layer',
153
- severity: 'high',
148
+ severity: 'critical',
154
149
  message: 'Repository implementation fuera de infrastructure/. Mover a infrastructure/repositories/',
155
150
  filePath: file,
156
151
  line: 1
@@ -162,7 +157,7 @@ class iOSArchitectureRules {
162
157
  if (!file.includes('/domain/')) {
163
158
  pushFinding(this.findings, {
164
159
  ruleId: 'ios.clean.repository_interface_wrong_layer',
165
- severity: 'high',
160
+ severity: 'critical',
166
161
  message: 'Repository protocol fuera de domain/. Mover a domain/interfaces/',
167
162
  filePath: file,
168
163
  line: 1
@@ -174,7 +169,7 @@ class iOSArchitectureRules {
174
169
  if (!file.includes('/application/')) {
175
170
  pushFinding(this.findings, {
176
171
  ruleId: 'ios.clean.dto_wrong_layer',
177
- severity: 'medium',
172
+ severity: 'critical',
178
173
  message: 'DTO fuera de application/. Mover a application/dto/',
179
174
  filePath: file,
180
175
  line: 1
@@ -213,7 +208,7 @@ class iOSArchitectureRules {
213
208
  if (complexMethods.length > 0) {
214
209
  pushFinding(this.findings, {
215
210
  ruleId: 'ios.clean.infrastructure_business_logic',
216
- severity: 'high',
211
+ severity: 'critical',
217
212
  message: 'Infrastructure con lógica de negocio compleja. Mover a domain/ o application/',
218
213
  filePath: file,
219
214
  line: 1,
@@ -226,7 +221,7 @@ class iOSArchitectureRules {
226
221
  if (content.includes('Entity') && !content.includes('DTO') && !content.includes('Dto')) {
227
222
  pushFinding(this.findings, {
228
223
  ruleId: 'ios.clean.presentation_uses_entity',
229
- severity: 'medium',
224
+ severity: 'critical',
230
225
  message: 'Presentation usando Entities de domain directamente. Usar DTOs para desacoplar.',
231
226
  filePath: file,
232
227
  line: 1,
@@ -245,7 +240,7 @@ class iOSArchitectureRules {
245
240
  if (!content.includes('ObservableObject') && !content.includes('@Observable')) {
246
241
  pushFinding(this.findings, {
247
242
  ruleId: 'ios.mvvm.viewmodel_not_observable',
248
- severity: 'high',
243
+ severity: 'critical',
249
244
  message: 'ViewModel debe conformar ObservableObject o usar @Observable macro (iOS 17+)',
250
245
  filePath: file,
251
246
  line: 1
@@ -255,7 +250,7 @@ class iOSArchitectureRules {
255
250
  if (content.match(/import\s+UIKit/) && !content.includes('#if canImport(UIKit)')) {
256
251
  pushFinding(this.findings, {
257
252
  ruleId: 'ios.mvvm.viewmodel_uikit_dependency',
258
- severity: 'high',
253
+ severity: 'critical',
259
254
  message: 'ViewModel NO debe depender de UIKit. Usar tipos agnósticos de plataforma.',
260
255
  filePath: file,
261
256
  line: content.split('\n').findIndex(line => line.includes('import UIKit')) + 1
@@ -266,7 +261,7 @@ class iOSArchitectureRules {
266
261
  if (classMatch && content.includes('var ') && !content.includes('@Published')) {
267
262
  pushFinding(this.findings, {
268
263
  ruleId: 'ios.mvvm.missing_published',
269
- severity: 'medium',
264
+ severity: 'critical',
270
265
  message: 'ViewModel properties que cambian deben usar @Published para notificar a la View',
271
266
  filePath: file,
272
267
  line: 1
@@ -276,7 +271,7 @@ class iOSArchitectureRules {
276
271
 
277
272
  if (file.includes('View.swift') || content.includes('struct ') && content.includes(': View')) {
278
273
  const hasBusinessLogic =
279
- /func\s+\w+\([^)]*\)\s*->\s*\w+\s*{[\s\S]{100,}/.test(content) ||
274
+ /func\s+\w+\([^)]*\)\s*->\s*\w+\s*{[\s\S]{100,}/.test(content) ||
280
275
  content.includes('URLSession') ||
281
276
  content.includes('CoreData') ||
282
277
  /\.save\(|\.fetch\(|\.delete\(/.test(content);
@@ -284,7 +279,7 @@ class iOSArchitectureRules {
284
279
  if (hasBusinessLogic) {
285
280
  pushFinding(this.findings, {
286
281
  ruleId: 'ios.mvvm.view_business_logic',
287
- severity: 'high',
282
+ severity: 'critical',
288
283
  message: 'View contiene lógica de negocio. Mover al ViewModel.',
289
284
  filePath: file,
290
285
  line: 1
@@ -294,63 +289,6 @@ class iOSArchitectureRules {
294
289
  });
295
290
  }
296
291
 
297
- checkMVVMCRules(files) {
298
- this.checkMVVMRules(files);
299
-
300
- files.forEach(file => {
301
- const content = this.readFile(file);
302
-
303
- if (file.includes('Coordinator.swift')) {
304
- if (!content.includes('protocol Coordinator') && !content.includes(': Coordinator')) {
305
- pushFinding(this.findings, {
306
- ruleId: 'ios.mvvmc.coordinator_protocol',
307
- severity: 'medium',
308
- message: 'Coordinator debe conformar protocol Coordinator con start() y navigate(to:)',
309
- filePath: file,
310
- line: 1
311
- });
312
- }
313
-
314
- if (!/func\s+start\(\)/.test(content)) {
315
- pushFinding(this.findings, {
316
- ruleId: 'ios.mvvmc.coordinator_missing_start',
317
- severity: 'high',
318
- message: 'Coordinator debe implementar func start() para iniciar el flujo',
319
- filePath: file,
320
- line: 1
321
- });
322
- }
323
-
324
- const hasBusinessLogic =
325
- content.includes('URLSession') ||
326
- content.includes('CoreData') ||
327
- /\.save\(|\.fetch\(|\.delete\(/.test(content);
328
-
329
- if (hasBusinessLogic) {
330
- pushFinding(this.findings, {
331
- ruleId: 'ios.mvvmc.coordinator_business_logic',
332
- severity: 'high',
333
- message: 'Coordinator NO debe contener lógica de negocio. Solo navegación.',
334
- filePath: file,
335
- line: 1
336
- });
337
- }
338
- }
339
-
340
- if (file.includes('ViewModel.swift')) {
341
- if (content.includes('navigationController') || content.includes('.present(')) {
342
- pushFinding(this.findings, {
343
- ruleId: 'ios.mvvmc.viewmodel_navigation',
344
- severity: 'high',
345
- message: 'ViewModel NO debe manejar navegación. Delegar al Coordinator.',
346
- filePath: file,
347
- line: 1
348
- });
349
- }
350
- }
351
- });
352
- }
353
-
354
292
  checkMVPRules(files) {
355
293
  files.forEach(file => {
356
294
  const content = this.readFile(file);
@@ -359,7 +297,7 @@ class iOSArchitectureRules {
359
297
  if (!content.includes('protocol ') && content.includes('View')) {
360
298
  pushFinding(this.findings, {
361
299
  ruleId: 'ios.mvp.view_not_protocol',
362
- severity: 'high',
300
+ severity: 'critical',
363
301
  message: 'En MVP, View debe ser un protocol implementado por ViewController',
364
302
  filePath: file,
365
303
  line: 1
@@ -371,7 +309,7 @@ class iOSArchitectureRules {
371
309
  if (content.includes('var view:') && !content.includes('weak var view')) {
372
310
  pushFinding(this.findings, {
373
311
  ruleId: 'ios.mvp.presenter_strong_view',
374
- severity: 'high',
312
+ severity: 'critical',
375
313
  message: 'Presenter debe tener referencia weak a View para evitar retain cycles',
376
314
  filePath: file,
377
315
  line: content.split('\n').findIndex(line => line.includes('var view:')) + 1
@@ -385,7 +323,7 @@ class iOSArchitectureRules {
385
323
  if (hasLogic < 3) {
386
324
  pushFinding(this.findings, {
387
325
  ruleId: 'ios.mvp.presenter_thin',
388
- severity: 'medium',
326
+ severity: 'critical',
389
327
  message: 'Presenter parece tener poca lógica. En MVP, Presenter debe contener toda la lógica de presentación.',
390
328
  filePath: file,
391
329
  line: 1
@@ -402,7 +340,7 @@ class iOSArchitectureRules {
402
340
  if (hasBusinessLogic) {
403
341
  pushFinding(this.findings, {
404
342
  ruleId: 'ios.mvp.viewcontroller_business_logic',
405
- severity: 'high',
343
+ severity: 'critical',
406
344
  message: 'ViewController NO debe contener lógica de negocio. Delegar al Presenter.',
407
345
  filePath: file,
408
346
  line: 1
@@ -494,7 +432,7 @@ class iOSArchitectureRules {
494
432
 
495
433
  if (file.includes('Entity.swift')) {
496
434
  const hasMethods = (content.match(/func\s+/g) || []).length;
497
- if (hasMethods > 2) {
435
+ if (hasMethods > 2) {
498
436
  pushFinding(this.findings, {
499
437
  ruleId: 'ios.viper.entity_with_logic',
500
438
  severity: 'medium',
@@ -537,7 +475,7 @@ class iOSArchitectureRules {
537
475
 
538
476
  if (content.includes(': Reducer')) {
539
477
  if ((content.includes('URLSession') || content.includes('async ')) &&
540
- !content.includes('Effect')) {
478
+ !content.includes('Effect')) {
541
479
  pushFinding(this.findings, {
542
480
  ruleId: 'ios.tca.missing_effect',
543
481
  severity: 'high',