pumuki 6.3.344 → 6.3.345
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/core/facts/detectors/text/android.test.ts +34 -0
- package/core/facts/detectors/text/android.ts +33 -0
- package/core/facts/extractHeuristicFacts.ts +1 -0
- package/core/rules/presets/heuristics/android.test.ts +6 -1
- package/core/rules/presets/heuristics/android.ts +20 -0
- package/integrations/config/skillsDetectorRegistry.ts +4 -0
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
hasKotlinGlobalScopeUsage,
|
|
13
13
|
hasKotlinGodActivityUsage,
|
|
14
14
|
hasKotlinHardcodedBackgroundDispatcherUsage,
|
|
15
|
+
hasKotlinApplicationOnCreateHeavyInitializationUsage,
|
|
15
16
|
hasKotlinIncompleteMaterialThemeUsage,
|
|
16
17
|
hasKotlinJUnit4Usage,
|
|
17
18
|
hasKotlinHardcodedUiStringUsage,
|
|
@@ -185,6 +186,39 @@ class MainActivity : ComponentActivity() {
|
|
|
185
186
|
assert.equal(hasKotlinGodActivityUsage(source), false);
|
|
186
187
|
});
|
|
187
188
|
|
|
189
|
+
test('hasKotlinApplicationOnCreateHeavyInitializationUsage detecta inicializacion pesada en Application.onCreate', () => {
|
|
190
|
+
const source = `
|
|
191
|
+
class RuralGoApplication : Application() {
|
|
192
|
+
override fun onCreate() {
|
|
193
|
+
super.onCreate()
|
|
194
|
+
FirebaseApp.initializeApp(this)
|
|
195
|
+
WorkManager.initialize(this, configuration)
|
|
196
|
+
Room.databaseBuilder(this, AppDatabase::class.java, "app.db").build()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
assert.equal(hasKotlinApplicationOnCreateHeavyInitializationUsage(source), true);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('hasKotlinApplicationOnCreateHeavyInitializationUsage permite Application delgada e Initializer dedicado', () => {
|
|
204
|
+
const thinApplication = `
|
|
205
|
+
class RuralGoApplication : Application() {
|
|
206
|
+
override fun onCreate() {
|
|
207
|
+
super.onCreate()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
`;
|
|
211
|
+
const startupInitializer = `
|
|
212
|
+
class AnalyticsInitializer : Initializer<FirebaseApp> {
|
|
213
|
+
override fun create(context: Context): FirebaseApp {
|
|
214
|
+
return FirebaseApp.initializeApp(context)!!
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
218
|
+
assert.equal(hasKotlinApplicationOnCreateHeavyInitializationUsage(thinApplication), false);
|
|
219
|
+
assert.equal(hasKotlinApplicationOnCreateHeavyInitializationUsage(startupInitializer), false);
|
|
220
|
+
});
|
|
221
|
+
|
|
188
222
|
test('hasKotlinNonLazyScrollableCollectionUsage detecta Column scrollable con iteracion de coleccion', () => {
|
|
189
223
|
const source = `
|
|
190
224
|
@Composable
|
|
@@ -465,6 +465,39 @@ export const hasKotlinGodActivityUsage = (source: string): boolean => {
|
|
|
465
465
|
});
|
|
466
466
|
};
|
|
467
467
|
|
|
468
|
+
export const hasKotlinApplicationOnCreateHeavyInitializationUsage = (source: string): boolean => {
|
|
469
|
+
const lines = source.split(/\r?\n/);
|
|
470
|
+
const declarations = parseKotlinTypeDeclarations(source);
|
|
471
|
+
|
|
472
|
+
return declarations.some((declaration) => {
|
|
473
|
+
const isApplication = declaration.conformances.some((conformance) =>
|
|
474
|
+
conformance === 'Application'
|
|
475
|
+
);
|
|
476
|
+
if (!isApplication) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const body = lines
|
|
481
|
+
.slice(declaration.bodyStartLine - 1, declaration.bodyEndLine)
|
|
482
|
+
.map((line) => stripKotlinLineForSemanticScan(line))
|
|
483
|
+
.filter((line) => !line.trimStart().startsWith('import '))
|
|
484
|
+
.join('\n');
|
|
485
|
+
|
|
486
|
+
const onCreateMatch = /\boverride\s+fun\s+onCreate\s*\([^)]*\)\s*\{/g.exec(body);
|
|
487
|
+
if (!onCreateMatch) {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const openBraceIndex = body.indexOf('{', onCreateMatch.index);
|
|
492
|
+
const onCreateBody = extractBalancedBlockSegment(body, openBraceIndex);
|
|
493
|
+
if (!onCreateBody) {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return /\b(?:FirebaseApp|WorkManager|Room|Retrofit|OkHttpClient|Glide|Coil|Timber)\s*(?:\.|Builder\s*\(|\()/.test(onCreateBody);
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
|
|
468
501
|
export const hasKotlinNonLazyScrollableCollectionUsage = (source: string): boolean => {
|
|
469
502
|
const sanitizedSource = source
|
|
470
503
|
.split(/\r?\n/)
|
|
@@ -894,6 +894,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
|
|
|
894
894
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinRunBlockingUsage, ruleId: 'heuristics.android.run-blocking.ast', code: 'HEURISTICS_ANDROID_RUN_BLOCKING_AST', message: 'AST heuristic detected runBlocking usage in production Kotlin code.' },
|
|
895
895
|
{ platform: 'android', pathCheck: isAndroidSourcePath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasAndroidAsyncTaskUsage, ruleId: 'heuristics.android.concurrency.asynctask.ast', code: 'HEURISTICS_ANDROID_CONCURRENCY_ASYNCTASK_AST', message: 'AST heuristic detected deprecated AsyncTask usage in Android production code; use coroutines.' },
|
|
896
896
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinGodActivityUsage, ruleId: 'heuristics.android.architecture.god-activity.ast', code: 'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST', message: 'AST heuristic detected an Android Activity mixing UI entrypoint with product responsibilities; keep Activity thin and move features to composables/ViewModels/use cases.' },
|
|
897
|
+
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinApplicationOnCreateHeavyInitializationUsage, ruleId: 'heuristics.android.startup.application-oncreate-heavy-init.ast', code: 'HEURISTICS_ANDROID_STARTUP_APPLICATION_ONCREATE_HEAVY_INIT_AST', message: 'AST heuristic detected heavy library initialization in Application.onCreate; move lazy startup work to AndroidX Startup Initializer or defer it behind the feature boundary.' },
|
|
897
898
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinNonLazyScrollableCollectionUsage, ruleId: 'heuristics.android.compose.non-lazy-scrollable-collection.ast', code: 'HEURISTICS_ANDROID_COMPOSE_NON_LAZY_SCROLLABLE_COLLECTION_AST', message: 'AST heuristic detected a scrollable Column/Row rendering a collection; use LazyColumn/LazyRow for virtualized lists.' },
|
|
898
899
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinUnstableLaunchedEffectKeyUsage, ruleId: 'heuristics.android.compose.unstable-launched-effect-key.ast', code: 'HEURISTICS_ANDROID_COMPOSE_UNSTABLE_LAUNCHED_EFFECT_KEY_AST', message: 'AST heuristic detected LaunchedEffect without a stable state key; use a meaningful key that controls when the effect restarts.' },
|
|
899
900
|
{ platform: 'android', pathCheck: isAndroidKotlinPath, excludePaths: [isKotlinTestPath], detect: TextAndroid.hasKotlinLaunchedEffectBusyLoopUsage, ruleId: 'heuristics.android.compose.launched-effect-busy-loop.ast', code: 'HEURISTICS_ANDROID_COMPOSE_LAUNCHED_EFFECT_BUSY_LOOP_AST', message: 'AST heuristic detected non-cooperative loop inside LaunchedEffect; add suspension/cancellation cooperation or move work to lifecycle-aware flow.' },
|
|
@@ -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, 39);
|
|
7
7
|
|
|
8
8
|
const ids = androidRules.map((rule) => rule.id);
|
|
9
9
|
assert.deepEqual(ids, [
|
|
@@ -15,6 +15,7 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
15
15
|
'heuristics.android.flow.sharedflow-used-as-state.ast',
|
|
16
16
|
'heuristics.android.concurrency.asynctask.ast',
|
|
17
17
|
'heuristics.android.architecture.god-activity.ast',
|
|
18
|
+
'heuristics.android.startup.application-oncreate-heavy-init.ast',
|
|
18
19
|
'heuristics.android.compose.non-lazy-scrollable-collection.ast',
|
|
19
20
|
'heuristics.android.compose.unstable-launched-effect-key.ast',
|
|
20
21
|
'heuristics.android.compose.launched-effect-busy-loop.ast',
|
|
@@ -90,6 +91,10 @@ test('androidRules define reglas heurísticas locked para plataforma android', (
|
|
|
90
91
|
byId.get('heuristics.android.architecture.god-activity.ast')?.then.code,
|
|
91
92
|
'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST'
|
|
92
93
|
);
|
|
94
|
+
assert.equal(
|
|
95
|
+
byId.get('heuristics.android.startup.application-oncreate-heavy-init.ast')?.then.code,
|
|
96
|
+
'HEURISTICS_ANDROID_STARTUP_APPLICATION_ONCREATE_HEAVY_INIT_AST'
|
|
97
|
+
);
|
|
93
98
|
assert.equal(
|
|
94
99
|
byId.get('heuristics.android.compose.non-lazy-scrollable-collection.ast')?.then.code,
|
|
95
100
|
'HEURISTICS_ANDROID_COMPOSE_NON_LAZY_SCROLLABLE_COLLECTION_AST'
|
|
@@ -155,6 +155,26 @@ export const androidRules: RuleSet = [
|
|
|
155
155
|
code: 'HEURISTICS_ANDROID_ARCHITECTURE_GOD_ACTIVITY_AST',
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
id: 'heuristics.android.startup.application-oncreate-heavy-init.ast',
|
|
160
|
+
description:
|
|
161
|
+
'Detects heavy library initialization inside Android Application.onCreate where AndroidX Startup Initializer or lazy feature initialization should be used.',
|
|
162
|
+
severity: 'WARN',
|
|
163
|
+
platform: 'android',
|
|
164
|
+
locked: true,
|
|
165
|
+
when: {
|
|
166
|
+
kind: 'Heuristic',
|
|
167
|
+
where: {
|
|
168
|
+
ruleId: 'heuristics.android.startup.application-oncreate-heavy-init.ast',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
then: {
|
|
172
|
+
kind: 'Finding',
|
|
173
|
+
message:
|
|
174
|
+
'AST heuristic detected heavy initialization inside Application.onCreate.',
|
|
175
|
+
code: 'HEURISTICS_ANDROID_STARTUP_APPLICATION_ONCREATE_HEAVY_INIT_AST',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
158
178
|
{
|
|
159
179
|
id: 'heuristics.android.compose.non-lazy-scrollable-collection.ast',
|
|
160
180
|
description:
|
|
@@ -1304,6 +1304,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
1304
1304
|
'android.architecture.god-activity',
|
|
1305
1305
|
['heuristics.android.architecture.god-activity.ast']
|
|
1306
1306
|
),
|
|
1307
|
+
'skills.android.guideline.android.app-startup-androidx-startup-para-lazy-init': heuristicDetector(
|
|
1308
|
+
'android.startup.application-oncreate-heavy-init',
|
|
1309
|
+
['heuristics.android.startup.application-oncreate-heavy-init.ast']
|
|
1310
|
+
),
|
|
1307
1311
|
'skills.android.guideline.android.lazycolumn-lazyrow-virtualizacio-n-de-listas': heuristicDetector(
|
|
1308
1312
|
'android.compose.lazy-collection',
|
|
1309
1313
|
['heuristics.android.compose.non-lazy-scrollable-collection.ast']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.345",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|