pumuki 6.3.270 → 6.3.272
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/CHANGELOG.md +8 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/android.test.ts +538 -0
- package/core/facts/detectors/text/android.ts +436 -0
- package/core/facts/detectors/text/ios.test.ts +328 -1
- package/core/facts/detectors/text/ios.ts +241 -0
- package/core/facts/detectors/typescript/index.test.ts +393 -0
- package/core/facts/detectors/typescript/index.ts +316 -0
- package/core/facts/extractHeuristicFacts.ts +70 -1
- package/core/rules/presets/heuristics/android.test.ts +91 -1
- package/core/rules/presets/heuristics/android.ts +360 -0
- package/core/rules/presets/heuristics/ios.test.ts +54 -1
- package/core/rules/presets/heuristics/ios.ts +243 -2
- package/core/rules/presets/heuristics/typescript.test.ts +50 -2
- package/core/rules/presets/heuristics/typescript.ts +162 -0
- package/docs/operations/RELEASE_NOTES.md +8 -0
- package/integrations/config/skillsDetectorRegistry.ts +501 -0
- package/integrations/config/skillsRuleClassification.ts +127 -3
- package/integrations/git/runPlatformGate.ts +4 -1
- package/integrations/lifecycle/preWriteAutomation.ts +5 -4
- package/integrations/lifecycle/preWriteLease.ts +41 -4
- package/package.json +1 -1
- package/scripts/classify-skills-rules.ts +2 -2
- package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
- package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
- package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
- package/scripts/framework-menu-gate-lib.ts +86 -1
- package/scripts/framework-menu-layout-data.ts +3 -3
- package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
- package/scripts/framework-menu.ts +10 -6
- package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
- package/scripts/package-install-smoke-lifecycle-lib.ts +19 -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,
|
|
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,
|
|
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'
|