pumuki 6.3.269 → 6.3.271

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 (35) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/VERSION +1 -1
  3. package/core/facts/detectors/text/android.test.ts +538 -0
  4. package/core/facts/detectors/text/android.ts +436 -0
  5. package/core/facts/detectors/text/ios.test.ts +328 -1
  6. package/core/facts/detectors/text/ios.ts +241 -0
  7. package/core/facts/detectors/typescript/index.test.ts +393 -0
  8. package/core/facts/detectors/typescript/index.ts +316 -0
  9. package/core/facts/extractHeuristicFacts.ts +70 -1
  10. package/core/rules/presets/heuristics/android.test.ts +91 -1
  11. package/core/rules/presets/heuristics/android.ts +360 -0
  12. package/core/rules/presets/heuristics/ios.test.ts +54 -1
  13. package/core/rules/presets/heuristics/ios.ts +243 -2
  14. package/core/rules/presets/heuristics/typescript.test.ts +50 -2
  15. package/core/rules/presets/heuristics/typescript.ts +162 -0
  16. package/docs/operations/RELEASE_NOTES.md +4 -0
  17. package/integrations/config/skillsDetectorRegistry.ts +501 -0
  18. package/integrations/config/skillsRuleClassification.ts +127 -3
  19. package/integrations/context/contextGate.ts +192 -0
  20. package/integrations/git/runPlatformGate.ts +4 -1
  21. package/integrations/lifecycle/preWriteAutomation.ts +1 -0
  22. package/integrations/lifecycle/preWriteLease.ts +41 -4
  23. package/package.json +2 -1
  24. package/scripts/classify-skills-rules.ts +2 -2
  25. package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
  26. package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
  27. package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
  28. package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
  29. package/scripts/framework-menu-gate-lib.ts +86 -1
  30. package/scripts/framework-menu-layout-data.ts +3 -3
  31. package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
  32. package/scripts/framework-menu.ts +10 -6
  33. package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
  34. package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
  35. package/scripts/package-manifest-lib.ts +1 -0
@@ -3,7 +3,7 @@ import test from 'node:test';
3
3
  import { androidRules } from './android';
4
4
 
