@archpilotlabs/archpilot 0.2.2 → 0.2.3
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/dist/archpilot-cli.js +516 -105
- package/dist/cli/src/archpilot-cli.js +5523 -0
- package/dist/cli/src/archpilot-cli.js.map +1 -0
- package/dist/core/src/adr/adrLoader.js +97 -0
- package/dist/core/src/adr/adrLoader.js.map +1 -0
- package/dist/core/src/adr/adrParser.js +77 -0
- package/dist/core/src/adr/adrParser.js.map +1 -0
- package/dist/core/src/adr/adrTemplate.js +45 -0
- package/dist/core/src/adr/adrTemplate.js.map +1 -0
- package/dist/core/src/adr/adrTypes.js +3 -0
- package/dist/core/src/adr/adrTypes.js.map +1 -0
- package/dist/core/src/adr/adrValidation.js +160 -0
- package/dist/core/src/adr/adrValidation.js.map +1 -0
- package/dist/core/src/adr/index.js +9 -0
- package/dist/core/src/adr/index.js.map +1 -0
- package/dist/core/src/adrManagement.js +174 -0
- package/dist/core/src/adrManagement.js.map +1 -0
- package/dist/core/src/adrRequirementStatus.js +62 -0
- package/dist/core/src/adrRequirementStatus.js.map +1 -0
- package/dist/core/src/adrScaffoldGeneration.js +325 -0
- package/dist/core/src/adrScaffoldGeneration.js.map +1 -0
- package/dist/core/src/apiDesignAnalysis.js +869 -0
- package/dist/core/src/apiDesignAnalysis.js.map +1 -0
- package/dist/core/src/applicationLayeringAnalysis.js +920 -0
- package/dist/core/src/applicationLayeringAnalysis.js.map +1 -0
- package/dist/core/src/applyRulePack.js +117 -0
- package/dist/core/src/applyRulePack.js.map +1 -0
- package/dist/core/src/architectureBaseline.js +271 -0
- package/dist/core/src/architectureBaseline.js.map +1 -0
- package/dist/core/src/architectureComponentDiscoveryDefaults.js +139 -0
- package/dist/core/src/architectureComponentDiscoveryDefaults.js.map +1 -0
- package/dist/core/src/architectureDependencyContext.js +316 -0
- package/dist/core/src/architectureDependencyContext.js.map +1 -0
- package/dist/core/src/architectureDriftDetection.js +96 -0
- package/dist/core/src/architectureDriftDetection.js.map +1 -0
- package/dist/core/src/architectureDriftTimeline.js +221 -0
- package/dist/core/src/architectureDriftTimeline.js.map +1 -0
- package/dist/core/src/architectureGovernanceBaseline.js +469 -0
- package/dist/core/src/architectureGovernanceBaseline.js.map +1 -0
- package/dist/core/src/architectureHealthHistory.js +208 -0
- package/dist/core/src/architectureHealthHistory.js.map +1 -0
- package/dist/core/src/architectureHealthScore.js +657 -0
- package/dist/core/src/architectureHealthScore.js.map +1 -0
- package/dist/core/src/architectureInit.js +826 -0
- package/dist/core/src/architectureInit.js.map +1 -0
- package/dist/core/src/architectureMap.js +727 -0
- package/dist/core/src/architectureMap.js.map +1 -0
- package/dist/core/src/architectureMigrationPreview.js +467 -0
- package/dist/core/src/architectureMigrationPreview.js.map +1 -0
- package/dist/core/src/architectureModelRefresh.js +209 -0
- package/dist/core/src/architectureModelRefresh.js.map +1 -0
- package/dist/core/src/architectureReportWorkflow.js +139 -0
- package/dist/core/src/architectureReportWorkflow.js.map +1 -0
- package/dist/core/src/architectureValidation.js +3879 -0
- package/dist/core/src/architectureValidation.js.map +1 -0
- package/dist/core/src/architectureWorkspaceModel.js +166 -0
- package/dist/core/src/architectureWorkspaceModel.js.map +1 -0
- package/dist/core/src/authorizationTenantAnalysis.js +652 -0
- package/dist/core/src/authorizationTenantAnalysis.js.map +1 -0
- package/dist/core/src/bootstrapArchitectureConfig.js +150 -0
- package/dist/core/src/bootstrapArchitectureConfig.js.map +1 -0
- package/dist/core/src/changeRisk/changeRiskAssessment.js +251 -0
- package/dist/core/src/changeRisk/changeRiskAssessment.js.map +1 -0
- package/dist/core/src/changeRisk/changeRiskTypes.js +3 -0
- package/dist/core/src/changeRisk/changeRiskTypes.js.map +1 -0
- package/dist/core/src/changeRisk/index.js +6 -0
- package/dist/core/src/changeRisk/index.js.map +1 -0
- package/dist/core/src/ci/githubInlineAnnotations.js +245 -0
- package/dist/core/src/ci/githubInlineAnnotations.js.map +1 -0
- package/dist/core/src/ci/githubPrCommentAdapter.js +232 -0
- package/dist/core/src/ci/githubPrCommentAdapter.js.map +1 -0
- package/dist/core/src/ciWorkflowTemplates.js +1042 -0
- package/dist/core/src/ciWorkflowTemplates.js.map +1 -0
- package/dist/core/src/compliance/complianceExportBuilder.js +122 -0
- package/dist/core/src/compliance/complianceExportBuilder.js.map +1 -0
- package/dist/core/src/compliance/complianceExportTypes.js +3 -0
- package/dist/core/src/compliance/complianceExportTypes.js.map +1 -0
- package/dist/core/src/compliance/complianceExportWriter.js +48 -0
- package/dist/core/src/compliance/complianceExportWriter.js.map +1 -0
- package/dist/core/src/compliance/complianceRulePackResolver.js +62 -0
- package/dist/core/src/compliance/complianceRulePackResolver.js.map +1 -0
- package/dist/core/src/config/index.js +17 -0
- package/dist/core/src/config/index.js.map +1 -0
- package/dist/core/src/config/projectConfig.js +224 -0
- package/dist/core/src/config/projectConfig.js.map +1 -0
- package/dist/core/src/config/projectIdentity.js +160 -0
- package/dist/core/src/config/projectIdentity.js.map +1 -0
- package/dist/core/src/dataQueryRiskDetection.js +1203 -0
- package/dist/core/src/dataQueryRiskDetection.js.map +1 -0
- package/dist/core/src/dependencyValidation.js +2557 -0
- package/dist/core/src/dependencyValidation.js.map +1 -0
- package/dist/core/src/discoverAdrs.js +15 -0
- package/dist/core/src/discoverAdrs.js.map +1 -0
- package/dist/core/src/drift/driftComparator.js +49 -0
- package/dist/core/src/drift/driftComparator.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotLoader.js +136 -0
- package/dist/core/src/drift/driftSnapshotLoader.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotTypes.js +3 -0
- package/dist/core/src/drift/driftSnapshotTypes.js.map +1 -0
- package/dist/core/src/drift/driftSnapshotWriter.js +48 -0
- package/dist/core/src/drift/driftSnapshotWriter.js.map +1 -0
- package/dist/core/src/drift/index.js +6 -0
- package/dist/core/src/drift/index.js.map +1 -0
- package/dist/core/src/eventArchitectureAnalysis.js +925 -0
- package/dist/core/src/eventArchitectureAnalysis.js.map +1 -0
- package/dist/core/src/findingTargetResolution.js +117 -0
- package/dist/core/src/findingTargetResolution.js.map +1 -0
- package/dist/core/src/generateArchitectureDiagrams.js +57 -0
- package/dist/core/src/generateArchitectureDiagrams.js.map +1 -0
- package/dist/core/src/generateImpactAnalysis.js +366 -0
- package/dist/core/src/generateImpactAnalysis.js.map +1 -0
- package/dist/core/src/generateOnboardingDocs.js +262 -0
- package/dist/core/src/generateOnboardingDocs.js.map +1 -0
- package/dist/core/src/governance/cloudConnection.js +203 -0
- package/dist/core/src/governance/cloudConnection.js.map +1 -0
- package/dist/core/src/governance/governanceContracts.js +129 -0
- package/dist/core/src/governance/governanceContracts.js.map +1 -0
- package/dist/core/src/governance/governanceGitMetadata.js +78 -0
- package/dist/core/src/governance/governanceGitMetadata.js.map +1 -0
- package/dist/core/src/governance/governanceUploadClient.js +119 -0
- package/dist/core/src/governance/governanceUploadClient.js.map +1 -0
- package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js +155 -0
- package/dist/core/src/governance/governanceUploadLimitErrorFormatting.js.map +1 -0
- package/dist/core/src/governance/governanceUploadPayload.js +439 -0
- package/dist/core/src/governance/governanceUploadPayload.js.map +1 -0
- package/dist/core/src/governance/runGovernanceUpload.js +103 -0
- package/dist/core/src/governance/runGovernanceUpload.js.map +1 -0
- package/dist/core/src/impactAnalysisEngine.js +376 -0
- package/dist/core/src/impactAnalysisEngine.js.map +1 -0
- package/dist/core/src/initializationState.js +99 -0
- package/dist/core/src/initializationState.js.map +1 -0
- package/dist/core/src/managedArtifacts.js +151 -0
- package/dist/core/src/managedArtifacts.js.map +1 -0
- package/dist/core/src/moduleContractValidation.js +266 -0
- package/dist/core/src/moduleContractValidation.js.map +1 -0
- package/dist/core/src/moduleContractsGenerator.js +337 -0
- package/dist/core/src/moduleContractsGenerator.js.map +1 -0
- package/dist/core/src/moduleDiscovery.js +416 -0
- package/dist/core/src/moduleDiscovery.js.map +1 -0
- package/dist/core/src/moduleScope.js +426 -0
- package/dist/core/src/moduleScope.js.map +1 -0
- package/dist/core/src/onboardingGuidance.js +74 -0
- package/dist/core/src/onboardingGuidance.js.map +1 -0
- package/dist/core/src/operationalLogging.js +7 -0
- package/dist/core/src/operationalLogging.js.map +1 -0
- package/dist/core/src/packageVersion.js +52 -0
- package/dist/core/src/packageVersion.js.map +1 -0
- package/dist/core/src/policy/effectivePolicyResolver.js +91 -0
- package/dist/core/src/policy/effectivePolicyResolver.js.map +1 -0
- package/dist/core/src/policy/exceptionLoader.js +285 -0
- package/dist/core/src/policy/exceptionLoader.js.map +1 -0
- package/dist/core/src/policy/remotePolicyLoader.js +227 -0
- package/dist/core/src/policy/remotePolicyLoader.js.map +1 -0
- package/dist/core/src/policy/remotePolicyTypes.js +3 -0
- package/dist/core/src/policy/remotePolicyTypes.js.map +1 -0
- package/dist/core/src/policy/riskPolicyConfig.js +206 -0
- package/dist/core/src/policy/riskPolicyConfig.js.map +1 -0
- package/dist/core/src/policy/riskPolicyEnforcement.js +203 -0
- package/dist/core/src/policy/riskPolicyEnforcement.js.map +1 -0
- package/dist/core/src/policy/riskPolicyExitCode.js +16 -0
- package/dist/core/src/policy/riskPolicyExitCode.js.map +1 -0
- package/dist/core/src/policy/riskPolicyInitializer.js +90 -0
- package/dist/core/src/policy/riskPolicyInitializer.js.map +1 -0
- package/dist/core/src/quickFix/quickFixEngine.js +132 -0
- package/dist/core/src/quickFix/quickFixEngine.js.map +1 -0
- package/dist/core/src/quickFix/quickFixMessaging.js +15 -0
- package/dist/core/src/quickFix/quickFixMessaging.js.map +1 -0
- package/dist/core/src/quickFix/quickFixProviders.js +87 -0
- package/dist/core/src/quickFix/quickFixProviders.js.map +1 -0
- package/dist/core/src/quickFix/quickFixTypes.js +3 -0
- package/dist/core/src/quickFix/quickFixTypes.js.map +1 -0
- package/dist/core/src/remediation/fileHelpers.js +86 -0
- package/dist/core/src/remediation/fileHelpers.js.map +1 -0
- package/dist/core/src/remediation/fixRegistry.js +645 -0
- package/dist/core/src/remediation/fixRegistry.js.map +1 -0
- package/dist/core/src/remediation/index.js +8 -0
- package/dist/core/src/remediation/index.js.map +1 -0
- package/dist/core/src/remediation/remediationEngine.js +207 -0
- package/dist/core/src/remediation/remediationEngine.js.map +1 -0
- package/dist/core/src/reporting/adrReporter.js +59 -0
- package/dist/core/src/reporting/adrReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureBaselineReporter.js +64 -0
- package/dist/core/src/reporting/architectureBaselineReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureDiagramReporter.js +148 -0
- package/dist/core/src/reporting/architectureDiagramReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureDriftTimelineReporter.js +44 -0
- package/dist/core/src/reporting/architectureDriftTimelineReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureHealthHistoryReporter.js +51 -0
- package/dist/core/src/reporting/architectureHealthHistoryReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureMapReporter.js +228 -0
- package/dist/core/src/reporting/architectureMapReporter.js.map +1 -0
- package/dist/core/src/reporting/architecturePrCommentReporter.js +324 -0
- package/dist/core/src/reporting/architecturePrCommentReporter.js.map +1 -0
- package/dist/core/src/reporting/architecturePrReviewReporter.js +389 -0
- package/dist/core/src/reporting/architecturePrReviewReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureReportArtifacts.js +166 -0
- package/dist/core/src/reporting/architectureReportArtifacts.js.map +1 -0
- package/dist/core/src/reporting/architectureReviewReportGenerator.js +382 -0
- package/dist/core/src/reporting/architectureReviewReportGenerator.js.map +1 -0
- package/dist/core/src/reporting/architectureReviewReporter.js +1633 -0
- package/dist/core/src/reporting/architectureReviewReporter.js.map +1 -0
- package/dist/core/src/reporting/architectureRiskAssessment.js +101 -0
- package/dist/core/src/reporting/architectureRiskAssessment.js.map +1 -0
- package/dist/core/src/reporting/architectureScoreBadge.js +177 -0
- package/dist/core/src/reporting/architectureScoreBadge.js.map +1 -0
- package/dist/core/src/reporting/architectureSupplementalReporters.js +124 -0
- package/dist/core/src/reporting/architectureSupplementalReporters.js.map +1 -0
- package/dist/core/src/reporting/changeRiskAssessment.js +6 -0
- package/dist/core/src/reporting/changeRiskAssessment.js.map +1 -0
- package/dist/core/src/reporting/changeRiskTypes.js +3 -0
- package/dist/core/src/reporting/changeRiskTypes.js.map +1 -0
- package/dist/core/src/reporting/impactAnalysisReporter.js +111 -0
- package/dist/core/src/reporting/impactAnalysisReporter.js.map +1 -0
- package/dist/core/src/reporting/onboardingDocsReporter.js +113 -0
- package/dist/core/src/reporting/onboardingDocsReporter.js.map +1 -0
- package/dist/core/src/reporting/rulePackReporter.js +61 -0
- package/dist/core/src/reporting/rulePackReporter.js.map +1 -0
- package/dist/core/src/reporting/safetyControlsDetection.js +343 -0
- package/dist/core/src/reporting/safetyControlsDetection.js.map +1 -0
- package/dist/core/src/resolveRuleAdrs.js +31 -0
- package/dist/core/src/resolveRuleAdrs.js.map +1 -0
- package/dist/core/src/reviewGenerationWorkflow.js +220 -0
- package/dist/core/src/reviewGenerationWorkflow.js.map +1 -0
- package/dist/core/src/rulePacks/index.js +17 -0
- package/dist/core/src/rulePacks/index.js.map +1 -0
- package/dist/core/src/rulePacks/layeredMonolithRulePack.js +45 -0
- package/dist/core/src/rulePacks/layeredMonolithRulePack.js.map +1 -0
- package/dist/core/src/rulePacks/modularMonolithRulePack.js +41 -0
- package/dist/core/src/rulePacks/modularMonolithRulePack.js.map +1 -0
- package/dist/core/src/rulePacks/types.js +3 -0
- package/dist/core/src/rulePacks/types.js.map +1 -0
- package/dist/core/src/setupNextSteps.js +939 -0
- package/dist/core/src/setupNextSteps.js.map +1 -0
- package/dist/core/src/setupRefresh.js +152 -0
- package/dist/core/src/setupRefresh.js.map +1 -0
- package/dist/core/src/smartInit/apiDbHintDetection.js +836 -0
- package/dist/core/src/smartInit/apiDbHintDetection.js.map +1 -0
- package/dist/core/src/smartInit/architectureProposalBuilder.js +1215 -0
- package/dist/core/src/smartInit/architectureProposalBuilder.js.map +1 -0
- package/dist/core/src/smartInit/architectureStyleDetection.js +472 -0
- package/dist/core/src/smartInit/architectureStyleDetection.js.map +1 -0
- package/dist/core/src/smartInit/archpilotIgnore.js +139 -0
- package/dist/core/src/smartInit/archpilotIgnore.js.map +1 -0
- package/dist/core/src/smartInit/componentDetection.js +1084 -0
- package/dist/core/src/smartInit/componentDetection.js.map +1 -0
- package/dist/core/src/smartInit/implementationProfileResolution.js +53 -0
- package/dist/core/src/smartInit/implementationProfileResolution.js.map +1 -0
- package/dist/core/src/smartInit/index.js +31 -0
- package/dist/core/src/smartInit/index.js.map +1 -0
- package/dist/core/src/smartInit/moduleDetection.js +982 -0
- package/dist/core/src/smartInit/moduleDetection.js.map +1 -0
- package/dist/core/src/smartInit/projectKindDetection.js +437 -0
- package/dist/core/src/smartInit/projectKindDetection.js.map +1 -0
- package/dist/core/src/smartInit/repoTopologyDetection.js +470 -0
- package/dist/core/src/smartInit/repoTopologyDetection.js.map +1 -0
- package/dist/core/src/smartInit/runContext.js +112 -0
- package/dist/core/src/smartInit/runContext.js.map +1 -0
- package/dist/core/src/smartInit/smartInitEngine.js +94 -0
- package/dist/core/src/smartInit/smartInitEngine.js.map +1 -0
- package/dist/core/src/smartInit/stackDetection.js +503 -0
- package/dist/core/src/smartInit/stackDetection.js.map +1 -0
- package/dist/core/src/smartInit/toolingHintDetection.js +345 -0
- package/dist/core/src/smartInit/toolingHintDetection.js.map +1 -0
- package/dist/core/src/sourceScopeClassification.js +83 -0
- package/dist/core/src/sourceScopeClassification.js.map +1 -0
- package/dist/core/src/stackAdapters.js +2475 -0
- package/dist/core/src/stackAdapters.js.map +1 -0
- package/dist/core/src/stackMetadata.js +157 -0
- package/dist/core/src/stackMetadata.js.map +1 -0
- package/dist/core/src/suppressions/index.js +3 -0
- package/dist/core/src/suppressions/index.js.map +1 -0
- package/dist/core/src/suppressions/suppressionReporter.js +38 -0
- package/dist/core/src/suppressions/suppressionReporter.js.map +1 -0
- package/dist/core/src/suppressions/suppressionTypes.js +3 -0
- package/dist/core/src/suppressions/suppressionTypes.js.map +1 -0
- package/dist/core/src/suppressions/suppressionValidator.js +119 -0
- package/dist/core/src/suppressions/suppressionValidator.js.map +1 -0
- package/dist/core/src/transactionDomainAnalysis.js +1416 -0
- package/dist/core/src/transactionDomainAnalysis.js.map +1 -0
- package/dist/core/src/validation/changedFilesResolver.js +165 -0
- package/dist/core/src/validation/changedFilesResolver.js.map +1 -0
- package/dist/core/src/validationProfiling.js +33 -0
- package/dist/core/src/validationProfiling.js.map +1 -0
- package/dist/core/src/validationRuleExplanations.js +69 -0
- package/dist/core/src/validationRuleExplanations.js.map +1 -0
- package/dist/core/src/validationSourceInventory.js +200 -0
- package/dist/core/src/validationSourceInventory.js.map +1 -0
- package/dist/core/src/validationSuppressions.js +231 -0
- package/dist/core/src/validationSuppressions.js.map +1 -0
- package/dist/core/src/validationTypeScriptAstCache.js +82 -0
- package/dist/core/src/validationTypeScriptAstCache.js.map +1 -0
- package/dist/core/src/validationWorkflow.js +222 -0
- package/dist/core/src/validationWorkflow.js.map +1 -0
- package/dist/core/src/workspaceRoot.js +68 -0
- package/dist/core/src/workspaceRoot.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,925 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectEventArchitectureFindings = detectEventArchitectureFindings;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const node_fs_1 = require("node:fs");
|
|
39
|
+
const ts = __importStar(require("typescript"));
|
|
40
|
+
const moduleDiscovery_1 = require("./moduleDiscovery");
|
|
41
|
+
const archpilotIgnore_1 = require("./smartInit/archpilotIgnore");
|
|
42
|
+
const validationProfiling_1 = require("./validationProfiling");
|
|
43
|
+
const RuleIds = {
|
|
44
|
+
UNHANDLED_EVENT: 'AP-EVT-001',
|
|
45
|
+
CIRCULAR_EVENT_CHAIN: 'AP-EVT-002',
|
|
46
|
+
SYNC_EVENT_DEPENDENCY: 'AP-EVT-003',
|
|
47
|
+
NAMING_INCONSISTENCY: 'AP-EVT-004',
|
|
48
|
+
};
|
|
49
|
+
const maxFileSizeBytes = 384 * 1024;
|
|
50
|
+
const supportedExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.java', '.cs', '.py', '.php', '.go']);
|
|
51
|
+
const ignoredDirectoryNames = new Set([
|
|
52
|
+
'.git',
|
|
53
|
+
'.archpilot',
|
|
54
|
+
'.next',
|
|
55
|
+
'.vscode-test',
|
|
56
|
+
'node_modules',
|
|
57
|
+
'dist',
|
|
58
|
+
'build',
|
|
59
|
+
'out',
|
|
60
|
+
'coverage',
|
|
61
|
+
'test',
|
|
62
|
+
'tests',
|
|
63
|
+
'__tests__',
|
|
64
|
+
'fixtures',
|
|
65
|
+
'smoke',
|
|
66
|
+
'examples',
|
|
67
|
+
'docs',
|
|
68
|
+
'reports',
|
|
69
|
+
'generated',
|
|
70
|
+
]);
|
|
71
|
+
const phpEventHandlerPattern = /\bEvent::listen\s*\(\s*([A-Z][A-Za-z0-9]*Event)::class\b|\bfunction\s+handle\s*\(\s*([A-Z][A-Za-z0-9]*Event)\s+\$\w+/gu;
|
|
72
|
+
const phpEventPublishPattern = /((?:return\s+)?(?:\$?\w+\s*=\s*)?(?:event|Event::dispatch|[A-Z][A-Za-z0-9]*Event::dispatch)\s*\(([^;]+)\))/gu;
|
|
73
|
+
const csharpHandlerPattern = /class\s+\w+[^{]*:\s*(?:INotificationHandler|IConsumer)<\s*([A-Z][A-Za-z0-9]*Event)\s*>/gu;
|
|
74
|
+
const csharpPublishPattern = /((?:await\s+)?(?:return\s+)?(?:[\w<>]+\s+\w+\s*=\s*)?[\w.]+(?:Publish|PublishAsync|SendMessageAsync)\s*\(([^;]+)\))/gu;
|
|
75
|
+
const pythonSubscriberPattern = /@(?:\w+\.)?(?:subscriber|consumer|task)\s*\(([^)]*)\)/gu;
|
|
76
|
+
const pythonPublishPattern = /((?:await\s+)?(?:return\s+)?[\w.]+(?:publish|send_task)\s*\(([^)\n]+)\))/gu;
|
|
77
|
+
const goHandlerPattern = /\.(?:Subscribe|QueueSubscribe|SubscribeSync|Consume)\s*\(([^)]*)\)/gu;
|
|
78
|
+
const goPublishPattern = /((?:[\w]+\s*:=\s*)?[\w.]+\.(?:Publish|Produce)\s*\(([^)\n]+)\))/gu;
|
|
79
|
+
const routeStringPattern = /['"`]([^'"`]+)['"`]/u;
|
|
80
|
+
function normalizePath(value) {
|
|
81
|
+
return value.replace(/\\/g, '/');
|
|
82
|
+
}
|
|
83
|
+
function toPosixRelative(workspaceRoot, absolutePath) {
|
|
84
|
+
return normalizePath(path.relative(workspaceRoot, absolutePath));
|
|
85
|
+
}
|
|
86
|
+
async function readTextFileIfReasonable(filePath, sourceInventory) {
|
|
87
|
+
if (sourceInventory) {
|
|
88
|
+
return sourceInventory.readText(filePath, { maxFileSizeBytes });
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const stats = await node_fs_1.promises.stat(filePath);
|
|
92
|
+
if (stats.size > maxFileSizeBytes) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return await node_fs_1.promises.readFile(filePath, { encoding: 'utf8' });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function inferModuleForPath(contract, relativePath) {
|
|
102
|
+
const normalized = normalizePath(relativePath);
|
|
103
|
+
const modulesRoot = (0, moduleDiscovery_1.resolveModulesRootFromContract)(contract);
|
|
104
|
+
for (const [moduleName, reference] of Object.entries(contract.modules ?? {})) {
|
|
105
|
+
const modulePath = normalizePath(reference.path ?? `${modulesRoot}/${moduleName}`);
|
|
106
|
+
if (normalized === modulePath || normalized.startsWith(`${modulePath}/`)) {
|
|
107
|
+
return moduleName;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
async function collectSourceFiles(workspaceRoot, contract, sourceInventory) {
|
|
113
|
+
if (sourceInventory) {
|
|
114
|
+
const files = await sourceInventory.getTextFiles({
|
|
115
|
+
extensions: [...supportedExtensions],
|
|
116
|
+
ignoredDirectoryNames: [...ignoredDirectoryNames],
|
|
117
|
+
maxFileSizeBytes,
|
|
118
|
+
});
|
|
119
|
+
return files.map((file) => ({
|
|
120
|
+
relativePath: file.relativePath,
|
|
121
|
+
content: file.content,
|
|
122
|
+
extension: file.extension,
|
|
123
|
+
moduleName: inferModuleForPath(contract, file.relativePath),
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
const ignoreMatcher = (0, archpilotIgnore_1.loadArchpilotIgnoreMatcher)(workspaceRoot);
|
|
127
|
+
const collected = [];
|
|
128
|
+
const stack = [workspaceRoot];
|
|
129
|
+
while (stack.length > 0) {
|
|
130
|
+
const current = stack.pop();
|
|
131
|
+
if (!current) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
let entries;
|
|
135
|
+
try {
|
|
136
|
+
entries = await node_fs_1.promises.readdir(current, { withFileTypes: true });
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const absoluteEntryPath = path.join(current, entry.name);
|
|
143
|
+
const relativeEntryPath = toPosixRelative(workspaceRoot, absoluteEntryPath);
|
|
144
|
+
if (ignoreMatcher.isIgnored(relativeEntryPath)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (entry.isDirectory()) {
|
|
148
|
+
if (entry.name.startsWith('.') || ignoredDirectoryNames.has(entry.name)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
stack.push(absoluteEntryPath);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const extension = path.extname(entry.name).toLowerCase();
|
|
155
|
+
if (!supportedExtensions.has(extension)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const content = await readTextFileIfReasonable(absoluteEntryPath, sourceInventory);
|
|
159
|
+
if (!content) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
collected.push({
|
|
163
|
+
relativePath: relativeEntryPath,
|
|
164
|
+
content,
|
|
165
|
+
extension,
|
|
166
|
+
module: inferModuleForPath(contract, relativeEntryPath),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return collected.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
171
|
+
}
|
|
172
|
+
function getLineStarts(file) {
|
|
173
|
+
if (file.lineStarts) {
|
|
174
|
+
return file.lineStarts;
|
|
175
|
+
}
|
|
176
|
+
const lineStarts = [0];
|
|
177
|
+
for (let index = 0; index < file.content.length; index += 1) {
|
|
178
|
+
if (file.content[index] === '\n') {
|
|
179
|
+
lineStarts.push(index + 1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
file.lineStarts = lineStarts;
|
|
183
|
+
return lineStarts;
|
|
184
|
+
}
|
|
185
|
+
function lineNumberForOffset(file, offset) {
|
|
186
|
+
const lineStarts = getLineStarts(file);
|
|
187
|
+
let low = 0;
|
|
188
|
+
let high = lineStarts.length - 1;
|
|
189
|
+
while (low <= high) {
|
|
190
|
+
const middle = Math.floor((low + high) / 2);
|
|
191
|
+
const lineStart = lineStarts[middle] ?? 0;
|
|
192
|
+
if (lineStart <= offset) {
|
|
193
|
+
low = middle + 1;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
high = middle - 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return Math.max(1, high + 1);
|
|
200
|
+
}
|
|
201
|
+
function findMatchingBrace(content, openBraceIndex) {
|
|
202
|
+
let depth = 0;
|
|
203
|
+
for (let index = openBraceIndex; index < content.length; index += 1) {
|
|
204
|
+
if (content[index] === '{') {
|
|
205
|
+
depth += 1;
|
|
206
|
+
}
|
|
207
|
+
if (content[index] === '}') {
|
|
208
|
+
depth -= 1;
|
|
209
|
+
if (depth === 0) {
|
|
210
|
+
return index;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return Math.min(content.length - 1, openBraceIndex + 5000);
|
|
215
|
+
}
|
|
216
|
+
function findPythonBlockEnd(content, blockStart) {
|
|
217
|
+
const lineStart = content.lastIndexOf('\n', blockStart) + 1;
|
|
218
|
+
const lineEnd = content.indexOf('\n', blockStart);
|
|
219
|
+
const line = content.slice(lineStart, lineEnd === -1 ? content.length : lineEnd);
|
|
220
|
+
const baseIndent = /^\s*/u.exec(line)?.[0].length ?? 0;
|
|
221
|
+
let offset = lineEnd === -1 ? content.length : lineEnd + 1;
|
|
222
|
+
while (offset < content.length) {
|
|
223
|
+
const nextLineEnd = content.indexOf('\n', offset);
|
|
224
|
+
const nextEnd = nextLineEnd === -1 ? content.length : nextLineEnd;
|
|
225
|
+
const nextLine = content.slice(offset, nextEnd);
|
|
226
|
+
if (nextLine.trim().length > 0) {
|
|
227
|
+
const indent = /^\s*/u.exec(nextLine)?.[0].length ?? 0;
|
|
228
|
+
if (indent <= baseIndent) {
|
|
229
|
+
return offset;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (nextLineEnd === -1) {
|
|
233
|
+
return content.length;
|
|
234
|
+
}
|
|
235
|
+
offset = nextLineEnd + 1;
|
|
236
|
+
}
|
|
237
|
+
return content.length;
|
|
238
|
+
}
|
|
239
|
+
function normalizeEventName(value) {
|
|
240
|
+
return value
|
|
241
|
+
.replace(/Event$/u, '')
|
|
242
|
+
.replace(/([a-z0-9])([A-Z])/gu, '$1.$2')
|
|
243
|
+
.replace(/[_\s-]+/gu, '.')
|
|
244
|
+
.toLowerCase()
|
|
245
|
+
.replace(/[^a-z0-9.]/gu, '')
|
|
246
|
+
.replace(/\.+/gu, '.')
|
|
247
|
+
.replace(/^\.|\.$/gu, '');
|
|
248
|
+
}
|
|
249
|
+
function classifyEventStyle(value) {
|
|
250
|
+
if (/^[A-Z0-9_]+$/u.test(value) && value.includes('_')) {
|
|
251
|
+
return 'constant';
|
|
252
|
+
}
|
|
253
|
+
if (/^[a-z0-9]+(?:\.[a-z0-9]+)+$/u.test(value)) {
|
|
254
|
+
return 'dot';
|
|
255
|
+
}
|
|
256
|
+
if (/^[A-Z][A-Za-z0-9]+$/u.test(value)) {
|
|
257
|
+
return value.endsWith('Event') ? 'class' : 'pascal';
|
|
258
|
+
}
|
|
259
|
+
return 'pascal';
|
|
260
|
+
}
|
|
261
|
+
function createFinding(input) {
|
|
262
|
+
return {
|
|
263
|
+
id: input.id,
|
|
264
|
+
severity: 'warning',
|
|
265
|
+
passed: false,
|
|
266
|
+
message: input.message,
|
|
267
|
+
findingType: 'file-scope',
|
|
268
|
+
filePath: input.signal.file.relativePath,
|
|
269
|
+
sourceFile: input.signal.file.relativePath,
|
|
270
|
+
sourceLine: input.signal.line,
|
|
271
|
+
module: input.signal.module,
|
|
272
|
+
target: input.target ?? input.signal.eventName,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function getStringLiteralText(node) {
|
|
276
|
+
if (ts.isStringLiteralLike(node)) {
|
|
277
|
+
return node.text;
|
|
278
|
+
}
|
|
279
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression)) {
|
|
280
|
+
return node.expression.text;
|
|
281
|
+
}
|
|
282
|
+
if (ts.isIdentifier(node)) {
|
|
283
|
+
return node.text.endsWith('Event') ? node.text : undefined;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
function getEventClassName(node) {
|
|
288
|
+
if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text.endsWith('Event')) {
|
|
289
|
+
return node.expression.text;
|
|
290
|
+
}
|
|
291
|
+
if (ts.isIdentifier(node) && node.text.endsWith('Event')) {
|
|
292
|
+
return node.text;
|
|
293
|
+
}
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
function getPropertyAccessParts(expression) {
|
|
297
|
+
if (!ts.isPropertyAccessExpression(expression)) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
receiverText: expression.expression.getText(),
|
|
302
|
+
propertyName: expression.name.text,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function hasKnownTypeScriptEventFrameworkEvidence(file) {
|
|
306
|
+
return /@nestjs\/event-emitter|OnEvent|EventEmitter2|from ['"]events['"]|EventEmitter|EventBus|VendureEvent|faststream|bullmq|nats|kafka|rabbit/iu.test(file.content);
|
|
307
|
+
}
|
|
308
|
+
function isMeaningfulEventTopic(value) {
|
|
309
|
+
return (/^[a-z][a-z0-9]*(?:[._:-][a-z0-9]+)+$/u.test(value) ||
|
|
310
|
+
/^[A-Z0-9_]+$/u.test(value) && value.includes('_') ||
|
|
311
|
+
/^[A-Z][A-Za-z0-9]*Event$/u.test(value));
|
|
312
|
+
}
|
|
313
|
+
function getFirstMeaningfulEventTopic(node) {
|
|
314
|
+
if (!node) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
const literal = getStringLiteralText(node);
|
|
318
|
+
return literal && isMeaningfulEventTopic(literal) ? literal : undefined;
|
|
319
|
+
}
|
|
320
|
+
function receiverLooksLikeKnownEventBus(receiverText) {
|
|
321
|
+
return /(?:^|\.)eventBus$/u.test(receiverText) || /(?:^|\.)eventEmitter$/u.test(receiverText);
|
|
322
|
+
}
|
|
323
|
+
function getMethodName(node) {
|
|
324
|
+
let current = node;
|
|
325
|
+
while (current) {
|
|
326
|
+
if (ts.isMethodDeclaration(current) && current.name) {
|
|
327
|
+
const name = current.name;
|
|
328
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
329
|
+
return name.text;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (ts.isFunctionDeclaration(current)) {
|
|
333
|
+
return current.name?.text;
|
|
334
|
+
}
|
|
335
|
+
current = current.parent;
|
|
336
|
+
}
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
function getStringLiteralFromText(value) {
|
|
340
|
+
const match = routeStringPattern.exec(value);
|
|
341
|
+
return match?.[1];
|
|
342
|
+
}
|
|
343
|
+
function getEventNameFromExpressionText(value) {
|
|
344
|
+
const stringLiterals = [...value.matchAll(/['"`]([^'"`]+)['"`]/gu)].map((match) => match[1]).filter(Boolean);
|
|
345
|
+
const eventLikeLiteral = stringLiterals.find((entry) => /^[a-z0-9]+(?:\.[a-z0-9]+)+$/u.test(entry)) ??
|
|
346
|
+
stringLiterals.find((entry) => /^[A-Z0-9_]+$/u.test(entry) && entry.includes('_')) ??
|
|
347
|
+
stringLiterals.find((entry) => /Event$/u.test(entry)) ??
|
|
348
|
+
stringLiterals.at(0);
|
|
349
|
+
if (eventLikeLiteral) {
|
|
350
|
+
return eventLikeLiteral;
|
|
351
|
+
}
|
|
352
|
+
const classMatch = /\b(?:new\s+)?([A-Z][A-Za-z0-9]*Event)\b/u.exec(value);
|
|
353
|
+
if (classMatch?.[1]) {
|
|
354
|
+
return classMatch[1];
|
|
355
|
+
}
|
|
356
|
+
const classRefMatch = /\b([A-Z][A-Za-z0-9]*Event)::class\b/u.exec(value);
|
|
357
|
+
return classRefMatch?.[1];
|
|
358
|
+
}
|
|
359
|
+
function getTypeScriptPublishEventName(file, node) {
|
|
360
|
+
const access = getPropertyAccessParts(node.expression);
|
|
361
|
+
if (!access) {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
const firstArg = node.arguments[0];
|
|
365
|
+
const eventClassName = firstArg ? getEventClassName(firstArg) : undefined;
|
|
366
|
+
const hasFrameworkEvidence = hasKnownTypeScriptEventFrameworkEvidence(file);
|
|
367
|
+
if (['publish', 'publishEvent', 'dispatch'].includes(access.propertyName) &&
|
|
368
|
+
eventClassName &&
|
|
369
|
+
(receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
|
|
370
|
+
return eventClassName;
|
|
371
|
+
}
|
|
372
|
+
if (access.propertyName === 'emit' &&
|
|
373
|
+
(receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
|
|
374
|
+
return getFirstMeaningfulEventTopic(firstArg);
|
|
375
|
+
}
|
|
376
|
+
if (['request', 'requestReply', 'sendAndReceive'].includes(access.propertyName) &&
|
|
377
|
+
(receiverLooksLikeKnownEventBus(access.receiverText) || hasFrameworkEvidence)) {
|
|
378
|
+
return getFirstMeaningfulEventTopic(firstArg);
|
|
379
|
+
}
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
function collectTypeScriptEventModuleInfo(file, sourceFile) {
|
|
383
|
+
const names = new Set();
|
|
384
|
+
const reExports = [];
|
|
385
|
+
const visit = (node) => {
|
|
386
|
+
if (ts.isClassDeclaration(node) &&
|
|
387
|
+
node.name?.text.endsWith('Event') &&
|
|
388
|
+
node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
389
|
+
names.add(normalizeEventName(node.name.text));
|
|
390
|
+
}
|
|
391
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
392
|
+
const specifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
|
|
393
|
+
? node.moduleSpecifier.text
|
|
394
|
+
: undefined;
|
|
395
|
+
const exportedNames = [];
|
|
396
|
+
for (const element of node.exportClause.elements) {
|
|
397
|
+
const exported = element.name.text;
|
|
398
|
+
if (exported.endsWith('Event')) {
|
|
399
|
+
names.add(normalizeEventName(exported));
|
|
400
|
+
exportedNames.push(exported);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (specifier && exportedNames.length > 0) {
|
|
404
|
+
reExports.push({ moduleSpecifier: specifier, names: exportedNames });
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (ts.isExportDeclaration(node) && !node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
408
|
+
reExports.push({ moduleSpecifier: node.moduleSpecifier.text });
|
|
409
|
+
}
|
|
410
|
+
ts.forEachChild(node, visit);
|
|
411
|
+
};
|
|
412
|
+
visit(sourceFile);
|
|
413
|
+
for (const match of file.content.matchAll(/\bexport\s+(?:type\s+)?\{\s*([^}]+)\s*\}/gu)) {
|
|
414
|
+
for (const part of (match[1] ?? '').split(',')) {
|
|
415
|
+
const exported = part.trim().split(/\s+as\s+/iu).at(-1)?.trim();
|
|
416
|
+
if (exported && /^[A-Z]\w*Event$/u.test(exported)) {
|
|
417
|
+
names.add(normalizeEventName(exported));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return { file, declaredEventNames: names, reExports };
|
|
422
|
+
}
|
|
423
|
+
function getTypeScriptHandlerEventName(file, node) {
|
|
424
|
+
const access = getPropertyAccessParts(node.expression);
|
|
425
|
+
if (!access) {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
if (access.propertyName === 'ofType') {
|
|
429
|
+
const firstArg = node.arguments[0];
|
|
430
|
+
return firstArg ? getEventClassName(firstArg) : undefined;
|
|
431
|
+
}
|
|
432
|
+
if (access.propertyName === 'registerBlockingEventHandler') {
|
|
433
|
+
const firstArg = node.arguments[0];
|
|
434
|
+
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
435
|
+
for (const property of firstArg.properties) {
|
|
436
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const name = property.name;
|
|
440
|
+
const propertyName = ts.isIdentifier(name) || ts.isStringLiteral(name) ? name.text : undefined;
|
|
441
|
+
if (propertyName !== 'event') {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const initializer = property.initializer;
|
|
445
|
+
if (ts.isArrayLiteralExpression(initializer)) {
|
|
446
|
+
for (const element of initializer.elements) {
|
|
447
|
+
const eventName = getEventClassName(element);
|
|
448
|
+
if (eventName) {
|
|
449
|
+
return eventName;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return getEventClassName(initializer);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (['on', 'addListener', 'subscribe', 'consume'].includes(access.propertyName) &&
|
|
458
|
+
(receiverLooksLikeKnownEventBus(access.receiverText) || hasKnownTypeScriptEventFrameworkEvidence(file))) {
|
|
459
|
+
return getFirstMeaningfulEventTopic(node.arguments[0]);
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
function isAwaitedOrResultUsed(node) {
|
|
464
|
+
const parent = node.parent;
|
|
465
|
+
const awaited = ts.isAwaitExpression(parent);
|
|
466
|
+
const outer = awaited ? parent.parent : parent;
|
|
467
|
+
return {
|
|
468
|
+
awaited,
|
|
469
|
+
assignedOrReturned: ts.isReturnStatement(outer) ||
|
|
470
|
+
ts.isVariableDeclaration(outer) ||
|
|
471
|
+
ts.isBinaryExpression(outer),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
function getHandlerFromDecorators(file, sourceFile, node) {
|
|
475
|
+
if (!ts.canHaveDecorators(node)) {
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
const decorators = ts.getDecorators(node) ?? [];
|
|
479
|
+
for (const decorator of decorators) {
|
|
480
|
+
const expression = decorator.expression;
|
|
481
|
+
if (!ts.isCallExpression(expression) || !/(?:OnEvent|EventPattern|EventListener)$/u.test(expression.expression.getText())) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const firstArg = expression.arguments[0];
|
|
485
|
+
const eventName = firstArg ? getStringLiteralText(firstArg) : undefined;
|
|
486
|
+
if (!eventName) {
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
eventName,
|
|
491
|
+
normalizedName: normalizeEventName(eventName),
|
|
492
|
+
file,
|
|
493
|
+
line: lineNumberForOffset(file, decorator.getStart(sourceFile, false)),
|
|
494
|
+
methodName: getMethodName(node),
|
|
495
|
+
module: file.module,
|
|
496
|
+
style: classifyEventStyle(eventName),
|
|
497
|
+
publishes: [],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
function extractTypeScriptSignals(file, astCache) {
|
|
503
|
+
const sourceFile = astCache
|
|
504
|
+
? astCache.parseSourceFile({
|
|
505
|
+
filePath: file.relativePath,
|
|
506
|
+
sourceText: file.content,
|
|
507
|
+
scriptTarget: ts.ScriptTarget.Latest,
|
|
508
|
+
setParentNodes: true,
|
|
509
|
+
})
|
|
510
|
+
: ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
|
|
511
|
+
const publishes = [];
|
|
512
|
+
const handlers = [];
|
|
513
|
+
const handlerStack = [];
|
|
514
|
+
const visit = (node) => {
|
|
515
|
+
if (ts.isMethodDeclaration(node)) {
|
|
516
|
+
const handler = getHandlerFromDecorators(file, sourceFile, node);
|
|
517
|
+
if (handler) {
|
|
518
|
+
handlers.push(handler);
|
|
519
|
+
handlerStack.push(handler);
|
|
520
|
+
if (node.body) {
|
|
521
|
+
ts.forEachChild(node.body, visit);
|
|
522
|
+
}
|
|
523
|
+
handlerStack.pop();
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (ts.isCallExpression(node)) {
|
|
528
|
+
const eventName = getTypeScriptHandlerEventName(file, node);
|
|
529
|
+
if (eventName) {
|
|
530
|
+
const handler = {
|
|
531
|
+
eventName,
|
|
532
|
+
normalizedName: normalizeEventName(eventName),
|
|
533
|
+
file,
|
|
534
|
+
line: lineNumberForOffset(file, node.getStart(sourceFile, false)),
|
|
535
|
+
methodName: getMethodName(node),
|
|
536
|
+
module: file.module,
|
|
537
|
+
style: classifyEventStyle(eventName),
|
|
538
|
+
publishes: [],
|
|
539
|
+
};
|
|
540
|
+
handlers.push(handler);
|
|
541
|
+
const callback = node.arguments.at(-1);
|
|
542
|
+
if (callback && (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && callback.body) {
|
|
543
|
+
handlerStack.push(handler);
|
|
544
|
+
ts.forEachChild(callback.body, visit);
|
|
545
|
+
handlerStack.pop();
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (ts.isCallExpression(node)) {
|
|
550
|
+
const eventName = getTypeScriptPublishEventName(file, node);
|
|
551
|
+
if (eventName) {
|
|
552
|
+
const usage = isAwaitedOrResultUsed(node);
|
|
553
|
+
const signal = {
|
|
554
|
+
eventName,
|
|
555
|
+
normalizedName: normalizeEventName(eventName),
|
|
556
|
+
file,
|
|
557
|
+
line: lineNumberForOffset(file, node.getStart(sourceFile, false)),
|
|
558
|
+
methodName: getMethodName(node),
|
|
559
|
+
module: file.module,
|
|
560
|
+
style: classifyEventStyle(eventName),
|
|
561
|
+
awaited: usage.awaited,
|
|
562
|
+
assignedOrReturned: usage.assignedOrReturned,
|
|
563
|
+
};
|
|
564
|
+
publishes.push(signal);
|
|
565
|
+
const activeHandler = handlerStack[handlerStack.length - 1];
|
|
566
|
+
if (activeHandler) {
|
|
567
|
+
activeHandler.publishes.push(signal);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
ts.forEachChild(node, visit);
|
|
572
|
+
};
|
|
573
|
+
visit(sourceFile);
|
|
574
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
575
|
+
}
|
|
576
|
+
function extractJavaSignals(file) {
|
|
577
|
+
const publishes = [];
|
|
578
|
+
const handlers = [];
|
|
579
|
+
for (const match of file.content.matchAll(/@EventListener\s*(?:\(\s*([A-Za-z]\w*Event)\.class\s*\))?/gu)) {
|
|
580
|
+
const eventName = match[1] ?? 'ApplicationEvent';
|
|
581
|
+
handlers.push({
|
|
582
|
+
eventName,
|
|
583
|
+
normalizedName: normalizeEventName(eventName),
|
|
584
|
+
file,
|
|
585
|
+
line: lineNumberForOffset(file, match.index ?? 0),
|
|
586
|
+
module: file.module,
|
|
587
|
+
style: classifyEventStyle(eventName),
|
|
588
|
+
publishes: [],
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
for (const match of file.content.matchAll(/(?:await\s+)?(?:\w+\.)?(?:publishEvent|publish|emit)\s*\(\s*(?:new\s+)?(["']?)([A-Za-z][\w.]*Event?|[a-z][\w.]+)\1/gu)) {
|
|
592
|
+
const eventName = match[2];
|
|
593
|
+
if (!eventName) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
publishes.push({
|
|
597
|
+
eventName,
|
|
598
|
+
normalizedName: normalizeEventName(eventName),
|
|
599
|
+
file,
|
|
600
|
+
line: lineNumberForOffset(file, match.index ?? 0),
|
|
601
|
+
module: file.module,
|
|
602
|
+
style: classifyEventStyle(eventName),
|
|
603
|
+
awaited: /^\s*await\b/u.test(match[0]),
|
|
604
|
+
assignedOrReturned: false,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
608
|
+
}
|
|
609
|
+
function extractPublishSignalsFromText(file, body, baseOffset, pattern) {
|
|
610
|
+
const publishes = [];
|
|
611
|
+
for (const match of body.matchAll(pattern)) {
|
|
612
|
+
const callText = match[1] ?? match[0] ?? '';
|
|
613
|
+
const argsText = match[2] ?? callText;
|
|
614
|
+
const eventName = getEventNameFromExpressionText(argsText);
|
|
615
|
+
if (!eventName) {
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
publishes.push({
|
|
619
|
+
eventName,
|
|
620
|
+
normalizedName: normalizeEventName(eventName),
|
|
621
|
+
file,
|
|
622
|
+
line: lineNumberForOffset(file, baseOffset + (match.index ?? 0)),
|
|
623
|
+
module: file.module,
|
|
624
|
+
style: classifyEventStyle(eventName),
|
|
625
|
+
awaited: /\bawait\b/u.test(callText),
|
|
626
|
+
assignedOrReturned: /\breturn\b/u.test(callText) || /(?:^|[\s(])[\w$]+\s*(?::=|=)\s*/u.test(callText),
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
return publishes;
|
|
630
|
+
}
|
|
631
|
+
function extractCsharpSignals(file) {
|
|
632
|
+
const publishes = extractPublishSignalsFromText(file, file.content, 0, csharpPublishPattern);
|
|
633
|
+
const handlers = [];
|
|
634
|
+
for (const match of file.content.matchAll(csharpHandlerPattern)) {
|
|
635
|
+
const eventName = match[1];
|
|
636
|
+
if (!eventName) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
const classStart = match.index ?? 0;
|
|
640
|
+
const classOpenBrace = file.content.indexOf('{', classStart);
|
|
641
|
+
const classEnd = classOpenBrace >= 0 ? findMatchingBrace(file.content, classOpenBrace) : classStart;
|
|
642
|
+
const classBody = classOpenBrace >= 0 ? file.content.slice(classOpenBrace, classEnd + 1) : '';
|
|
643
|
+
handlers.push({
|
|
644
|
+
eventName,
|
|
645
|
+
normalizedName: normalizeEventName(eventName),
|
|
646
|
+
file,
|
|
647
|
+
line: lineNumberForOffset(file, classStart),
|
|
648
|
+
module: file.module,
|
|
649
|
+
style: classifyEventStyle(eventName),
|
|
650
|
+
publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, csharpPublishPattern),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
654
|
+
}
|
|
655
|
+
function extractPythonSignals(file) {
|
|
656
|
+
const publishes = extractPublishSignalsFromText(file, file.content, 0, pythonPublishPattern);
|
|
657
|
+
const handlers = [];
|
|
658
|
+
for (const match of file.content.matchAll(pythonSubscriberPattern)) {
|
|
659
|
+
const eventName = getEventNameFromExpressionText(match[1] ?? '');
|
|
660
|
+
if (!eventName) {
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const blockStart = (match.index ?? 0) + match[0].length;
|
|
664
|
+
const blockEnd = findPythonBlockEnd(file.content, blockStart);
|
|
665
|
+
const block = file.content.slice(blockStart, blockEnd);
|
|
666
|
+
handlers.push({
|
|
667
|
+
eventName,
|
|
668
|
+
normalizedName: normalizeEventName(eventName),
|
|
669
|
+
file,
|
|
670
|
+
line: lineNumberForOffset(file, match.index ?? 0),
|
|
671
|
+
module: file.module,
|
|
672
|
+
style: classifyEventStyle(eventName),
|
|
673
|
+
publishes: extractPublishSignalsFromText(file, block, blockStart, pythonPublishPattern),
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
677
|
+
}
|
|
678
|
+
function extractPhpSignals(file) {
|
|
679
|
+
const publishes = extractPublishSignalsFromText(file, file.content, 0, phpEventPublishPattern);
|
|
680
|
+
const handlers = [];
|
|
681
|
+
for (const match of file.content.matchAll(phpEventHandlerPattern)) {
|
|
682
|
+
const eventName = match[1] ?? match[2];
|
|
683
|
+
if (!eventName) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
const classStart = Math.max(0, (match.index ?? 0) - 300);
|
|
687
|
+
const nextClass = file.content.lastIndexOf('class ', match.index ?? 0);
|
|
688
|
+
const classOpenBrace = nextClass >= 0 ? file.content.indexOf('{', nextClass) : -1;
|
|
689
|
+
const classEnd = classOpenBrace >= 0 ? findMatchingBrace(file.content, classOpenBrace) : match.index ?? 0;
|
|
690
|
+
const classBody = classOpenBrace >= 0 && classEnd > classOpenBrace ? file.content.slice(classOpenBrace, classEnd + 1) : '';
|
|
691
|
+
handlers.push({
|
|
692
|
+
eventName,
|
|
693
|
+
normalizedName: normalizeEventName(eventName),
|
|
694
|
+
file,
|
|
695
|
+
line: lineNumberForOffset(file, match.index ?? 0),
|
|
696
|
+
module: file.module,
|
|
697
|
+
style: classifyEventStyle(eventName),
|
|
698
|
+
publishes: extractPublishSignalsFromText(file, classBody, classOpenBrace >= 0 ? classOpenBrace : classStart, phpEventPublishPattern),
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
702
|
+
}
|
|
703
|
+
function extractGoSignals(file) {
|
|
704
|
+
const publishes = extractPublishSignalsFromText(file, file.content, 0, goPublishPattern);
|
|
705
|
+
const handlers = [];
|
|
706
|
+
for (const match of file.content.matchAll(goHandlerPattern)) {
|
|
707
|
+
const eventName = getEventNameFromExpressionText(match[1] ?? '');
|
|
708
|
+
if (!eventName) {
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
handlers.push({
|
|
712
|
+
eventName,
|
|
713
|
+
normalizedName: normalizeEventName(eventName),
|
|
714
|
+
file,
|
|
715
|
+
line: lineNumberForOffset(file, match.index ?? 0),
|
|
716
|
+
module: file.module,
|
|
717
|
+
style: classifyEventStyle(eventName),
|
|
718
|
+
publishes: [],
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return { publishes, handlers, publicEventNames: new Set() };
|
|
722
|
+
}
|
|
723
|
+
function isTypeScriptLikeExtension(extension) {
|
|
724
|
+
return ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(extension);
|
|
725
|
+
}
|
|
726
|
+
function parseTypeScriptFile(file, astCache) {
|
|
727
|
+
return astCache
|
|
728
|
+
? astCache.parseSourceFile({
|
|
729
|
+
filePath: file.relativePath,
|
|
730
|
+
sourceText: file.content,
|
|
731
|
+
scriptTarget: ts.ScriptTarget.Latest,
|
|
732
|
+
setParentNodes: true,
|
|
733
|
+
})
|
|
734
|
+
: ts.createSourceFile(file.relativePath, file.content, ts.ScriptTarget.Latest, true);
|
|
735
|
+
}
|
|
736
|
+
function resolveRelativeExportTarget(fromFile, moduleSpecifier, filesByPath) {
|
|
737
|
+
if (!moduleSpecifier.startsWith('.')) {
|
|
738
|
+
return undefined;
|
|
739
|
+
}
|
|
740
|
+
const fromDirectory = path.posix.dirname(fromFile.relativePath);
|
|
741
|
+
const basePath = normalizePath(path.posix.normalize(path.posix.join(fromDirectory, moduleSpecifier)));
|
|
742
|
+
const candidates = [
|
|
743
|
+
basePath,
|
|
744
|
+
`${basePath}.ts`,
|
|
745
|
+
`${basePath}.tsx`,
|
|
746
|
+
`${basePath}.js`,
|
|
747
|
+
`${basePath}.jsx`,
|
|
748
|
+
`${basePath}/index.ts`,
|
|
749
|
+
`${basePath}/index.tsx`,
|
|
750
|
+
`${basePath}/index.js`,
|
|
751
|
+
`${basePath}/index.jsx`,
|
|
752
|
+
];
|
|
753
|
+
return candidates.find((candidate) => filesByPath.has(candidate));
|
|
754
|
+
}
|
|
755
|
+
function getConfiguredPublicEntrypoints(contract) {
|
|
756
|
+
const entrypoints = new Set();
|
|
757
|
+
for (const reference of Object.values(contract.modules ?? {})) {
|
|
758
|
+
for (const entrypoint of reference.publicEntrypoints ?? []) {
|
|
759
|
+
entrypoints.add(normalizePath(entrypoint));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return entrypoints;
|
|
763
|
+
}
|
|
764
|
+
function isDefaultPublicEntrypoint(file) {
|
|
765
|
+
const normalized = normalizePath(file.relativePath);
|
|
766
|
+
return /(?:^|\/)index\.(?:ts|tsx|js|jsx|mjs|cjs)$/u.test(normalized);
|
|
767
|
+
}
|
|
768
|
+
function collectPublicTypeScriptEventNames(files, contract, astCache) {
|
|
769
|
+
const filesByPath = new Map(files.map((file) => [normalizePath(file.relativePath), file]));
|
|
770
|
+
const moduleInfoByPath = new Map();
|
|
771
|
+
for (const file of files) {
|
|
772
|
+
if (!isTypeScriptLikeExtension(file.extension)) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
moduleInfoByPath.set(normalizePath(file.relativePath), collectTypeScriptEventModuleInfo(file, parseTypeScriptFile(file, astCache)));
|
|
776
|
+
}
|
|
777
|
+
const configuredEntrypoints = getConfiguredPublicEntrypoints(contract);
|
|
778
|
+
const queue = [...moduleInfoByPath.values()]
|
|
779
|
+
.filter((info) => isDefaultPublicEntrypoint(info.file) ||
|
|
780
|
+
configuredEntrypoints.has(normalizePath(info.file.relativePath)))
|
|
781
|
+
.map((info) => normalizePath(info.file.relativePath));
|
|
782
|
+
const visited = new Set();
|
|
783
|
+
const publicEventNames = new Set();
|
|
784
|
+
while (queue.length > 0) {
|
|
785
|
+
const currentPath = queue.shift();
|
|
786
|
+
if (!currentPath || visited.has(currentPath)) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
visited.add(currentPath);
|
|
790
|
+
const info = moduleInfoByPath.get(currentPath);
|
|
791
|
+
if (!info) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
for (const eventName of info.declaredEventNames) {
|
|
795
|
+
publicEventNames.add(eventName);
|
|
796
|
+
}
|
|
797
|
+
for (const reExport of info.reExports) {
|
|
798
|
+
const targetPath = resolveRelativeExportTarget(info.file, reExport.moduleSpecifier, filesByPath);
|
|
799
|
+
if (!targetPath || visited.has(targetPath)) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (reExport.names && reExport.names.length > 0) {
|
|
803
|
+
const targetInfo = moduleInfoByPath.get(targetPath);
|
|
804
|
+
if (targetInfo) {
|
|
805
|
+
for (const eventName of reExport.names) {
|
|
806
|
+
if (targetInfo.declaredEventNames.has(normalizeEventName(eventName))) {
|
|
807
|
+
publicEventNames.add(normalizeEventName(eventName));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
queue.push(targetPath);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return publicEventNames;
|
|
817
|
+
}
|
|
818
|
+
function collectSignals(files, contract, astCache) {
|
|
819
|
+
const publishes = [];
|
|
820
|
+
const handlers = [];
|
|
821
|
+
const publicEventNames = collectPublicTypeScriptEventNames(files, contract, astCache);
|
|
822
|
+
for (const file of files) {
|
|
823
|
+
let signals;
|
|
824
|
+
switch (file.extension) {
|
|
825
|
+
case '.java':
|
|
826
|
+
signals = extractJavaSignals(file);
|
|
827
|
+
break;
|
|
828
|
+
case '.cs':
|
|
829
|
+
signals = extractCsharpSignals(file);
|
|
830
|
+
break;
|
|
831
|
+
case '.py':
|
|
832
|
+
signals = extractPythonSignals(file);
|
|
833
|
+
break;
|
|
834
|
+
case '.php':
|
|
835
|
+
signals = extractPhpSignals(file);
|
|
836
|
+
break;
|
|
837
|
+
case '.go':
|
|
838
|
+
signals = extractGoSignals(file);
|
|
839
|
+
break;
|
|
840
|
+
default:
|
|
841
|
+
signals = extractTypeScriptSignals(file, astCache);
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
publishes.push(...signals.publishes);
|
|
845
|
+
handlers.push(...signals.handlers);
|
|
846
|
+
}
|
|
847
|
+
return { publishes, handlers, publicEventNames };
|
|
848
|
+
}
|
|
849
|
+
function buildEventFindings(signals) {
|
|
850
|
+
const findings = [];
|
|
851
|
+
const handledNames = new Set(signals.handlers.map((handler) => handler.normalizedName));
|
|
852
|
+
for (const publish of signals.publishes) {
|
|
853
|
+
if (!handledNames.has(publish.normalizedName)) {
|
|
854
|
+
if (signals.publicEventNames.has(publish.normalizedName)) {
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
findings.push(createFinding({
|
|
858
|
+
id: RuleIds.UNHANDLED_EVENT,
|
|
859
|
+
message: `Published event '${publish.eventName}' has no statically visible handler/listener.`,
|
|
860
|
+
signal: publish,
|
|
861
|
+
}));
|
|
862
|
+
}
|
|
863
|
+
if (publish.assignedOrReturned) {
|
|
864
|
+
findings.push(createFinding({
|
|
865
|
+
id: RuleIds.SYNC_EVENT_DEPENDENCY,
|
|
866
|
+
message: `Event '${publish.eventName}' appears to be used as a synchronous request/response dependency.`,
|
|
867
|
+
signal: publish,
|
|
868
|
+
}));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const handlerByName = new Map(signals.handlers.map((handler) => [handler.normalizedName, handler]));
|
|
872
|
+
for (const handler of signals.handlers) {
|
|
873
|
+
for (const publish of handler.publishes) {
|
|
874
|
+
const nextHandler = handlerByName.get(publish.normalizedName);
|
|
875
|
+
if (!nextHandler) {
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (nextHandler.publishes.some((nextPublish) => nextPublish.normalizedName === handler.normalizedName)) {
|
|
879
|
+
findings.push(createFinding({
|
|
880
|
+
id: RuleIds.CIRCULAR_EVENT_CHAIN,
|
|
881
|
+
message: `Circular event chain risk: handler for '${handler.eventName}' publishes '${publish.eventName}', whose handler publishes '${handler.eventName}'.`,
|
|
882
|
+
signal: publish,
|
|
883
|
+
target: `${handler.eventName}<->${publish.eventName}`,
|
|
884
|
+
}));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const styleGroups = new Map();
|
|
889
|
+
const representative = new Map();
|
|
890
|
+
for (const signal of [...signals.publishes, ...signals.handlers]) {
|
|
891
|
+
const existing = styleGroups.get(signal.normalizedName) ?? new Set();
|
|
892
|
+
existing.add(signal.style);
|
|
893
|
+
styleGroups.set(signal.normalizedName, existing);
|
|
894
|
+
representative.set(signal.normalizedName, signal);
|
|
895
|
+
}
|
|
896
|
+
for (const [eventName, styles] of styleGroups.entries()) {
|
|
897
|
+
if (styles.size >= 3) {
|
|
898
|
+
const signal = representative.get(eventName);
|
|
899
|
+
if (signal) {
|
|
900
|
+
findings.push(createFinding({
|
|
901
|
+
id: RuleIds.NAMING_INCONSISTENCY,
|
|
902
|
+
message: `Event '${eventName}' uses mixed naming styles (${[...styles].sort().join(', ')}).`,
|
|
903
|
+
signal,
|
|
904
|
+
target: eventName,
|
|
905
|
+
}));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
return findings
|
|
910
|
+
.sort((left, right) => (left.sourceFile ?? '').localeCompare(right.sourceFile ?? '') || left.id.localeCompare(right.id))
|
|
911
|
+
.filter((finding, index, values) => {
|
|
912
|
+
const previous = values[index - 1];
|
|
913
|
+
return !previous || previous.id !== finding.id || previous.sourceFile !== finding.sourceFile || previous.target !== finding.target;
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
async function detectEventArchitectureFindings(input) {
|
|
917
|
+
const started = Date.now();
|
|
918
|
+
const files = await collectSourceFiles(input.workspaceRoot, input.contract, input.sourceInventory);
|
|
919
|
+
(0, validationProfiling_1.emitValidationProfile)('AP-EVT analyzer file scan', Date.now() - started, [
|
|
920
|
+
{ name: 'files', value: files.length },
|
|
921
|
+
]);
|
|
922
|
+
const signals = collectSignals(files, input.contract, input.astCache);
|
|
923
|
+
return buildEventFindings(signals).filter((finding) => !(input.disabledRuleIds?.has(finding.id) ?? false));
|
|
924
|
+
}
|
|
925
|
+
//# sourceMappingURL=eventArchitectureAnalysis.js.map
|