pumuki-ast-hooks 5.3.18 → 5.3.20
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/docs/VIOLATIONS_RESOLUTION_PLAN.md +39 -37
- package/package.json +8 -2
- package/scripts/hooks-system/application/CompositionRoot.js +24 -73
- package/scripts/hooks-system/application/services/AutonomousOrchestrator.js +18 -0
- package/scripts/hooks-system/application/services/ContextDetectionEngine.js +58 -0
- package/scripts/hooks-system/application/services/DynamicRulesLoader.js +12 -2
- package/scripts/hooks-system/application/services/GitFlowService.js +80 -0
- package/scripts/hooks-system/application/services/GitTreeState.js +143 -13
- package/scripts/hooks-system/application/services/HookSystemScheduler.js +47 -0
- package/scripts/hooks-system/application/services/IntelligentCommitAnalyzer.js +25 -0
- package/scripts/hooks-system/application/services/IntelligentGitTreeMonitor.js +11 -0
- package/scripts/hooks-system/application/services/PlatformAnalysisService.js +19 -0
- package/scripts/hooks-system/application/services/PlatformDetectionService.js +19 -0
- package/scripts/hooks-system/application/services/PlaybookRunner.js +22 -1
- package/scripts/hooks-system/application/services/PredictiveHookAdvisor.js +19 -0
- package/scripts/hooks-system/application/services/RealtimeGuardPlugin.js +25 -0
- package/scripts/hooks-system/application/services/RealtimeGuardService.js +41 -84
- package/scripts/hooks-system/application/services/SmartDirtyTreeAnalyzer.js +11 -0
- package/scripts/hooks-system/application/services/commit/CommitMessageGenerator.js +11 -0
- package/scripts/hooks-system/application/services/commit/FeatureDetector.js +11 -0
- package/scripts/hooks-system/application/services/evidence/EvidenceContextManager.js +25 -0
- package/scripts/hooks-system/application/services/guard/GuardAutoManagerService.js +21 -31
- package/scripts/hooks-system/application/services/guard/GuardConfig.js +18 -15
- package/scripts/hooks-system/application/services/guard/GuardEventLogger.js +11 -0
- package/scripts/hooks-system/application/services/guard/GuardHealthReminder.js +26 -0
- package/scripts/hooks-system/application/services/guard/GuardHeartbeatMonitor.js +20 -6
- package/scripts/hooks-system/application/services/guard/GuardLockManager.js +11 -0
- package/scripts/hooks-system/application/services/guard/GuardMonitorLoop.js +25 -0
- package/scripts/hooks-system/application/services/guard/GuardNotificationHandler.js +11 -0
- package/scripts/hooks-system/application/services/guard/GuardProcessManager.js +10 -28
- package/scripts/hooks-system/application/services/guard/GuardRecoveryService.js +11 -0
- package/scripts/hooks-system/application/services/installation/ConfigurationGeneratorService.js +18 -0
- package/scripts/hooks-system/application/services/installation/FileSystemInstallerService.js +18 -0
- package/scripts/hooks-system/application/services/installation/GitEnvironmentService.js +18 -3
- package/scripts/hooks-system/application/services/installation/HookInstaller.js +19 -0
- package/scripts/hooks-system/application/services/installation/IdeIntegrationService.js +11 -0
- package/scripts/hooks-system/application/services/installation/InstallService.js +25 -1
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +19 -2
- package/scripts/hooks-system/application/services/installation/PlatformDetectorService.js +11 -0
- package/scripts/hooks-system/application/services/installation/VSCodeTaskConfigurator.js +11 -0
- package/scripts/hooks-system/application/services/logging/AuditLogger.js +90 -1
- package/scripts/hooks-system/application/services/logging/UnifiedLogger.js +15 -13
- package/scripts/hooks-system/application/services/monitoring/ActivityMonitor.js +33 -0
- package/scripts/hooks-system/application/services/monitoring/AstMonitor.js +27 -0
- package/scripts/hooks-system/application/services/monitoring/DevDocsMonitor.js +26 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitor.js +19 -0
- package/scripts/hooks-system/application/services/monitoring/EvidenceMonitorService.js +27 -6
- package/scripts/hooks-system/application/services/monitoring/GitTreeMonitor.js +28 -0
- package/scripts/hooks-system/application/services/monitoring/GitTreeMonitorService.js +26 -0
- package/scripts/hooks-system/application/services/monitoring/HealthCheckProviders.js +4 -0
- package/scripts/hooks-system/application/services/monitoring/HealthCheckService.js +25 -0
- package/scripts/hooks-system/application/services/monitoring/HeartbeatMonitorService.js +26 -0
- package/scripts/hooks-system/application/services/monitoring/TokenMonitor.js +26 -0
- package/scripts/hooks-system/application/services/notification/MacNotificationSender.js +11 -0
- package/scripts/hooks-system/application/services/notification/NotificationCenterService.js +18 -0
- package/scripts/hooks-system/application/services/notification/NotificationDispatcher.js +11 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationCooldownManager.js +18 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationDeduplicator.js +18 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationQueue.js +11 -0
- package/scripts/hooks-system/application/services/notification/components/NotificationRetryExecutor.js +20 -0
- package/scripts/hooks-system/application/services/platform/PlatformHeuristics.js +19 -0
- package/scripts/hooks-system/application/services/recovery/AutoRecoveryManager.js +19 -0
- package/scripts/hooks-system/application/services/smart-commit/CommitMessageSuggester.js +11 -0
- package/scripts/hooks-system/application/services/smart-commit/FileContextGrouper.js +19 -0
- package/scripts/hooks-system/application/services/smart-commit/SmartCommitSummaryBuilder.js +4 -0
- package/scripts/hooks-system/application/services/token/CursorTokenService.js +20 -0
- package/scripts/hooks-system/application/services/token/TokenMetricsService.js +11 -13
- package/scripts/hooks-system/application/services/token/TokenMonitorService.js +19 -0
- package/scripts/hooks-system/application/services/token/TokenStatusReporter.js +12 -0
- package/scripts/hooks-system/bin/__tests__/evidence-update.spec.js +49 -0
- package/scripts/hooks-system/bin/cli.js +1 -15
- package/scripts/hooks-system/config/project.config.json +1 -1
- package/scripts/hooks-system/domain/events/index.js +24 -31
- package/scripts/hooks-system/domain/exceptions/index.js +87 -0
- package/scripts/hooks-system/infrastructure/ast/android/analyzers/AndroidAnalysisOrchestrator.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/ast-core.js +20 -12
- package/scripts/hooks-system/infrastructure/ast/ast-intelligence.js +18 -8
- package/scripts/hooks-system/infrastructure/ast/backend/analyzers/BackendPatternDetector.js +1 -2
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +14 -18
- package/scripts/hooks-system/infrastructure/ast/frontend/ast-frontend.js +196 -196
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/__tests__/iOSASTIntelligentAnalyzer.spec.js +66 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSASTIntelligentAnalyzer.js +2 -3
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSArchitectureRules.js +24 -86
- package/scripts/hooks-system/infrastructure/config/config.js +5 -0
- package/scripts/hooks-system/infrastructure/hooks/skill-activation-prompt.js +2 -3
- package/scripts/hooks-system/infrastructure/logging/UnifiedLoggerFactory.js +5 -35
- package/scripts/hooks-system/infrastructure/orchestration/intelligent-audit.js +16 -86
- package/scripts/hooks-system/infrastructure/shell/orchestrators/audit-orchestrator.sh +54 -92
- package/scripts/hooks-system/infrastructure/telemetry/metric-scope.js +98 -0
- package/scripts/hooks-system/infrastructure/telemetry/metrics-server.js +2 -51
- package/scripts/hooks-system/infrastructure/validators/enforce-english-literals.js +8 -6
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const env = require('../../../config/env');
|
|
3
2
|
const {
|
|
4
|
-
platformOf,
|
|
5
3
|
pushFinding,
|
|
6
|
-
pushFileFinding,
|
|
7
4
|
mapToLevel,
|
|
8
|
-
positionOf,
|
|
9
|
-
isTestFile,
|
|
10
5
|
SyntaxKind,
|
|
6
|
+
isTestFile,
|
|
7
|
+
platformOf,
|
|
11
8
|
hasImport,
|
|
12
9
|
hasDecorator,
|
|
13
10
|
findStringLiterals,
|
|
@@ -121,7 +118,7 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
121
118
|
const filePath = sf.getFilePath();
|
|
122
119
|
if (platformOf(filePath) !== 'backend') return;
|
|
123
120
|
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
124
|
-
if (
|
|
121
|
+
if (process.env.AUDIT_LIBRARY !== 'true') {
|
|
125
122
|
if (/scripts\/hooks-system\/infrastructure\/ast\//i.test(filePath) || /\/infrastructure\/ast\//i.test(filePath)) return;
|
|
126
123
|
}
|
|
127
124
|
if (isTestFile(filePath)) return;
|
|
@@ -146,8 +143,8 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
146
143
|
|
|
147
144
|
if (metrics.length === 0) return null;
|
|
148
145
|
|
|
149
|
-
const pOutlier = env.
|
|
150
|
-
const pExtreme = env.
|
|
146
|
+
const pOutlier = Number(process.env.AST_GODCLASS_P_OUTLIER || 90);
|
|
147
|
+
const pExtreme = Number(process.env.AST_GODCLASS_P_EXTREME || 97);
|
|
151
148
|
|
|
152
149
|
const methods = metrics.map(m => m.methodsCount);
|
|
153
150
|
const props = metrics.map(m => m.propertiesCount);
|
|
@@ -204,12 +201,12 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
204
201
|
if (platformOf(filePath) !== "backend") return;
|
|
205
202
|
|
|
206
203
|
if (/\/ast-[^/]+\.js$/.test(filePath)) return;
|
|
207
|
-
if (
|
|
204
|
+
if (process.env.AUDIT_LIBRARY !== 'true') {
|
|
208
205
|
if (/scripts\/hooks-system\/infrastructure\/ast\//i.test(filePath) || /\/infrastructure\/ast\//i.test(filePath)) return;
|
|
209
206
|
}
|
|
210
207
|
|
|
211
208
|
const fullText = sf.getFullText();
|
|
212
|
-
const insightsEnabled = env.
|
|
209
|
+
const insightsEnabled = process.env.AST_INSIGHTS === '1';
|
|
213
210
|
const isSpecFile = /\.(spec|test)\.(ts|tsx|js|jsx)$/.test(filePath);
|
|
214
211
|
const secretPattern = /(password|secret|key|token)\s*[:=]\s*['"`]([^'"]{8,})['"`]/gi;
|
|
215
212
|
const matches = Array.from(fullText.matchAll(secretPattern));
|
|
@@ -369,11 +366,12 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
369
366
|
}
|
|
370
367
|
});
|
|
371
368
|
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
369
|
+
// Removed controller-level CORS check because global CORS is enabled
|
|
370
|
+
// const hasCors = sf.getFullText().includes("cors") || sf.getFullText().includes("CORS") || sf.getFullText().includes("@CrossOrigin");
|
|
371
|
+
// const missingCorsSeverity = hasGlobalCors ? "low" : "high";
|
|
372
|
+
// if (!hasCors && (sf.getFullText().includes("controller") || sf.getFullText().includes("Controller"))) {
|
|
373
|
+
// pushFinding("backend.auth.missing_cors", missingCorsSeverity, sf, sf, "Missing CORS configuration in controller - consider @CrossOrigin or global CORS config", findings);
|
|
374
|
+
// }
|
|
377
375
|
|
|
378
376
|
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
379
377
|
const expr = call.getExpression();
|
|
@@ -425,9 +423,7 @@ function runBackendIntelligence(project, findings, platform) {
|
|
|
425
423
|
|
|
426
424
|
if (isTestFile(filePath)) {
|
|
427
425
|
sf.getDescendantsOfKind(SyntaxKind.CallExpression).forEach((call) => {
|
|
428
|
-
const
|
|
429
|
-
if (!expr) return;
|
|
430
|
-
const exprText = expr.getText();
|
|
426
|
+
const exprText = call.getExpression().getText();
|
|
431
427
|
if (/Thread\.sleep|await|delay/.test(exprText)) {
|
|
432
428
|
pushFinding("backend.testing.slow_tests", "medium", sf, call, "Test with sleep/delay detected - slow tests impact CI/CD performance", findings);
|
|
433
429
|
}
|
|
@@ -57,8 +57,8 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
57
57
|
return 0.6745 * (x - med) / madValue;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
const pOutlier = env.
|
|
61
|
-
const pExtreme = env.
|
|
60
|
+
const pOutlier = Number(process.env.AST_GODCLASS_P_OUTLIER || 90);
|
|
61
|
+
const pExtreme = Number(process.env.AST_GODCLASS_P_EXTREME || 97);
|
|
62
62
|
|
|
63
63
|
const componentPattern = /^(export\s+)?(?:const|function)\s+([A-Z]\w+)\s*[=:].*(?:React\.FC|JSX\.Element|\(\)\s*=>|function)/gm;
|
|
64
64
|
const metrics = [];
|
|
@@ -1129,10 +1129,10 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1129
1129
|
}
|
|
1130
1130
|
if (/page\.tsx$/.test(filePath)) {
|
|
1131
1131
|
const candidate = filePath.replace(/\/app\//, '/e2e/').replace(/\.tsx$/, '.spec.ts');
|
|
1132
|
-
try {
|
|
1133
|
-
if (!fs.existsSync(candidate)) {
|
|
1134
|
-
pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
|
|
1135
|
-
}
|
|
1132
|
+
try {
|
|
1133
|
+
if (!fs.existsSync(candidate)) {
|
|
1134
|
+
pushFinding("frontend.testing.missing_e2e", "info", sf, sf, "No E2E spec found for this page (heuristic)", findings);
|
|
1135
|
+
}
|
|
1136
1136
|
} catch (error) {
|
|
1137
1137
|
if (process.env.DEBUG) {
|
|
1138
1138
|
console.debug(`[frontend-ast] Failed to check E2E spec file: ${error.message}`);
|
|
@@ -1181,14 +1181,14 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1181
1181
|
const extreme = totalComplexityZ >= godComponentBaseline.thresholds.extreme.totalComplexityZ || bodyLinesZ >= godComponentBaseline.thresholds.extreme.bodyLinesZ;
|
|
1182
1182
|
|
|
1183
1183
|
if (extreme || (complexityOutlier && sizeOutlier)) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1184
|
+
pushFinding(
|
|
1185
|
+
"frontend.solid.srp_god_component",
|
|
1186
|
+
"critical",
|
|
1187
|
+
sf,
|
|
1188
|
+
sf,
|
|
1189
|
+
`Component '${componentName}' has ${hookCount} hooks + ${functionCount} functions = ${totalComplexity} (z=${totalComplexityZ.toFixed(2)}), ${bodyLines} lines (z=${bodyLinesZ.toFixed(2)}) - split responsibilities (SRP)`,
|
|
1190
|
+
findings
|
|
1191
|
+
);
|
|
1192
1192
|
}
|
|
1193
1193
|
}
|
|
1194
1194
|
});
|
|
@@ -1823,269 +1823,269 @@ function runFrontendIntelligence(project, findings, platform) {
|
|
|
1823
1823
|
}
|
|
1824
1824
|
|
|
1825
1825
|
const useStateCallsInComponent = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1829
|
-
const name = fn.getName();
|
|
1830
|
-
if (name && /^[A-Z]/.test(name)) {
|
|
1831
|
-
const useStatesInFn = useStateCallsInComponent.filter(call =>
|
|
1832
|
-
fn.getDescendants().includes(call)
|
|
1833
|
-
);
|
|
1834
|
-
|
|
1835
|
-
if (useStatesInFn.length >= 4) {
|
|
1836
|
-
pushFinding(
|
|
1837
|
-
"frontend.hooks.useState_overuse",
|
|
1838
|
-
"medium",
|
|
1839
|
-
sf,
|
|
1840
|
-
fn,
|
|
1841
|
-
`Component ${name} has ${useStatesInFn.length}+ useState. Consider useReducer for complex state. Benefits: Single state object, predictable updates, easier testing.`,
|
|
1842
|
-
findings
|
|
1843
|
-
);
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
|
|
1848
|
-
const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1849
|
-
.filter(call => call.getExpression().getText() === 'createContext');
|
|
1826
|
+
.filter(call => call.getExpression().getText() === 'useState');
|
|
1850
1827
|
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
sf,
|
|
1857
|
-
`Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
|
|
1858
|
-
findings
|
|
1828
|
+
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1829
|
+
const name = fn.getName();
|
|
1830
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
1831
|
+
const useStatesInFn = useStateCallsInComponent.filter(call =>
|
|
1832
|
+
fn.getDescendants().includes(call)
|
|
1859
1833
|
);
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1863
|
-
const name = fn.getName();
|
|
1864
|
-
const body = fn.getBody()?.getText() || '';
|
|
1865
|
-
const hasHookCall = /use[A-Z]/.test(body);
|
|
1866
1834
|
|
|
1867
|
-
if (
|
|
1835
|
+
if (useStatesInFn.length >= 4) {
|
|
1868
1836
|
pushFinding(
|
|
1869
|
-
"frontend.hooks.
|
|
1837
|
+
"frontend.hooks.useState_overuse",
|
|
1870
1838
|
"medium",
|
|
1871
1839
|
sf,
|
|
1872
1840
|
fn,
|
|
1873
|
-
`
|
|
1841
|
+
`Component ${name} has ${useStatesInFn.length}+ useState. Consider useReducer for complex state. Benefits: Single state object, predictable updates, easier testing.`,
|
|
1874
1842
|
findings
|
|
1875
1843
|
);
|
|
1876
1844
|
}
|
|
1877
|
-
}
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
const contextCreations = sf.getDescendantsOfKind(SyntaxKind.CallExpression)
|
|
1849
|
+
.filter(call => call.getExpression().getText() === 'createContext');
|
|
1850
|
+
|
|
1851
|
+
if (contextCreations.length >= 3 && !sf.getFilePath().includes('context')) {
|
|
1852
|
+
pushFinding(
|
|
1853
|
+
"frontend.state.context_overuse",
|
|
1854
|
+
"medium",
|
|
1855
|
+
sf,
|
|
1856
|
+
sf,
|
|
1857
|
+
`Multiple Context creations (${contextCreations.length}). Consider Zustand for global state. Context rerenders all consumers. Zustand: Selective subscriptions, better performance.`,
|
|
1858
|
+
findings
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1878
1861
|
|
|
1879
|
-
|
|
1880
|
-
const
|
|
1881
|
-
|
|
1862
|
+
sf.getDescendantsOfKind(SyntaxKind.FunctionDeclaration).forEach(fn => {
|
|
1863
|
+
const name = fn.getName();
|
|
1864
|
+
const body = fn.getBody()?.getText() || '';
|
|
1865
|
+
const hasHookCall = /use[A-Z]/.test(body);
|
|
1882
1866
|
|
|
1883
|
-
if (
|
|
1867
|
+
if (name && hasHookCall && !/^use[A-Z]/.test(name)) {
|
|
1884
1868
|
pushFinding(
|
|
1885
|
-
"frontend.
|
|
1869
|
+
"frontend.hooks.naming_convention",
|
|
1886
1870
|
"medium",
|
|
1887
1871
|
sf,
|
|
1888
|
-
|
|
1889
|
-
|
|
1872
|
+
fn,
|
|
1873
|
+
`Function ${name} uses hooks but doesn't start with 'use'. Rename to use${name[0].toUpperCase()}${name.slice(1)}. React ESLint requires 'use' prefix for hook functions.`,
|
|
1890
1874
|
findings
|
|
1891
1875
|
);
|
|
1892
1876
|
}
|
|
1877
|
+
});
|
|
1893
1878
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1879
|
+
const fullText = sf.getFullText();
|
|
1880
|
+
const hasLargeImports = fullText.includes('import * as') ||
|
|
1881
|
+
fullText.match(/import\s+\{[^}]{200,}\}/);
|
|
1882
|
+
|
|
1883
|
+
if (hasLargeImports) {
|
|
1884
|
+
pushFinding(
|
|
1885
|
+
"frontend.performance.large_imports",
|
|
1886
|
+
"medium",
|
|
1887
|
+
sf,
|
|
1888
|
+
sf,
|
|
1889
|
+
'Large import detected. Use tree-shaking: import { Button } from \'@mui/material/Button\' (not from \'@mui/material\'). Reduces bundle size by importing only needed components.',
|
|
1890
|
+
findings
|
|
1891
|
+
);
|
|
1892
|
+
}
|
|
1896
1893
|
|
|
1897
|
-
|
|
1894
|
+
if (content.includes('class') && content.includes('extends Component') &&
|
|
1895
|
+
!content.includes('componentDidCatch') && !content.includes('getDerivedStateFromError')) {
|
|
1898
1896
|
|
|
1899
|
-
|
|
1900
|
-
pushFinding(
|
|
1901
|
-
"frontend.error_handling.missing_error_boundary",
|
|
1902
|
-
"medium",
|
|
1903
|
-
sf,
|
|
1904
|
-
sf,
|
|
1905
|
-
'Component renders children without error boundary. Add: componentDidCatch(error, errorInfo) { logError(error); }. Prevents whole app crash from single component error.',
|
|
1906
|
-
findings
|
|
1907
|
-
);
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
const hasRoutes = content.includes('Route') || content.includes('router');
|
|
1912
|
-
const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
|
|
1897
|
+
const hasChildrenRender = content.includes('render()') && content.includes('children');
|
|
1913
1898
|
|
|
1914
|
-
if (
|
|
1899
|
+
if (hasChildrenRender) {
|
|
1915
1900
|
pushFinding(
|
|
1916
|
-
"frontend.
|
|
1901
|
+
"frontend.error_handling.missing_error_boundary",
|
|
1917
1902
|
"medium",
|
|
1918
1903
|
sf,
|
|
1919
1904
|
sf,
|
|
1920
|
-
'
|
|
1905
|
+
'Component renders children without error boundary. Add: componentDidCatch(error, errorInfo) { logError(error); }. Prevents whole app crash from single component error.',
|
|
1921
1906
|
findings
|
|
1922
1907
|
);
|
|
1923
1908
|
}
|
|
1909
|
+
}
|
|
1924
1910
|
|
|
1911
|
+
const hasRoutes = content.includes('Route') || content.includes('router');
|
|
1912
|
+
const hasLazy = content.includes('React.lazy') || content.includes('dynamic(');
|
|
1913
|
+
|
|
1914
|
+
if (hasRoutes && !hasLazy && sf.getFilePath().includes('app')) {
|
|
1915
|
+
pushFinding(
|
|
1916
|
+
"frontend.performance.missing_lazy_loading",
|
|
1917
|
+
"medium",
|
|
1918
|
+
sf,
|
|
1919
|
+
sf,
|
|
1920
|
+
'Routes without lazy loading. Use: const Dashboard = lazy(() => import(\'./Dashboard\')). Wrap with <Suspense>. Reduces initial bundle, faster First Contentful Paint.',
|
|
1921
|
+
findings
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1925
1924
|
|
|
1926
|
-
if (sf.getFilePath().includes('layout.tsx') || sf.getFilePath().includes('page.tsx')) {
|
|
1927
|
-
const hasMetadata = content.includes('metadata') || content.includes('generateMetadata');
|
|
1928
|
-
|
|
1929
|
-
if (!hasMetadata) {
|
|
1930
|
-
pushFinding(
|
|
1931
|
-
"frontend.seo.missing_metadata",
|
|
1932
|
-
"low",
|
|
1933
|
-
sf,
|
|
1934
|
-
sf,
|
|
1935
|
-
'Missing SEO metadata. Export: export const metadata = { title, description, openGraph }. Improves Google ranking, social media previews.',
|
|
1936
|
-
findings
|
|
1937
|
-
);
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
if (sf.getFilePath().includes('sitemap')) {
|
|
1942
|
-
const hasDynamicRoutes = content.includes('fetch') || content.includes('database');
|
|
1943
|
-
|
|
1944
|
-
if (!hasDynamicRoutes && content.length < 200) {
|
|
1945
|
-
pushFinding(
|
|
1946
|
-
"frontend.seo.static_sitemap",
|
|
1947
|
-
"low",
|
|
1948
|
-
sf,
|
|
1949
|
-
sf,
|
|
1950
|
-
'Static sitemap. Generate dynamically: fetch all routes from API/DB. Update automatically when content changes. Better SEO indexing.',
|
|
1951
|
-
findings
|
|
1952
|
-
);
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
if (sf.getFilePath().includes('robots')) {
|
|
1957
|
-
const hasDisallow = content.includes('Disallow');
|
|
1958
|
-
|
|
1959
|
-
if (!hasDisallow) {
|
|
1960
|
-
pushFinding(
|
|
1961
|
-
"frontend.seo.robots_config",
|
|
1962
|
-
"low",
|
|
1963
|
-
sf,
|
|
1964
|
-
sf,
|
|
1965
|
-
'Robots.txt incomplete. Add Disallow rules for: /api, /admin, /_next. Prevents search engines indexing private routes.',
|
|
1966
|
-
findings
|
|
1967
|
-
);
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
|
-
if (sf.getFilePath().includes('manifest')) {
|
|
1972
|
-
const hasIcons = content.includes('icons');
|
|
1973
|
-
const hasThemeColor = content.includes('theme_color');
|
|
1974
1925
|
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
"frontend.pwa.manifest_incomplete",
|
|
1978
|
-
"low",
|
|
1979
|
-
sf,
|
|
1980
|
-
sf,
|
|
1981
|
-
'PWA manifest incomplete. Add: icons (192x192, 512x512), theme_color, background_color, display: standalone. Enables Add to Home Screen.',
|
|
1982
|
-
findings
|
|
1983
|
-
);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1926
|
+
if (sf.getFilePath().includes('layout.tsx') || sf.getFilePath().includes('page.tsx')) {
|
|
1927
|
+
const hasMetadata = content.includes('metadata') || content.includes('generateMetadata');
|
|
1986
1928
|
|
|
1987
|
-
if (
|
|
1929
|
+
if (!hasMetadata) {
|
|
1988
1930
|
pushFinding(
|
|
1989
|
-
"frontend.
|
|
1931
|
+
"frontend.seo.missing_metadata",
|
|
1990
1932
|
"low",
|
|
1991
1933
|
sf,
|
|
1992
1934
|
sf,
|
|
1993
|
-
'
|
|
1935
|
+
'Missing SEO metadata. Export: export const metadata = { title, description, openGraph }. Improves Google ranking, social media previews.',
|
|
1994
1936
|
findings
|
|
1995
1937
|
);
|
|
1996
1938
|
}
|
|
1939
|
+
}
|
|
1997
1940
|
|
|
1998
|
-
|
|
1999
|
-
|
|
1941
|
+
if (sf.getFilePath().includes('sitemap')) {
|
|
1942
|
+
const hasDynamicRoutes = content.includes('fetch') || content.includes('database');
|
|
2000
1943
|
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
}
|
|
1944
|
+
if (!hasDynamicRoutes && content.length < 200) {
|
|
1945
|
+
pushFinding(
|
|
1946
|
+
"frontend.seo.static_sitemap",
|
|
1947
|
+
"low",
|
|
1948
|
+
sf,
|
|
1949
|
+
sf,
|
|
1950
|
+
'Static sitemap. Generate dynamically: fetch all routes from API/DB. Update automatically when content changes. Better SEO indexing.',
|
|
1951
|
+
findings
|
|
1952
|
+
);
|
|
2011
1953
|
}
|
|
1954
|
+
}
|
|
2012
1955
|
|
|
2013
|
-
|
|
2014
|
-
const
|
|
1956
|
+
if (sf.getFilePath().includes('robots')) {
|
|
1957
|
+
const hasDisallow = content.includes('Disallow');
|
|
2015
1958
|
|
|
2016
|
-
if (
|
|
1959
|
+
if (!hasDisallow) {
|
|
2017
1960
|
pushFinding(
|
|
2018
|
-
"frontend.
|
|
1961
|
+
"frontend.seo.robots_config",
|
|
2019
1962
|
"low",
|
|
2020
1963
|
sf,
|
|
2021
1964
|
sf,
|
|
2022
|
-
'
|
|
1965
|
+
'Robots.txt incomplete. Add Disallow rules for: /api, /admin, /_next. Prevents search engines indexing private routes.',
|
|
2023
1966
|
findings
|
|
2024
1967
|
);
|
|
2025
1968
|
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
if (sf.getFilePath().includes('manifest')) {
|
|
1972
|
+
const hasIcons = content.includes('icons');
|
|
1973
|
+
const hasThemeColor = content.includes('theme_color');
|
|
2026
1974
|
|
|
2027
|
-
if (
|
|
1975
|
+
if (!hasIcons || !hasThemeColor) {
|
|
2028
1976
|
pushFinding(
|
|
2029
|
-
"frontend.
|
|
1977
|
+
"frontend.pwa.manifest_incomplete",
|
|
2030
1978
|
"low",
|
|
2031
1979
|
sf,
|
|
2032
1980
|
sf,
|
|
2033
|
-
'
|
|
1981
|
+
'PWA manifest incomplete. Add: icons (192x192, 512x512), theme_color, background_color, display: standalone. Enables Add to Home Screen.',
|
|
2034
1982
|
findings
|
|
2035
1983
|
);
|
|
2036
1984
|
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if (content.includes('serviceWorker') && !content.includes('unregister')) {
|
|
1988
|
+
pushFinding(
|
|
1989
|
+
"frontend.pwa.missing_sw_unregister",
|
|
1990
|
+
"low",
|
|
1991
|
+
sf,
|
|
1992
|
+
sf,
|
|
1993
|
+
'Service Worker registration without unregister fallback. Add: if (!production) navigator.serviceWorker.unregister(). Prevents caching issues in development.',
|
|
1994
|
+
findings
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
if (sf.getFilePath().includes('lighthouse') || sf.getFilePath().includes('performance')) {
|
|
1999
|
+
const hasThresholds = content.includes('performance') && content.includes('accessibility');
|
|
2037
2000
|
|
|
2038
|
-
if (
|
|
2001
|
+
if (!hasThresholds) {
|
|
2039
2002
|
pushFinding(
|
|
2040
|
-
"frontend.
|
|
2003
|
+
"frontend.performance.lighthouse_monitoring",
|
|
2041
2004
|
"low",
|
|
2042
2005
|
sf,
|
|
2043
2006
|
sf,
|
|
2044
|
-
'
|
|
2007
|
+
'Lighthouse config without thresholds. Set CI thresholds: performance: 90, accessibility: 95, best-practices: 90, seo: 90. Prevents performance regressions.',
|
|
2045
2008
|
findings
|
|
2046
2009
|
);
|
|
2047
2010
|
}
|
|
2011
|
+
}
|
|
2048
2012
|
|
|
2049
|
-
|
|
2050
|
-
|
|
2013
|
+
const hasWebVitals = content.includes('reportWebVitals') || content.includes('web-vitals');
|
|
2014
|
+
const hasAnalytics = content.includes('analytics') || content.includes('gtag');
|
|
2015
|
+
|
|
2016
|
+
if (hasWebVitals && !hasAnalytics) {
|
|
2017
|
+
pushFinding(
|
|
2018
|
+
"frontend.monitoring.web_vitals_no_analytics",
|
|
2019
|
+
"low",
|
|
2020
|
+
sf,
|
|
2021
|
+
sf,
|
|
2022
|
+
'Web Vitals captured but not sent to analytics. Send to Google Analytics: gtag(\'event\', metric.name, { value: metric.value }). Track LCP, FID, CLS in production.',
|
|
2023
|
+
findings
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2051
2026
|
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2027
|
+
if (sf.getFilePath().includes('layout') && !content.includes('analytics') && !content.includes('gtag')) {
|
|
2028
|
+
pushFinding(
|
|
2029
|
+
"frontend.monitoring.missing_analytics",
|
|
2030
|
+
"low",
|
|
2031
|
+
sf,
|
|
2032
|
+
sf,
|
|
2033
|
+
'Root layout without analytics. Add Google Analytics: <Script src="https://www.googletagmanager.com/gtag/js" />. Track user behavior, conversion rates.',
|
|
2034
|
+
findings
|
|
2035
|
+
);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
if (sf.getFilePath().includes('error.tsx') && !content.includes('Sentry') && !content.includes('captureException')) {
|
|
2039
|
+
pushFinding(
|
|
2040
|
+
"frontend.monitoring.missing_error_tracking",
|
|
2041
|
+
"low",
|
|
2042
|
+
sf,
|
|
2043
|
+
sf,
|
|
2044
|
+
'Error page without error tracking. Install: npm i @sentry/nextjs. Track production errors: Sentry.captureException(error). Get alerts when users hit errors.',
|
|
2045
|
+
findings
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2063
2048
|
|
|
2064
|
-
|
|
2049
|
+
if (content.includes('if (') && /feature|experiment|rollout|beta/i.test(content)) {
|
|
2050
|
+
const hasFeatureFlagLib = content.includes('unleash') || content.includes('launchdarkly') || content.includes('flagsmith');
|
|
2051
|
+
|
|
2052
|
+
if (!hasFeatureFlagLib) {
|
|
2065
2053
|
pushFinding(
|
|
2066
|
-
"frontend.devops.
|
|
2054
|
+
"frontend.devops.hardcoded_feature_flag",
|
|
2067
2055
|
"low",
|
|
2068
2056
|
sf,
|
|
2069
2057
|
sf,
|
|
2070
|
-
'
|
|
2058
|
+
'Hardcoded feature flag. Use feature flag service: Unleash, LaunchDarkly, Flagsmith. Benefits: Toggle features without deployment, gradual rollout, A/B testing.',
|
|
2071
2059
|
findings
|
|
2072
2060
|
);
|
|
2073
2061
|
}
|
|
2062
|
+
}
|
|
2074
2063
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2064
|
+
if (content.includes('variant') && content.includes('Math.random')) {
|
|
2065
|
+
pushFinding(
|
|
2066
|
+
"frontend.devops.manual_ab_testing",
|
|
2067
|
+
"low",
|
|
2068
|
+
sf,
|
|
2069
|
+
sf,
|
|
2070
|
+
'Manual A/B testing with Math.random. Use: Google Optimize, Optimizely, VWO. Benefits: Statistical significance, reporting, targeting rules.',
|
|
2071
|
+
findings
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2077
2074
|
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2075
|
+
if (content.includes('fetch') && !content.includes('cache') && !content.includes('revalidate')) {
|
|
2076
|
+
const isFetchInServerComponent = content.includes('async function') && !content.includes('\'use client\'');
|
|
2077
|
+
|
|
2078
|
+
if (isFetchInServerComponent) {
|
|
2079
|
+
pushFinding(
|
|
2080
|
+
"frontend.performance.missing_cache_strategy",
|
|
2081
|
+
"low",
|
|
2082
|
+
sf,
|
|
2083
|
+
sf,
|
|
2084
|
+
'Fetch without cache strategy. Add: { cache: \'force-cache\' } or { next: { revalidate: 3600 } }. Default behavior may change. Be explicit.',
|
|
2085
|
+
findings
|
|
2086
|
+
);
|
|
2088
2087
|
}
|
|
2088
|
+
}
|
|
2089
2089
|
});
|
|
2090
2090
|
}
|
|
2091
2091
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { iOSASTIntelligentAnalyzer } = require('../iOSASTIntelligentAnalyzer');
|
|
2
|
+
|
|
3
|
+
describe('iOSASTIntelligentAnalyzer - event-driven navigation rules', () => {
|
|
4
|
+
const makeSUT = () => {
|
|
5
|
+
const findings = [];
|
|
6
|
+
const sut = new iOSASTIntelligentAnalyzer(findings);
|
|
7
|
+
sut.fileContent = '';
|
|
8
|
+
sut.syntaxTokens = [];
|
|
9
|
+
sut.imports = [];
|
|
10
|
+
sut.classes = [];
|
|
11
|
+
sut.structs = [];
|
|
12
|
+
return { sut, findings };
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const identifierToken = (value, offset = 0) => ({
|
|
16
|
+
kind: 'source.lang.swift.syntaxtype.identifier',
|
|
17
|
+
value,
|
|
18
|
+
offset,
|
|
19
|
+
length: value.length,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should report CRITICAL when UIKit imperative navigation is detected', () => {
|
|
23
|
+
const { sut, findings } = makeSUT();
|
|
24
|
+
sut.fileContent = 'pushViewController';
|
|
25
|
+
sut.syntaxTokens = [identifierToken('pushViewController', 0)];
|
|
26
|
+
|
|
27
|
+
sut.analyzeAdditionalRules('/tmp/File.swift');
|
|
28
|
+
|
|
29
|
+
const rule = findings.find((f) => f.ruleId === 'ios.navigation.imperative_navigation');
|
|
30
|
+
expect(rule).toBeDefined();
|
|
31
|
+
expect(String(rule.severity).toLowerCase()).toBe('critical');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should report CRITICAL when SwiftUI navigation API is detected outside View types', () => {
|
|
35
|
+
const { sut, findings } = makeSUT();
|
|
36
|
+
sut.fileContent = 'NavigationLink';
|
|
37
|
+
sut.syntaxTokens = [identifierToken('NavigationLink', 0)];
|
|
38
|
+
sut.imports = [];
|
|
39
|
+
sut.classes = [];
|
|
40
|
+
sut.structs = [];
|
|
41
|
+
|
|
42
|
+
sut.analyzeAdditionalRules('/tmp/NotAView.swift');
|
|
43
|
+
|
|
44
|
+
const rule = findings.find((f) => f.ruleId === 'ios.navigation.swiftui_navigation_outside_view');
|
|
45
|
+
expect(rule).toBeDefined();
|
|
46
|
+
expect(String(rule.severity).toLowerCase()).toBe('critical');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should not report SwiftUI navigation outside View when file is a SwiftUI View', () => {
|
|
50
|
+
const { sut, findings } = makeSUT();
|
|
51
|
+
sut.fileContent = 'import SwiftUI\nstruct MyView: View { var body: some View { NavigationLink("x", destination: Text("y")) } }';
|
|
52
|
+
sut.syntaxTokens = [identifierToken('NavigationLink', 0)];
|
|
53
|
+
sut.imports = [{ name: 'SwiftUI', line: 1 }];
|
|
54
|
+
sut.structs = [
|
|
55
|
+
{
|
|
56
|
+
'key.name': 'MyView',
|
|
57
|
+
'key.inheritedtypes': [{ 'key.name': 'View' }],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
sut.analyzeAdditionalRules('/tmp/MyView.swift');
|
|
62
|
+
|
|
63
|
+
const rule = findings.find((f) => f.ruleId === 'ios.navigation.swiftui_navigation_outside_view');
|
|
64
|
+
expect(rule).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
});
|