5
5
  test('androidRules define reglas heurísticas locked para plataforma android', () => {
6
- assert.equal(androidRules.length, 20);
6
+ assert.equal(androidRules.length, 38);
7
7
 
8
8
  const ids = androidRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
@@ -11,10 +11,28 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
11
11
  'heuristics.android.globalscope.ast',
12
12
  'heuristics.android.run-blocking.ast',
13
13
  'heuristics.android.flow.livedata-state-exposure.ast',
14
+ 'heuristics.android.flow.viewmodel-flow-without-statein.ast',
15
+ 'heuristics.android.flow.sharedflow-used-as-state.ast',
16
+ 'heuristics.android.concurrency.asynctask.ast',
17
+ 'heuristics.android.architecture.god-activity.ast',
18
+ 'heuristics.android.compose.non-lazy-scrollable-collection.ast',
19
+ 'heuristics.android.compose.unstable-launched-effect-key.ast',
20
+ 'heuristics.android.compose.launched-effect-busy-loop.ast',
21
+ 'heuristics.android.observability.production-logging.ast',
22
+ 'heuristics.android.compose.modifier-background-before-padding.ast',
23
+ 'heuristics.android.accessibility.missing-content-description.ast',
24
+ 'heuristics.android.accessibility.fontscale-disabled.ast',
25
+ 'heuristics.android.compose.incomplete-material-theme.ast',
26
+ 'heuristics.android.compose.legacy-bottom-navigation.ast',
27
+ 'heuristics.android.navigation.imperative-navigation.ast',
28
+ 'heuristics.android.compose.object-creation-without-remember.ast',
29
+ 'heuristics.android.compose.state-creation-without-remember.ast',
30
+ 'heuristics.android.null-safety.force-unwrap.ast',
14
31
  'heuristics.android.security.local-properties-tracked.ast',
15
32
  'heuristics.android.persistence.shared-preferences-usage.ast',
16
33
  'heuristics.android.testing.junit4-usage.ast',
17
34
  'heuristics.android.testing.production-mock-usage.ast',
35
+ 'heuristics.android.ui.hardcoded-string.ast',
18
36
  'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
19
37
  'heuristics.android.coroutines.dispatchers-main-boundary-leak.ast',
20
38
  'heuristics.android.coroutines.hardcoded-background-dispatcher.ast',
@@ -56,6 +74,74 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
56
74
  byId.get('heuristics.android.flow.livedata-state-exposure.ast')?.then.code,
57
75
  'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST'
58
76
  );
77
+ assert.equal(
78
+ byId.get('heuristics.android.flow.viewmodel-flow-without-statein.ast')?.then.code,
79
+ 'HEURISTICS_ANDROID_FLOW_VIEWMODEL_FLOW_WITHOUT_STATEIN_AST'
80
+ );
81
+ assert.equal(
82
+ byId.get('heuristics.android.flow.sharedflow-used-as-state.ast')?.then.code,
83
+ 'HEURISTICS_ANDROID_FLOW_SHAREDFLOW_USED_AS_STATE_AST'
84
+ );
85
+ assert.equal(
86
+ byId.get('heuristics.android.concurrency.asynctask.ast')?.then.code,
87
+ 'HEURISTICS_ANDROID_CONCURRENCY_ASYNCTASK_AST'
88
+ );
89
+ assert.equal(
90
+ byId.get('heuristics.android.architecture.god-activity.ast')?.then.code,
91
+ 'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST'
92
+ );
93
+ assert.equal(
94
+ byId.get('heuristics.android.compose.non-lazy-scrollable-collection.ast')?.then.code,
95
+ 'HEURISTICS_ANDROID_COMPOSE_NON_LAZY_SCROLLABLE_COLLECTION_AST'
96
+ );
97
+ assert.equal(
98
+ byId.get('heuristics.android.compose.unstable-launched-effect-key.ast')?.then.code,
99
+ 'HEURISTICS_ANDROID_COMPOSE_UNSTABLE_LAUNCHED_EFFECT_KEY_AST'
100
+ );
101
+ assert.equal(
102
+ byId.get('heuristics.android.compose.launched-effect-busy-loop.ast')?.then.code,
103
+ 'HEURISTICS_ANDROID_COMPOSE_LAUNCHED_EFFECT_BUSY_LOOP_AST'
104
+ );
105
+ assert.equal(
106
+ byId.get('heuristics.android.observability.production-logging.ast')?.then.code,
107
+ 'HEURISTICS_ANDROID_OBSERVABILITY_PRODUCTION_LOGGING_AST'
108
+ );
109
+ assert.equal(
110
+ byId.get('heuristics.android.compose.modifier-background-before-padding.ast')?.then.code,
111
+ 'HEURISTICS_ANDROID_COMPOSE_MODIFIER_BACKGROUND_BEFORE_PADDING_AST'
112
+ );
113
+ assert.equal(
114
+ byId.get('heuristics.android.accessibility.missing-content-description.ast')?.then.code,
115
+ 'HEURISTICS_ANDROID_ACCESSIBILITY_MISSING_CONTENT_DESCRIPTION_AST'
116
+ );
117
+ assert.equal(
118
+ byId.get('heuristics.android.accessibility.fontscale-disabled.ast')?.then.code,
119
+ 'HEURISTICS_ANDROID_ACCESSIBILITY_FONTSCALE_DISABLED_AST'
120
+ );
121
+ assert.equal(
122
+ byId.get('heuristics.android.compose.incomplete-material-theme.ast')?.then.code,
123
+ 'HEURISTICS_ANDROID_COMPOSE_INCOMPLETE_MATERIAL_THEME_AST'
124
+ );
125
+ assert.equal(
126
+ byId.get('heuristics.android.compose.legacy-bottom-navigation.ast')?.then.code,
127
+ 'HEURISTICS_ANDROID_COMPOSE_LEGACY_BOTTOM_NAVIGATION_AST'
128
+ );
129
+ assert.equal(
130
+ byId.get('heuristics.android.navigation.imperative-navigation.ast')?.then.code,
131
+ 'HEURISTICS_ANDROID_NAVIGATION_IMPERATIVE_NAVIGATION_AST'
132
+ );
133
+ assert.equal(
134
+ byId.get('heuristics.android.compose.object-creation-without-remember.ast')?.then.code,
135
+ 'HEURISTICS_ANDROID_COMPOSE_OBJECT_CREATION_WITHOUT_REMEMBER_AST'
136
+ );
137
+ assert.equal(
138
+ byId.get('heuristics.android.compose.state-creation-without-remember.ast')?.then.code,
139
+ 'HEURISTICS_ANDROID_COMPOSE_STATE_CREATION_WITHOUT_REMEMBER_AST'
140
+ );
141
+ assert.equal(
142
+ byId.get('heuristics.android.null-safety.force-unwrap.ast')?.then.code,
143
+ 'HEURISTICS_ANDROID_NULL_SAFETY_FORCE_UNWRAP_AST'
144
+ );
59
145
  assert.equal(
60
146
  byId.get('heuristics.android.security.local-properties-tracked.ast')?.then.code,
61
147
  'HEURISTICS_ANDROID_SECURITY_LOCAL_PROPERTIES_TRACKED_AST'
@@ -72,6 +158,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
72
158
  byId.get('heuristics.android.testing.production-mock-usage.ast')?.then.code,
73
159
  'HEURISTICS_ANDROID_TESTING_PRODUCTION_MOCK_USAGE_AST'
74
160
  );
161
+ assert.equal(
162
+ byId.get('heuristics.android.ui.hardcoded-string.ast')?.then.code,
163
+ 'HEURISTICS_ANDROID_UI_HARDCODED_STRING_AST'
164
+ );
75
165
  assert.equal(
76
166
  byId.get('heuristics.android.coroutines.manual-scope-in-viewmodel.ast')?.then.code,
77
167
  'HEURISTICS_ANDROID_COROUTINES_MANUAL_SCOPE_IN_VIEWMODEL_AST'
@@ -75,6 +75,346 @@ export const androidRules: RuleSet = [
75
75
  code: 'HEURISTICS_ANDROID_FLOW_LIVEDATA_STATE_EXPOSURE_AST',
76
76
  },
77
77
  },
78
+ {
79
+ id: 'heuristics.android.flow.viewmodel-flow-without-statein.ast',
80
+ description:
81
+ 'Detects Android ViewModel state exposed as cold Flow without stateIn conversion to StateFlow.',
82
+ severity: 'WARN',
83
+ platform: 'android',
84
+ locked: true,
85
+ when: {
86
+ kind: 'Heuristic',
87
+ where: {
88
+ ruleId: 'heuristics.android.flow.viewmodel-flow-without-statein.ast',
89
+ },
90
+ },
91
+ then: {
92
+ kind: 'Finding',
93
+ message:
94
+ 'AST heuristic detected ViewModel exposing cold Flow state without stateIn.',
95
+ code: 'HEURISTICS_ANDROID_FLOW_VIEWMODEL_FLOW_WITHOUT_STATEIN_AST',
96
+ },
97
+ },
98
+ {
99
+ id: 'heuristics.android.flow.sharedflow-used-as-state.ast',
100
+ description:
101
+ 'Detects SharedFlow or MutableSharedFlow used as ViewModel state instead of events.',
102
+ severity: 'WARN',
103
+ platform: 'android',
104
+ locked: true,
105
+ when: {
106
+ kind: 'Heuristic',
107
+ where: {
108
+ ruleId: 'heuristics.android.flow.sharedflow-used-as-state.ast',
109
+ },
110
+ },
111
+ then: {
112
+ kind: 'Finding',
113
+ message:
114
+ 'AST heuristic detected SharedFlow used as ViewModel state.',
115
+ code: 'HEURISTICS_ANDROID_FLOW_SHAREDFLOW_USED_AS_STATE_AST',
116
+ },
117
+ },
118
+ {
119
+ id: 'heuristics.android.concurrency.asynctask.ast',
120
+ description:
121
+ 'Detects deprecated AsyncTask usage in Android production code where coroutines should be used.',
122
+ severity: 'WARN',
123
+ platform: 'android',
124
+ locked: true,
125
+ when: {
126
+ kind: 'Heuristic',
127
+ where: {
128
+ ruleId: 'heuristics.android.concurrency.asynctask.ast',
129
+ },
130
+ },
131
+ then: {
132
+ kind: 'Finding',
133
+ message:
134
+ 'AST heuristic detected deprecated AsyncTask usage in Android production code.',
135
+ code: 'HEURISTICS_ANDROID_CONCURRENCY_ASYNCTASK_AST',
136
+ },
137
+ },
138
+ {
139
+ id: 'heuristics.android.architecture.god-activity.ast',
140
+ description:
141
+ 'Detects Android Activity classes that mix UI entrypoint with product responsibilities instead of staying as thin single-activity shells.',
142
+ severity: 'WARN',
143
+ platform: 'android',
144
+ locked: true,
145
+ when: {
146
+ kind: 'Heuristic',
147
+ where: {
148
+ ruleId: 'heuristics.android.architecture.god-activity.ast',
149
+ },
150
+ },
151
+ then: {
152
+ kind: 'Finding',
153
+ message:
154
+ 'AST heuristic detected an Android Activity mixing UI entrypoint with product responsibilities.',
155
+ code: 'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST',
156
+ },
157
+ },
158
+ {
159
+ id: 'heuristics.android.compose.non-lazy-scrollable-collection.ast',
160
+ description:
161
+ 'Detects scrollable Column/Row collection rendering where LazyColumn or LazyRow should be used for virtualization.',
162
+ severity: 'WARN',
163
+ platform: 'android',
164
+ locked: true,
165
+ when: {
166
+ kind: 'Heuristic',
167
+ where: {
168
+ ruleId: 'heuristics.android.compose.non-lazy-scrollable-collection.ast',
169
+ },
170
+ },
171
+ then: {
172
+ kind: 'Finding',
173
+ message:
174
+ 'AST heuristic detected non-lazy scrollable collection rendering in Compose.',
175
+ code: 'HEURISTICS_ANDROID_COMPOSE_NON_LAZY_SCROLLABLE_COLLECTION_AST',
176
+ },
177
+ },
178
+ {
179
+ id: 'heuristics.android.compose.unstable-launched-effect-key.ast',
180
+ description:
181
+ 'Detects LaunchedEffect calls without meaningful state keys, such as Unit, true, false, null or no key.',
182
+ severity: 'WARN',
183
+ platform: 'android',
184
+ locked: true,
185
+ when: {
186
+ kind: 'Heuristic',
187
+ where: {
188
+ ruleId: 'heuristics.android.compose.unstable-launched-effect-key.ast',
189
+ },
190
+ },
191
+ then: {
192
+ kind: 'Finding',
193
+ message:
194
+ 'AST heuristic detected LaunchedEffect without a stable restart key.',
195
+ code: 'HEURISTICS_ANDROID_COMPOSE_UNSTABLE_LAUNCHED_EFFECT_KEY_AST',
196
+ },
197
+ },
198
+ {
199
+ id: 'heuristics.android.compose.launched-effect-busy-loop.ast',
200
+ description:
201
+ 'Detects non-cooperative busy loops inside LaunchedEffect blocks.',
202
+ severity: 'WARN',
203
+ platform: 'android',
204
+ locked: true,
205
+ when: {
206
+ kind: 'Heuristic',
207
+ where: {
208
+ ruleId: 'heuristics.android.compose.launched-effect-busy-loop.ast',
209
+ },
210
+ },
211
+ then: {
212
+ kind: 'Finding',
213
+ message:
214
+ 'AST heuristic detected non-cooperative loop inside LaunchedEffect.',
215
+ code: 'HEURISTICS_ANDROID_COMPOSE_LAUNCHED_EFFECT_BUSY_LOOP_AST',
216
+ },
217
+ },
218
+ {
219
+ id: 'heuristics.android.observability.production-logging.ast',
220
+ description:
221
+ 'Detects unguarded Android production logging through println, System.out/err, Log.* or Timber.*.',
222
+ severity: 'WARN',
223
+ platform: 'android',
224
+ locked: true,
225
+ when: {
226
+ kind: 'Heuristic',
227
+ where: {
228
+ ruleId: 'heuristics.android.observability.production-logging.ast',
229
+ },
230
+ },
231
+ then: {
232
+ kind: 'Finding',
233
+ message:
234
+ 'AST heuristic detected unguarded Android production logging.',
235
+ code: 'HEURISTICS_ANDROID_OBSERVABILITY_PRODUCTION_LOGGING_AST',
236
+ },
237
+ },
238
+ {
239
+ id: 'heuristics.android.compose.modifier-background-before-padding.ast',
240
+ description:
241
+ 'Detects Compose Modifier chains that apply background before padding, changing the painted area unexpectedly.',
242
+ severity: 'WARN',
243
+ platform: 'android',
244
+ locked: true,
245
+ when: {
246
+ kind: 'Heuristic',
247
+ where: {
248
+ ruleId: 'heuristics.android.compose.modifier-background-before-padding.ast',
249
+ },
250
+ },
251
+ then: {
252
+ kind: 'Finding',
253
+ message:
254
+ 'AST heuristic detected Modifier.background before padding.',
255
+ code: 'HEURISTICS_ANDROID_COMPOSE_MODIFIER_BACKGROUND_BEFORE_PADDING_AST',
256
+ },
257
+ },
258
+ {
259
+ id: 'heuristics.android.accessibility.missing-content-description.ast',
260
+ description:
261
+ 'Detects Compose Image/Icon calls without an explicit contentDescription.',
262
+ severity: 'WARN',
263
+ platform: 'android',
264
+ locked: true,
265
+ when: {
266
+ kind: 'Heuristic',
267
+ where: {
268
+ ruleId: 'heuristics.android.accessibility.missing-content-description.ast',
269
+ },
270
+ },
271
+ then: {
272
+ kind: 'Finding',
273
+ message:
274
+ 'AST heuristic detected Image/Icon without contentDescription.',
275
+ code: 'HEURISTICS_ANDROID_ACCESSIBILITY_MISSING_CONTENT_DESCRIPTION_AST',
276
+ },
277
+ },
278
+ {
279
+ id: 'heuristics.android.accessibility.fontscale-disabled.ast',
280
+ description:
281
+ 'Detects Android Compose code that disables system text scaling by forcing fontScale to 1.',
282
+ severity: 'WARN',
283
+ platform: 'android',
284
+ locked: true,
285
+ when: {
286
+ kind: 'Heuristic',
287
+ where: {
288
+ ruleId: 'heuristics.android.accessibility.fontscale-disabled.ast',
289
+ },
290
+ },
291
+ then: {
292
+ kind: 'Finding',
293
+ message:
294
+ 'AST heuristic detected disabled Android system font scaling.',
295
+ code: 'HEURISTICS_ANDROID_ACCESSIBILITY_FONTSCALE_DISABLED_AST',
296
+ },
297
+ },
298
+ {
299
+ id: 'heuristics.android.compose.incomplete-material-theme.ast',
300
+ description:
301
+ 'Detects MaterialTheme declarations missing colorScheme, typography or shapes.',
302
+ severity: 'WARN',
303
+ platform: 'android',
304
+ locked: true,
305
+ when: {
306
+ kind: 'Heuristic',
307
+ where: {
308
+ ruleId: 'heuristics.android.compose.incomplete-material-theme.ast',
309
+ },
310
+ },
311
+ then: {
312
+ kind: 'Finding',
313
+ message:
314
+ 'AST heuristic detected incomplete MaterialTheme declaration.',
315
+ code: 'HEURISTICS_ANDROID_COMPOSE_INCOMPLETE_MATERIAL_THEME_AST',
316
+ },
317
+ },
318
+ {
319
+ id: 'heuristics.android.compose.legacy-bottom-navigation.ast',
320
+ description:
321
+ 'Detects legacy Material 2 BottomNavigation/BottomNavigationItem usage where Material 3 NavigationBar should be used.',
322
+ severity: 'WARN',
323
+ platform: 'android',
324
+ locked: true,
325
+ when: {
326
+ kind: 'Heuristic',
327
+ where: {
328
+ ruleId: 'heuristics.android.compose.legacy-bottom-navigation.ast',
329
+ },
330
+ },
331
+ then: {
332
+ kind: 'Finding',
333
+ message:
334
+ 'AST heuristic detected legacy BottomNavigation usage.',
335
+ code: 'HEURISTICS_ANDROID_COMPOSE_LEGACY_BOTTOM_NAVIGATION_AST',
336
+ },
337
+ },
338
+ {
339
+ id: 'heuristics.android.navigation.imperative-navigation.ast',
340
+ description:
341
+ 'Detects imperative Android navigation through startActivity(Intent) or FragmentManager transactions where Navigation Compose should be used.',
342
+ severity: 'WARN',
343
+ platform: 'android',
344
+ locked: true,
345
+ when: {
346
+ kind: 'Heuristic',
347
+ where: {
348
+ ruleId: 'heuristics.android.navigation.imperative-navigation.ast',
349
+ },
350
+ },
351
+ then: {
352
+ kind: 'Finding',
353
+ message:
354
+ 'AST heuristic detected imperative Android navigation.',
355
+ code: 'HEURISTICS_ANDROID_NAVIGATION_IMPERATIVE_NAVIGATION_AST',
356
+ },
357
+ },
358
+ {
359
+ id: 'heuristics.android.compose.object-creation-without-remember.ast',
360
+ description:
361
+ 'Detects expensive object creation inside Composable functions without remember.',
362
+ severity: 'WARN',
363
+ platform: 'android',
364
+ locked: true,
365
+ when: {
366
+ kind: 'Heuristic',
367
+ where: {
368
+ ruleId: 'heuristics.android.compose.object-creation-without-remember.ast',
369
+ },
370
+ },
371
+ then: {
372
+ kind: 'Finding',
373
+ message:
374
+ 'AST heuristic detected object creation inside a Composable without remember.',
375
+ code: 'HEURISTICS_ANDROID_COMPOSE_OBJECT_CREATION_WITHOUT_REMEMBER_AST',
376
+ },
377
+ },
378
+ {
379
+ id: 'heuristics.android.compose.state-creation-without-remember.ast',
380
+ description:
381
+ 'Detects mutableStateOf or derivedStateOf created inside Composable functions without remember.',
382
+ severity: 'WARN',
383
+ platform: 'android',
384
+ locked: true,
385
+ when: {
386
+ kind: 'Heuristic',
387
+ where: {
388
+ ruleId: 'heuristics.android.compose.state-creation-without-remember.ast',
389
+ },
390
+ },
391
+ then: {
392
+ kind: 'Finding',
393
+ message:
394
+ 'AST heuristic detected Compose state creation without remember.',
395
+ code: 'HEURISTICS_ANDROID_COMPOSE_STATE_CREATION_WITHOUT_REMEMBER_AST',
396
+ },
397
+ },
398
+ {
399
+ id: 'heuristics.android.null-safety.force-unwrap.ast',
400
+ description:
401
+ 'Detects Kotlin force unwrap (!!) usage in Android production code.',
402
+ severity: 'WARN',
403
+ platform: 'android',
404
+ locked: true,
405
+ when: {
406
+ kind: 'Heuristic',
407
+ where: {
408
+ ruleId: 'heuristics.android.null-safety.force-unwrap.ast',
409
+ },
410
+ },
411
+ then: {
412
+ kind: 'Finding',
413
+ message:
414
+ 'AST heuristic detected Kotlin force unwrap (!!) usage.',
415
+ code: 'HEURISTICS_ANDROID_NULL_SAFETY_FORCE_UNWRAP_AST',
416
+ },
417
+ },
78
418
  {
79
419
  id: 'heuristics.android.security.local-properties-tracked.ast',
80
420
  description:
@@ -155,6 +495,26 @@ export const androidRules: RuleSet = [
155
495
  code: 'HEURISTICS_ANDROID_TESTING_PRODUCTION_MOCK_USAGE_AST',
156
496
  },
157
497
  },
498
+ {
499
+ id: 'heuristics.android.ui.hardcoded-string.ast',
500
+ description:
501
+ 'Detects hardcoded user-facing Android UI strings in production Kotlin code.',
502
+ severity: 'WARN',
503
+ platform: 'android',
504
+ locked: true,
505
+ when: {
506
+ kind: 'Heuristic',
507
+ where: {
508
+ ruleId: 'heuristics.android.ui.hardcoded-string.ast',
509
+ },
510
+ },
511
+ then: {
512
+ kind: 'Finding',
513
+ message:
514
+ 'AST heuristic detected hardcoded Android UI string in production Kotlin code.',
515
+ code: 'HEURISTICS_ANDROID_UI_HARDCODED_STRING_AST',
516
+ },
517
+ },
158
518
  {
159
519
  id: 'heuristics.android.coroutines.manual-scope-in-viewmodel.ast',
160
520
  description:
@@ -3,15 +3,17 @@ import test from 'node:test';
3
3
  import { iosRules } from './ios';
4
4
 
5
5
  test('iosRules define reglas heurísticas locked para plataforma ios', () => {
6
- assert.equal(iosRules.length, 88);
6
+ assert.equal(iosRules.length, 101);
7
7
 
8
8
  const ids = iosRules.map((rule) => rule.id);
9
9
  assert.deepEqual(ids, [
10
10
  'heuristics.ios.force-unwrap.ast',
11
11
  'heuristics.ios.anyview.ast',
12
+ 'heuristics.ios.type-erasure.any.ast',
12
13
  'heuristics.ios.force-try.ast',
13
14
  'heuristics.ios.force-cast.ast',
14
15
  'heuristics.ios.callback-style.ast',
16
+ 'heuristics.ios.combine.sink-without-store.ast',
15
17
  'heuristics.ios.dispatchqueue.ast',
16
18
  'heuristics.ios.dispatchgroup.ast',
17
19
  'heuristics.ios.dispatchsemaphore.ast',
@@ -21,8 +23,12 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
21
23
  'heuristics.ios.error.empty-catch.ast',
22
24
  'heuristics.ios.swiftui.onappear-task.ast',
23
25
  'heuristics.ios.swiftui.onchange-task.ast',
26
+ 'heuristics.ios.swiftui.onchange-readonly-var.ast',
24
27
  'heuristics.ios.memory.strong-delegate.ast',
25
28
  'heuristics.ios.memory.strong-self-escaping-closure.ast',
29
+ 'heuristics.ios.memory.unowned-self-capture.ast',
30
+ 'heuristics.ios.maintainability.nested-if-pyramid.ast',
31
+ 'heuristics.ios.maintainability.comment-trivia.ast',
26
32
  'heuristics.ios.architecture.custom-singleton.ast',
27
33
  'heuristics.ios.architecture.swinject.ast',
28
34
  'heuristics.ios.architecture.massive-view-controller.ast',
@@ -39,18 +45,25 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
39
45
  'heuristics.ios.security.userdefaults-sensitive-data.ast',
40
46
  'heuristics.ios.security.insecure-transport.ast',
41
47
  'heuristics.ios.localization.localizable-strings.ast',
48
+ 'heuristics.ios.interface-builder.storyboard-xib.ast',
42
49
  'heuristics.ios.localization.hardcoded-ui-string.ast',
43
50
  'heuristics.ios.assets.loose-resource.ast',
44
51
  'heuristics.ios.accessibility.fixed-font-size.ast',
45
52
  'heuristics.ios.localization.physical-text-alignment.ast',
46
53
  'heuristics.ios.performance.blocking-sleep.ast',
47
54
  'heuristics.ios.accessibility.icon-only-control-label.ast',
55
+ 'heuristics.ios.accessibility.missing-accessibility-identifier.ast',
56
+ 'heuristics.ios.swiftui.missing-bindable-observable-binding.ast',
48
57
  'heuristics.ios.unchecked-sendable.ast',
49
58
  'heuristics.ios.preconcurrency.ast',
50
59
  'heuristics.ios.nonisolated-unsafe.ast',
51
60
  'heuristics.ios.assume-isolated.ast',
52
61
  'heuristics.ios.observable-object.ast',
62
+ 'heuristics.ios.swiftui.legacy-preview-provider.ast',
53
63
  'heuristics.ios.legacy-swiftui-observable-wrapper.ast',
64
+ 'heuristics.ios.testing.test-double-without-protocol.ast',
65
+ 'heuristics.ios.testing.production-test-double.ast',
66
+ 'heuristics.ios.accessibility.low-contrast-static-color-pair.ast',
54
67
  'heuristics.ios.swiftui.non-private-state-ownership.ast',
55
68
  'heuristics.ios.passed-value-state-wrapper.ast',
56
69
  'heuristics.ios.foreach-indices.ast',
@@ -102,6 +115,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
102
115
  byId.get('heuristics.ios.force-unwrap.ast')?.then.code,
103
116
  'HEURISTICS_IOS_FORCE_UNWRAP_AST'
104
117
  );
118
+ assert.equal(
119
+ byId.get('heuristics.ios.type-erasure.any.ast')?.then.code,
120
+ 'HEURISTICS_IOS_TYPE_ERASURE_ANY_AST'
121
+ );
105
122
  assert.equal(
106
123
  byId.get('heuristics.ios.task-detached.ast')?.then.code,
107
124
  'HEURISTICS_IOS_TASK_DETACHED_AST'
@@ -114,10 +131,30 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
114
131
  byId.get('heuristics.ios.error.empty-catch.ast')?.then.code,
115
132
  'HEURISTICS_IOS_ERROR_EMPTY_CATCH_AST'
116
133
  );
134
+ assert.equal(
135
+ byId.get('heuristics.ios.combine.sink-without-store.ast')?.then.code,
136
+ 'HEURISTICS_IOS_COMBINE_SINK_WITHOUT_STORE_AST'
137
+ );
138
+ assert.equal(
139
+ byId.get('heuristics.ios.memory.unowned-self-capture.ast')?.then.code,
140
+ 'HEURISTICS_IOS_MEMORY_UNOWNED_SELF_CAPTURE_AST'
141
+ );
142
+ assert.equal(
143
+ byId.get('heuristics.ios.maintainability.nested-if-pyramid.ast')?.then.code,
144
+ 'HEURISTICS_IOS_MAINTAINABILITY_NESTED_IF_PYRAMID_AST'
145
+ );
146
+ assert.equal(
147
+ byId.get('heuristics.ios.maintainability.comment-trivia.ast')?.then.code,
148
+ 'HEURISTICS_IOS_MAINTAINABILITY_COMMENT_TRIVIA_AST'
149
+ );
117
150
  assert.equal(
118
151
  byId.get('heuristics.ios.swiftui.onchange-task.ast')?.then.code,
119
152
  'HEURISTICS_IOS_SWIFTUI_ONCHANGE_TASK_AST'
120
153
  );
154
+ assert.equal(
155
+ byId.get('heuristics.ios.swiftui.onchange-readonly-var.ast')?.then.code,
156
+ 'HEURISTICS_IOS_SWIFTUI_ONCHANGE_READONLY_VAR_AST'
157
+ );
121
158
  assert.equal(
122
159
  byId.get('heuristics.ios.logging.adhoc-print.ast')?.then.code,
123
160
  'HEURISTICS_IOS_LOGGING_ADHOC_PRINT_AST'
@@ -154,6 +191,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
154
191
  byId.get('heuristics.ios.localization.localizable-strings.ast')?.then.code,
155
192
  'HEURISTICS_IOS_LOCALIZATION_LOCALIZABLE_STRINGS_AST'
156
193
  );
194
+ assert.equal(
195
+ byId.get('heuristics.ios.interface-builder.storyboard-xib.ast')?.then.code,
196
+ 'HEURISTICS_IOS_INTERFACE_BUILDER_STORYBOARD_XIB_AST'
197
+ );
157
198
  assert.equal(
158
199
  byId.get('heuristics.ios.localization.hardcoded-ui-string.ast')?.then.code,
159
200
  'HEURISTICS_IOS_LOCALIZATION_HARDCODED_UI_STRING_AST'
@@ -182,6 +223,14 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
182
223
  byId.get('heuristics.ios.accessibility.icon-only-control-label.ast')?.then.code,
183
224
  'HEURISTICS_IOS_ACCESSIBILITY_ICON_ONLY_CONTROL_LABEL_AST'
184
225
  );
226
+ assert.equal(
227
+ byId.get('heuristics.ios.accessibility.missing-accessibility-identifier.ast')?.then.code,
228
+ 'HEURISTICS_IOS_ACCESSIBILITY_MISSING_ACCESSIBILITY_IDENTIFIER_AST'
229
+ );
230
+ assert.equal(
231
+ byId.get('heuristics.ios.swiftui.missing-bindable-observable-binding.ast')?.then.code,
232
+ 'HEURISTICS_IOS_SWIFTUI_MISSING_BINDABLE_OBSERVABLE_BINDING_AST'
233
+ );
185
234
  assert.equal(
186
235
  byId.get('heuristics.ios.preconcurrency.ast')?.then.code,
187
236
  'HEURISTICS_IOS_PRECONCURRENCY_AST'
@@ -198,6 +247,10 @@ test('iosRules define reglas heurísticas locked para plataforma ios', () => {
198
247
  byId.get('heuristics.ios.legacy-swiftui-observable-wrapper.ast')?.then.code,
199
248
  'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST'
200
249
  );
250
+ assert.equal(
251
+ byId.get('heuristics.ios.testing.production-test-double.ast')?.then.code,
252
+ 'HEURISTICS_IOS_TESTING_PRODUCTION_TEST_DOUBLE_AST'
253
+ );
201
254
  assert.equal(
202
255
  byId.get('heuristics.ios.passed-value-state-wrapper.ast')?.then.code,
203
256
  'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST'