codebase-onboarder 0.6.0 → 0.7.0
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/README.md +174 -46
- package/bin/build-if-source.js +23 -0
- package/cli/commands.d.ts +26 -0
- package/cli/commands.js +836 -811
- package/cli/commands.js.map +1 -0
- package/cli/explorer/advanced.d.ts +14 -0
- package/cli/explorer/advanced.js +321 -329
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +384 -313
- package/cli/explorer/app.js.map +1 -0
- package/cli/explorer/assistant.d.ts +9 -0
- package/cli/explorer/assistant.js +58 -0
- package/cli/explorer/assistant.js.map +1 -0
- package/cli/explorer/commands.d.ts +17 -0
- package/cli/explorer/commands.js +368 -302
- package/cli/explorer/commands.js.map +1 -0
- package/cli/explorer/featureViews.d.ts +124 -0
- package/cli/explorer/featureViews.js +179 -0
- package/cli/explorer/featureViews.js.map +1 -0
- package/cli/explorer/github.d.ts +21 -0
- package/cli/explorer/github.js +42 -44
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +90 -95
- package/cli/explorer/graphs.js.map +1 -0
- package/cli/explorer/picker.d.ts +22 -0
- package/cli/explorer/picker.js +150 -0
- package/cli/explorer/picker.js.map +1 -0
- package/cli/explorer/report.d.ts +85 -0
- package/cli/explorer/report.js +60 -0
- package/cli/explorer/report.js.map +1 -0
- package/cli/explorer/session.d.ts +449 -0
- package/cli/explorer/session.js +143 -144
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +465 -482
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +28 -44
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +214 -161
- package/cli/main.js.map +1 -0
- package/cli/prompt.d.ts +5 -0
- package/cli/prompt.js +84 -80
- package/cli/prompt.js.map +1 -0
- package/cli/scanOptions.d.ts +2 -0
- package/cli/scanOptions.js +11 -0
- package/cli/scanOptions.js.map +1 -0
- package/cli/ui.d.ts +33 -0
- package/cli/ui.js +35 -44
- package/cli/ui.js.map +1 -0
- package/cli/wizard.d.ts +187 -0
- package/cli/wizard.js +304 -284
- package/cli/wizard.js.map +1 -0
- package/package.json +15 -4
- package/public/app.d.ts +1 -0
- package/public/app.js +1057 -1009
- package/public/app.js.map +1 -0
- package/public/browser.d.ts +68 -0
- package/public/browser.js +2 -0
- package/public/browser.js.map +1 -0
- package/public/index.html +30 -13
- package/public/js/about.d.ts +6 -0
- package/public/js/about.js +152 -172
- package/public/js/about.js.map +1 -0
- package/public/js/aiDraft.d.ts +3 -0
- package/public/js/aiDraft.js +116 -124
- package/public/js/aiDraft.js.map +1 -0
- package/public/js/analysisPanel.d.ts +7 -0
- package/public/js/analysisPanel.js +166 -166
- package/public/js/analysisPanel.js.map +1 -0
- package/public/js/analysisReport.d.ts +38 -0
- package/public/js/analysisReport.js +105 -110
- package/public/js/analysisReport.js.map +1 -0
- package/public/js/api.d.ts +26 -0
- package/public/js/api.js +176 -167
- package/public/js/api.js.map +1 -0
- package/public/js/atlas.d.ts +6 -0
- package/public/js/atlas.js +50 -58
- package/public/js/atlas.js.map +1 -0
- package/public/js/blameView.d.ts +2 -0
- package/public/js/blameView.js +36 -37
- package/public/js/blameView.js.map +1 -0
- package/public/js/codeTab.d.ts +3 -0
- package/public/js/codeTab.js +175 -180
- package/public/js/codeTab.js.map +1 -0
- package/public/js/codeViewer.d.ts +5 -0
- package/public/js/codeViewer.js +162 -150
- package/public/js/codeViewer.js.map +1 -0
- package/public/js/components/FileChip.d.ts +9 -0
- package/public/js/components/FileChip.js +45 -49
- package/public/js/components/FileChip.js.map +1 -0
- package/public/js/components/MetricSparkline.d.ts +7 -0
- package/public/js/components/MetricSparkline.js +35 -44
- package/public/js/components/MetricSparkline.js.map +1 -0
- package/public/js/components/RiskBadge.d.ts +9 -0
- package/public/js/components/RiskBadge.js +55 -63
- package/public/js/components/RiskBadge.js.map +1 -0
- package/public/js/deepAnalysisView.d.ts +99 -0
- package/public/js/deepAnalysisView.js +282 -302
- package/public/js/deepAnalysisView.js.map +1 -0
- package/public/js/diagramPane.d.ts +8 -0
- package/public/js/diagramPane.js +133 -135
- package/public/js/diagramPane.js.map +1 -0
- package/public/js/diffView.d.ts +6 -0
- package/public/js/diffView.js +222 -209
- package/public/js/diffView.js.map +1 -0
- package/public/js/docsView.d.ts +2 -0
- package/public/js/docsView.js +441 -442
- package/public/js/docsView.js.map +1 -0
- package/public/js/dom.d.ts +189 -0
- package/public/js/dom.js +5 -0
- package/public/js/dom.js.map +1 -0
- package/public/js/fileSourceBrowser.d.ts +5 -0
- package/public/js/fileSourceBrowser.js +33 -41
- package/public/js/fileSourceBrowser.js.map +1 -0
- package/public/js/flameGraph.d.ts +5 -0
- package/public/js/flameGraph.js +62 -72
- package/public/js/flameGraph.js.map +1 -0
- package/public/js/forceGraph.d.ts +10 -0
- package/public/js/forceGraph.js +581 -556
- package/public/js/forceGraph.js.map +1 -0
- package/public/js/heatmap.d.ts +4 -0
- package/public/js/heatmap.js +142 -164
- package/public/js/heatmap.js.map +1 -0
- package/public/js/highlight.d.ts +2 -0
- package/public/js/highlight.js +116 -107
- package/public/js/highlight.js.map +1 -0
- package/public/js/html.d.ts +2 -0
- package/public/js/html.js +14 -44
- package/public/js/html.js.map +1 -0
- package/public/js/insightsView.d.ts +5 -0
- package/public/js/insightsView.js +93 -111
- package/public/js/insightsView.js.map +1 -0
- package/public/js/inspector.d.ts +37 -0
- package/public/js/inspector.js +416 -445
- package/public/js/inspector.js.map +1 -0
- package/public/js/llm.d.ts +113 -0
- package/public/js/llm.js +233 -272
- package/public/js/llm.js.map +1 -0
- package/public/js/mapView.d.ts +3 -0
- package/public/js/mapView.js +121 -120
- package/public/js/mapView.js.map +1 -0
- package/public/js/markdown.d.ts +3 -0
- package/public/js/markdown.js +84 -91
- package/public/js/markdown.js.map +1 -0
- package/public/js/mindmap.d.ts +32 -0
- package/public/js/mindmap.js +244 -259
- package/public/js/mindmap.js.map +1 -0
- package/public/js/mobilePanels.d.ts +5 -0
- package/public/js/mobilePanels.js +50 -0
- package/public/js/mobilePanels.js.map +1 -0
- package/public/js/repoFiles.d.ts +12 -0
- package/public/js/repoFiles.js +28 -27
- package/public/js/repoFiles.js.map +1 -0
- package/public/js/reviewView.d.ts +28 -0
- package/public/js/reviewView.js +256 -0
- package/public/js/reviewView.js.map +1 -0
- package/public/js/sbomView.d.ts +3 -0
- package/public/js/sbomView.js +59 -67
- package/public/js/sbomView.js.map +1 -0
- package/public/js/scanCache.d.ts +16 -0
- package/public/js/scanCache.js +67 -65
- package/public/js/scanCache.js.map +1 -0
- package/public/js/search.d.ts +7 -0
- package/public/js/search.js +868 -800
- package/public/js/search.js.map +1 -0
- package/public/js/searchLocal.d.ts +42 -0
- package/public/js/searchLocal.js +87 -0
- package/public/js/searchLocal.js.map +1 -0
- package/public/js/serverSettings.d.ts +5 -0
- package/public/js/serverSettings.js +153 -164
- package/public/js/serverSettings.js.map +1 -0
- package/public/js/state.d.ts +798 -0
- package/public/js/state.js +114 -122
- package/public/js/state.js.map +1 -0
- package/public/js/tour.d.ts +6 -0
- package/public/js/tour.js +5 -7
- package/public/js/tour.js.map +1 -0
- package/public/js/transitions.d.ts +2 -0
- package/public/js/transitions.js +9 -10
- package/public/js/transitions.js.map +1 -0
- package/public/js/tree.d.ts +12 -0
- package/public/js/tree.js +157 -154
- package/public/js/tree.js.map +1 -0
- package/public/js/workflowsView.d.ts +3 -0
- package/public/js/workflowsView.js +15 -22
- package/public/js/workflowsView.js.map +1 -0
- package/public/search.css +495 -0
- package/public/styles.css +163 -346
- package/public/workbench.css +166 -0
- package/server/apiAuth.d.ts +3 -0
- package/server/apiAuth.js +18 -20
- package/server/apiAuth.js.map +1 -0
- package/server/apiDiff.d.ts +2 -0
- package/server/apiDiff.js +20 -18
- package/server/apiDiff.js.map +1 -0
- package/server/apiDocs.d.ts +2 -0
- package/server/apiDocs.js +38 -40
- package/server/apiDocs.js.map +1 -0
- package/server/apiFile.d.ts +1 -0
- package/server/apiFile.js +14 -17
- package/server/apiFile.js.map +1 -0
- package/server/apiGitBlame.d.ts +2 -0
- package/server/apiGitBlame.js +50 -51
- package/server/apiGitBlame.js.map +1 -0
- package/server/apiMcp.d.ts +4 -0
- package/server/apiMcp.js +64 -66
- package/server/apiMcp.js.map +1 -0
- package/server/apiReview.d.ts +2 -0
- package/server/apiReview.js +45 -0
- package/server/apiReview.js.map +1 -0
- package/server/apiScan.d.ts +5 -0
- package/server/apiScan.js +90 -71
- package/server/apiScan.js.map +1 -0
- package/server/apiSearch.d.ts +33 -0
- package/server/apiSearch.js +184 -200
- package/server/apiSearch.js.map +1 -0
- package/server/apiSettings.d.ts +4 -0
- package/server/apiSettings.js +84 -85
- package/server/apiSettings.js.map +1 -0
- package/server/apiTools.d.ts +3 -0
- package/server/apiTools.js +55 -56
- package/server/apiTools.js.map +1 -0
- package/server/auth.d.ts +5 -0
- package/server/auth.js +54 -51
- package/server/auth.js.map +1 -0
- package/server/config.d.ts +42 -0
- package/server/config.js +189 -194
- package/server/config.js.map +1 -0
- package/server/daemon.d.ts +12 -0
- package/server/daemon.js +124 -114
- package/server/daemon.js.map +1 -0
- package/server/fileSourceNode.d.ts +2 -0
- package/server/fileSourceNode.js +34 -36
- package/server/fileSourceNode.js.map +1 -0
- package/server/gitClone.d.ts +9 -0
- package/server/gitClone.js +59 -61
- package/server/gitClone.js.map +1 -0
- package/server/gitDiff.d.ts +36 -0
- package/server/gitDiff.js +271 -169
- package/server/gitDiff.js.map +1 -0
- package/server/gitHistory.d.ts +12 -0
- package/server/gitHistory.js +80 -80
- package/server/gitHistory.js.map +1 -0
- package/server/gitRemote.d.ts +12 -0
- package/server/gitRemote.js +37 -38
- package/server/gitRemote.js.map +1 -0
- package/server/htmlText.d.ts +4 -0
- package/server/htmlText.js +16 -23
- package/server/htmlText.js.map +1 -0
- package/server/http.d.ts +5 -0
- package/server/http.js +27 -30
- package/server/http.js.map +1 -0
- package/server/httpGuards.d.ts +2 -0
- package/server/httpGuards.js +47 -43
- package/server/httpGuards.js.map +1 -0
- package/server/https.d.ts +34 -0
- package/server/https.js +121 -111
- package/server/https.js.map +1 -0
- package/server/index.d.ts +40 -0
- package/server/index.js +139 -147
- package/server/index.js.map +1 -0
- package/server/layout.d.ts +26 -0
- package/server/layout.js +59 -64
- package/server/layout.js.map +1 -0
- package/server/llmProxy.d.ts +3 -0
- package/server/llmProxy.js +137 -135
- package/server/llmProxy.js.map +1 -0
- package/server/logger.d.ts +38 -0
- package/server/logger.js +198 -211
- package/server/logger.js.map +1 -0
- package/server/mcp/analysis.d.ts +29 -0
- package/server/mcp/analysis.js +99 -120
- package/server/mcp/analysis.js.map +1 -0
- package/server/mcp/http.d.ts +9 -0
- package/server/mcp/http.js +128 -137
- package/server/mcp/http.js.map +1 -0
- package/server/mcp/runner.d.ts +68 -0
- package/server/mcp/runner.js +208 -218
- package/server/mcp/runner.js.map +1 -0
- package/server/mcp/server.d.ts +29 -0
- package/server/mcp/server.js +166 -179
- package/server/mcp/server.js.map +1 -0
- package/server/mcp/standalone.d.ts +1 -0
- package/server/mcp/standalone.js +8 -10
- package/server/mcp/standalone.js.map +1 -0
- package/server/mcp/tools.d.ts +2020 -0
- package/server/mcp/tools.js +523 -574
- package/server/mcp/tools.js.map +1 -0
- package/server/paths.d.ts +3 -0
- package/server/paths.js +17 -15
- package/server/paths.js.map +1 -0
- package/server/pidfile.d.ts +9 -0
- package/server/pidfile.js +43 -45
- package/server/pidfile.js.map +1 -0
- package/server/review.d.ts +5 -0
- package/server/review.js +35 -0
- package/server/review.js.map +1 -0
- package/server/router.d.ts +33 -0
- package/server/router.js +191 -197
- package/server/router.js.map +1 -0
- package/server/scanRequest.d.ts +3 -0
- package/server/scanRequest.js +36 -0
- package/server/scanRequest.js.map +1 -0
- package/server/searchIndex.d.ts +17 -0
- package/server/searchIndex.js +83 -80
- package/server/searchIndex.js.map +1 -0
- package/server/sessions.d.ts +14 -0
- package/server/sessions.js +62 -70
- package/server/sessions.js.map +1 -0
- package/server/startup.d.ts +118 -0
- package/server/startup.js +143 -148
- package/server/startup.js.map +1 -0
- package/server/static.d.ts +6 -0
- package/server/static.js +28 -31
- package/server/static.js.map +1 -0
- package/server/tools/install.d.ts +4 -0
- package/server/tools/install.js +176 -185
- package/server/tools/install.js.map +1 -0
- package/server/tools/parse.d.ts +8 -0
- package/server/tools/parse.js +112 -114
- package/server/tools/parse.js.map +1 -0
- package/server/tools/platform.d.ts +9 -0
- package/server/tools/platform.js +49 -47
- package/server/tools/platform.js.map +1 -0
- package/server/tools/registry.d.ts +137 -0
- package/server/tools/registry.js +173 -172
- package/server/tools/registry.js.map +1 -0
- package/server/tools/scan.d.ts +34 -0
- package/server/tools/scan.js +89 -98
- package/server/tools/scan.js.map +1 -0
- package/server/tools.d.ts +34 -0
- package/server/tools.js +94 -107
- package/server/tools.js.map +1 -0
- package/server/tunnel.d.ts +29 -0
- package/server/tunnel.js +61 -66
- package/server/tunnel.js.map +1 -0
- package/shared/analyzer/docs.d.ts +16 -0
- package/shared/analyzer/docs.js +99 -93
- package/shared/analyzer/docs.js.map +1 -0
- package/shared/analyzer/explainLocal.d.ts +4 -0
- package/shared/analyzer/explainLocal.js +122 -142
- package/shared/analyzer/explainLocal.js.map +1 -0
- package/shared/analyzer/github.d.ts +37 -0
- package/shared/analyzer/github.js +41 -43
- package/shared/analyzer/github.js.map +1 -0
- package/shared/analyzer/graph.d.ts +41 -0
- package/shared/analyzer/graph.js +393 -400
- package/shared/analyzer/graph.js.map +1 -0
- package/shared/analyzer/health.d.ts +33 -0
- package/shared/analyzer/health.js +197 -193
- package/shared/analyzer/health.js.map +1 -0
- package/shared/analyzer/history.d.ts +29 -0
- package/shared/analyzer/history.js +110 -111
- package/shared/analyzer/history.js.map +1 -0
- package/shared/analyzer/jsonConfig.d.ts +2 -0
- package/shared/analyzer/jsonConfig.js +61 -0
- package/shared/analyzer/jsonConfig.js.map +1 -0
- package/shared/analyzer/languages/csharp.d.ts +26 -0
- package/shared/analyzer/languages/csharp.js +83 -88
- package/shared/analyzer/languages/csharp.js.map +1 -0
- package/shared/analyzer/languages/generic.d.ts +23 -0
- package/shared/analyzer/languages/generic.js +94 -105
- package/shared/analyzer/languages/generic.js.map +1 -0
- package/shared/analyzer/languages/go.d.ts +24 -0
- package/shared/analyzer/languages/go.js +45 -54
- package/shared/analyzer/languages/go.js.map +1 -0
- package/shared/analyzer/languages/index.d.ts +6 -0
- package/shared/analyzer/languages/index.js +19 -20
- package/shared/analyzer/languages/index.js.map +1 -0
- package/shared/analyzer/languages/java.d.ts +20 -0
- package/shared/analyzer/languages/java.js +73 -78
- package/shared/analyzer/languages/java.js.map +1 -0
- package/shared/analyzer/languages/javascript.d.ts +12 -0
- package/shared/analyzer/languages/javascript.js +258 -209
- package/shared/analyzer/languages/javascript.js.map +1 -0
- package/shared/analyzer/languages/python.d.ts +23 -0
- package/shared/analyzer/languages/python.js +92 -100
- package/shared/analyzer/languages/python.js.map +1 -0
- package/shared/analyzer/languages/rust.d.ts +17 -0
- package/shared/analyzer/languages/rust.js +96 -98
- package/shared/analyzer/languages/rust.js.map +1 -0
- package/shared/analyzer/languages/typescript.d.ts +11 -0
- package/shared/analyzer/languages/typescript.js +117 -116
- package/shared/analyzer/languages/typescript.js.map +1 -0
- package/shared/analyzer/licenses.d.ts +18 -0
- package/shared/analyzer/licenses.js +130 -133
- package/shared/analyzer/licenses.js.map +1 -0
- package/shared/analyzer/metrics.d.ts +20 -0
- package/shared/analyzer/metrics.js +112 -104
- package/shared/analyzer/metrics.js.map +1 -0
- package/shared/analyzer/pathUtil.d.ts +10 -0
- package/shared/analyzer/pathUtil.js +29 -35
- package/shared/analyzer/pathUtil.js.map +1 -0
- package/shared/analyzer/patterns.d.ts +11 -0
- package/shared/analyzer/patterns.js +231 -253
- package/shared/analyzer/patterns.js.map +1 -0
- package/shared/analyzer/scan.d.ts +62 -0
- package/shared/analyzer/scan.js +451 -442
- package/shared/analyzer/scan.js.map +1 -0
- package/shared/analyzer/scanControl.d.ts +11 -0
- package/shared/analyzer/scanControl.js +62 -0
- package/shared/analyzer/scanControl.js.map +1 -0
- package/shared/analyzer/security.d.ts +18 -0
- package/shared/analyzer/security.js +159 -159
- package/shared/analyzer/security.js.map +1 -0
- package/shared/analyzer/services.d.ts +5 -0
- package/shared/analyzer/services.js +141 -138
- package/shared/analyzer/services.js.map +1 -0
- package/shared/analyzer/stack.d.ts +6 -0
- package/shared/analyzer/stack.js +134 -141
- package/shared/analyzer/stack.js.map +1 -0
- package/shared/analyzer/tour.d.ts +2 -0
- package/shared/analyzer/tour.js +45 -53
- package/shared/analyzer/tour.js.map +1 -0
- package/shared/analyzer/util.d.ts +8 -0
- package/shared/analyzer/util.js +130 -76
- package/shared/analyzer/util.js.map +1 -0
- package/shared/analyzer/workflows.d.ts +8 -0
- package/shared/analyzer/workflows.js +139 -134
- package/shared/analyzer/workflows.js.map +1 -0
- package/shared/contracts.d.ts +250 -0
- package/shared/contracts.js +2 -0
- package/shared/contracts.js.map +1 -0
- package/shared/diagram/aiFacts.d.ts +35 -0
- package/shared/diagram/aiFacts.js +102 -105
- package/shared/diagram/aiFacts.js.map +1 -0
- package/shared/diagram/aiMermaid.d.ts +5 -0
- package/shared/diagram/aiMermaid.js +46 -45
- package/shared/diagram/aiMermaid.js.map +1 -0
- package/shared/diagram/atlas.d.ts +16 -0
- package/shared/diagram/atlas.js +51 -60
- package/shared/diagram/atlas.js.map +1 -0
- package/shared/diagram/mermaid.d.ts +43 -0
- package/shared/diagram/mermaid.js +348 -370
- package/shared/diagram/mermaid.js.map +1 -0
- package/shared/review/contracts.d.ts +95 -0
- package/shared/review/contracts.js +2 -0
- package/shared/review/contracts.js.map +1 -0
- package/shared/review/review.d.ts +10 -0
- package/shared/review/review.js +184 -0
- package/shared/review/review.js.map +1 -0
- package/shared/search/query.d.ts +32 -0
- package/shared/search/query.js +395 -388
- package/shared/search/query.js.map +1 -0
|
@@ -7,140 +7,139 @@
|
|
|
7
7
|
// Everything is keyed on file path, which is already the join key the rest of
|
|
8
8
|
// the engine speaks — complexity comes over from scan.files, structural edges
|
|
9
9
|
// from scan.edges, and the two views of "these change together" sit side by side.
|
|
10
|
-
|
|
11
10
|
import { baseName } from './pathUtil.js';
|
|
12
|
-
|
|
13
11
|
// Commits touching more files than this are skipped for pair counting, not for
|
|
14
12
|
// churn — a 900-file reformat or a mass rename would otherwise contribute
|
|
15
13
|
// C(900,2) ≈ 400k pairs of noise to co-change while saying nothing about
|
|
16
14
|
// coupling. Churn still counts them; every file in them was really touched.
|
|
17
15
|
const MAX_COMMIT_FILES_FOR_PAIRS = 50;
|
|
18
|
-
|
|
19
16
|
// Once this many distinct pairs are tracked, new pairs stop being recorded and
|
|
20
17
|
// only existing ones keep counting. A hard bound on memory for repos with long
|
|
21
18
|
// histories of small commits; the top of the list is decided well before this.
|
|
22
19
|
const MAX_TRACKED_PAIRS = 5000;
|
|
23
|
-
|
|
24
20
|
export function analyzeHistory(scan, commits, opts = {}) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
21
|
+
const totalCommits = opts.totalCommits ?? commits.length;
|
|
22
|
+
const truncated = totalCommits > commits.length;
|
|
23
|
+
// ---- per-file rollup -----------------------------------------------------
|
|
24
|
+
// One pass over every commit's file list. Authors are identified by email —
|
|
25
|
+
// names collide and change; the email is what git itself keys identity on.
|
|
26
|
+
const churn = new Map(); // path -> commit count
|
|
27
|
+
const authorsOf = new Map(); // path -> Map(email -> display name)
|
|
28
|
+
const firstSeen = new Map(); // path -> earliest commit date (ISO strings sort)
|
|
29
|
+
const lastTouched = new Map();
|
|
30
|
+
const authorCommits = new Map(); // email -> { name, commits }
|
|
31
|
+
let pathsGone = 0;
|
|
32
|
+
for (const c of commits) {
|
|
33
|
+
const date = c.date || '';
|
|
34
|
+
const known = authorCommits.get(c.author?.email);
|
|
35
|
+
if (known)
|
|
36
|
+
known.commits++;
|
|
37
|
+
else if (c.author?.email)
|
|
38
|
+
authorCommits.set(c.author.email, { name: c.author.name || c.author.email, commits: 1 });
|
|
39
|
+
for (const p of c.files || []) {
|
|
40
|
+
churn.set(p, (churn.get(p) || 0) + 1);
|
|
41
|
+
if (!firstSeen.has(p) || date < firstSeen.get(p))
|
|
42
|
+
firstSeen.set(p, date);
|
|
43
|
+
if (!lastTouched.has(p) || date > lastTouched.get(p))
|
|
44
|
+
lastTouched.set(p, date);
|
|
45
|
+
let byEmail = authorsOf.get(p);
|
|
46
|
+
if (!byEmail) {
|
|
47
|
+
byEmail = new Map();
|
|
48
|
+
authorsOf.set(p, byEmail);
|
|
49
|
+
}
|
|
50
|
+
if (!byEmail.has(c.author?.email))
|
|
51
|
+
byEmail.set(c.author?.email, c.author?.name || c.author?.email || '?');
|
|
52
|
+
}
|
|
51
53
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
// ---- join with the scan ---------------------------------------------------
|
|
55
|
+
// Complexity lives on scan.files; history paths that are not in the scan are
|
|
56
|
+
// files deleted or renamed inside the window. They had churn once — counted,
|
|
57
|
+
// then set aside rather than silently mixed into rankings of files that no
|
|
58
|
+
// longer exist.
|
|
59
|
+
const cxOf = new Map(scan.files.map((f) => [f.path, f.complexity || 1]));
|
|
60
|
+
for (const p of churn.keys())
|
|
61
|
+
if (!cxOf.has(p))
|
|
62
|
+
pathsGone++;
|
|
63
|
+
const live = [...churn.entries()].filter(([p]) => cxOf.has(p));
|
|
64
|
+
const maxChurn = Math.max(1, ...live.map(([, n]) => n));
|
|
65
|
+
const maxCx = Math.max(1, ...live.map(([p]) => cxOf.get(p)));
|
|
66
|
+
const perFile = live.map(([p, n]) => {
|
|
67
|
+
const authors = authorsOf.get(p).size;
|
|
68
|
+
return {
|
|
69
|
+
path: p,
|
|
70
|
+
name: baseName(p),
|
|
71
|
+
churn: n,
|
|
72
|
+
authors,
|
|
73
|
+
solo: authors === 1,
|
|
74
|
+
firstSeen: firstSeen.get(p),
|
|
75
|
+
lastTouched: lastTouched.get(p),
|
|
76
|
+
complexity: cxOf.get(p),
|
|
77
|
+
// The cross-tab: both axes normalized against the repo's own worst, so
|
|
78
|
+
// the score reads "how far up both columns at once" and a file maxing
|
|
79
|
+
// one axis but not the other cannot outrank one high on both.
|
|
80
|
+
hotspot: Math.round(100 * (n / maxChurn) * (cxOf.get(p) / maxCx)),
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));
|
|
84
|
+
const byPath = {};
|
|
85
|
+
for (const row of perFile)
|
|
86
|
+
byPath[row.path] = row;
|
|
87
|
+
const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;
|
|
88
|
+
const dates = commits.map((c) => c.date).filter(Boolean);
|
|
68
89
|
return {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
available: true,
|
|
91
|
+
commitCount: commits.length,
|
|
92
|
+
totalCommits,
|
|
93
|
+
truncated,
|
|
94
|
+
// The raw commit list, not just the rollups. The insights view draws a
|
|
95
|
+
// 52-week heatmap and a punch card from per-commit dates; without the
|
|
96
|
+
// original array, that view rendered an empty calendar even when history
|
|
97
|
+
// was fully available. The cost is the same parsed commits we already hold
|
|
98
|
+
// — `parseGitLog` capped at 2,000 — so duplication here is honest.
|
|
99
|
+
commits,
|
|
100
|
+
authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),
|
|
101
|
+
firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,
|
|
102
|
+
lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,
|
|
103
|
+
perFile,
|
|
104
|
+
byPath,
|
|
105
|
+
soloFiles,
|
|
106
|
+
coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),
|
|
107
|
+
pathsGone,
|
|
81
108
|
};
|
|
82
|
-
});
|
|
83
|
-
perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));
|
|
84
|
-
|
|
85
|
-
const byPath = {};
|
|
86
|
-
for (const row of perFile) byPath[row.path] = row;
|
|
87
|
-
const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;
|
|
88
|
-
|
|
89
|
-
const dates = commits.map((c) => c.date).filter(Boolean);
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
available: true,
|
|
93
|
-
commitCount: commits.length,
|
|
94
|
-
totalCommits,
|
|
95
|
-
truncated,
|
|
96
|
-
// The raw commit list, not just the rollups. The insights view draws a
|
|
97
|
-
// 52-week heatmap and a punch card from per-commit dates; without the
|
|
98
|
-
// original array, that view rendered an empty calendar even when history
|
|
99
|
-
// was fully available. The cost is the same parsed commits we already hold
|
|
100
|
-
// — `parseGitLog` capped at 2,000 — so duplication here is honest.
|
|
101
|
-
commits,
|
|
102
|
-
authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),
|
|
103
|
-
firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,
|
|
104
|
-
lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,
|
|
105
|
-
perFile,
|
|
106
|
-
byPath,
|
|
107
|
-
soloFiles,
|
|
108
|
-
coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),
|
|
109
|
-
pathsGone,
|
|
110
|
-
};
|
|
111
109
|
}
|
|
112
|
-
|
|
113
110
|
// Files that keep turning up in the same commit without importing each other.
|
|
114
111
|
// The structural coupling matrix already shows what imports what; this is the
|
|
115
112
|
// contrast — changes that travel together for reasons the import graph does
|
|
116
113
|
// not know about (shared schema, feature pairs, copy-paste siblings).
|
|
117
114
|
function coChangePairs(commits, structuralEdges) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
115
|
+
const pairs = new Map(); // 'a|b' (sorted) -> count
|
|
116
|
+
for (const c of commits) {
|
|
117
|
+
const files = c.files || [];
|
|
118
|
+
if (files.length < 2 || files.length > MAX_COMMIT_FILES_FOR_PAIRS)
|
|
119
|
+
continue;
|
|
120
|
+
for (let i = 0; i < files.length; i++) {
|
|
121
|
+
for (let j = i + 1; j < files.length; j++) {
|
|
122
|
+
const a = files[i] < files[j] ? files[i] : files[j];
|
|
123
|
+
const b = files[i] < files[j] ? files[j] : files[i];
|
|
124
|
+
if (structuralEdges.has(a + '->' + b) || structuralEdges.has(b + '->' + a))
|
|
125
|
+
continue;
|
|
126
|
+
const key = a + '|' + b;
|
|
127
|
+
const seen = pairs.get(key);
|
|
128
|
+
if (seen)
|
|
129
|
+
seen.count++;
|
|
130
|
+
else if (pairs.size < MAX_TRACKED_PAIRS)
|
|
131
|
+
pairs.set(key, { a, b, count: 1 });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))
|
|
139
|
-
.slice(0, 20);
|
|
135
|
+
return [...pairs.values()]
|
|
136
|
+
.filter((p) => p.count >= 2) // once is coincidence; the plan is about habits
|
|
137
|
+
.sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))
|
|
138
|
+
.slice(0, 20);
|
|
140
139
|
}
|
|
141
|
-
|
|
142
140
|
// The shape callers should hold when there is nothing to analyze — same fields,
|
|
143
141
|
// honest reason. Views render this instead of inventing zeros.
|
|
144
142
|
export function unavailableHistory(reason) {
|
|
145
|
-
|
|
143
|
+
return { available: false, reason };
|
|
146
144
|
}
|
|
145
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/shared/analyzer/history.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,+EAA+E;AAC/E,+EAA+E;AAC/E,uDAAuD;AACvD,EAAE;AACF,mFAAmF;AACnF,8EAA8E;AAC9E,8EAA8E;AAC9E,kFAAkF;AAElF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,+EAA+E;AAC/E,0EAA0E;AAC1E,yEAAyE;AACzE,4EAA4E;AAC5E,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAEtC,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,MAAM,UAAU,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAwB,EAAE;IAC1E,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IACzD,MAAM,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAEhD,6EAA6E;IAC7E,4EAA4E;IAC5E,2EAA2E;IAC3E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,CAAK,uBAAuB;IACpD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,qCAAqC;IAClE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,kDAAkD;IAC/E,MAAM,WAAW,GAAG,IAAI,GAAG,EAAE,CAAC;IAC9B,MAAM,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,6BAA6B;IAC9D,IAAI,SAAS,GAAG,CAAC,CAAC;IAElB,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACjD,IAAI,KAAK;YAAE,KAAK,CAAC,OAAO,EAAE,CAAC;aACtB,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK;YAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC;QAEnH,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;YAC9B,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YACzE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAC/E,IAAI,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;gBAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;gBAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YAAC,CAAC;YACjE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,IAAI,GAAG,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,gBAAgB;IAChB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAiB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACzF,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE;QAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAS,EAAE,CAAC;IAE5D,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE;QAClC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACtC,OAAO;YACL,IAAI,EAAE,CAAC;YACP,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;YACjB,KAAK,EAAE,CAAC;YACR,OAAO;YACP,IAAI,EAAE,OAAO,KAAK,CAAC;YACnB,SAAS,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/B,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YACvB,uEAAuE;YACvE,sEAAsE;YACtE,8DAA8D;YAC9D,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;SAClE,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEnG,MAAM,MAAM,GAAwB,EAAE,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,OAAO;QAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAClD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAEvE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,OAAO;QACL,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,OAAO,CAAC,MAAM;QAC3B,YAAY;QACZ,SAAS;QACT,uEAAuE;QACvE,sEAAsE;QACtE,yEAAyE;QACzE,2EAA2E;QAC3E,mEAAmE;QACnE,OAAO;QACP,OAAO,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QAC1E,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QAC5E,YAAY,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QAC3E,OAAO;QACP,MAAM;QACN,SAAS;QACT,SAAS,EAAE,aAAa,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACvF,SAAS;KACV,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,8EAA8E;AAC9E,4EAA4E;AAC5E,sEAAsE;AACtE,SAAS,aAAa,CAAC,OAAO,EAAE,eAAe;IAC7C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC,0BAA0B;IAEnD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,0BAA0B;YAAE,SAAS;QAC5E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBACpD,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;oBAAE,SAAS;gBACrF,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;gBACxB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC5B,IAAI,IAAI;oBAAE,IAAI,CAAC,KAAK,EAAE,CAAC;qBAClB,IAAI,KAAK,CAAC,IAAI,GAAG,iBAAiB;oBAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,gDAAgD;SAC5E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC3D,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,gFAAgF;AAChF,+DAA+D;AAC/D,MAAM,UAAU,kBAAkB,CAAC,MAAM;IACvC,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC","sourcesContent":["// Git history analysis: churn, authorship, the hotspot cross-tab, and\n// temporal co-change coupling. Pure — the commits arrive as plain data from an\n// injected provider (the server's `gitLog`), exactly as `scanRepo` takes files\n// through `FileSource`. Nothing here knows git exists.\n//\n// The unit of history is a commit: { hash, author: { name, email }, date, files }.\n// Everything is keyed on file path, which is already the join key the rest of\n// the engine speaks — complexity comes over from scan.files, structural edges\n// from scan.edges, and the two views of \"these change together\" sit side by side.\n\nimport { baseName } from './pathUtil.js';\n\n// Commits touching more files than this are skipped for pair counting, not for\n// churn — a 900-file reformat or a mass rename would otherwise contribute\n// C(900,2) ≈ 400k pairs of noise to co-change while saying nothing about\n// coupling. Churn still counts them; every file in them was really touched.\nconst MAX_COMMIT_FILES_FOR_PAIRS = 50;\n\n// Once this many distinct pairs are tracked, new pairs stop being recorded and\n// only existing ones keep counting. A hard bound on memory for repos with long\n// histories of small commits; the top of the list is decided well before this.\nconst MAX_TRACKED_PAIRS = 5000;\n\nexport function analyzeHistory(scan, commits, opts: Record<string, any> = {}) {\n const totalCommits = opts.totalCommits ?? commits.length;\n const truncated = totalCommits > commits.length;\n\n // ---- per-file rollup -----------------------------------------------------\n // One pass over every commit's file list. Authors are identified by email —\n // names collide and change; the email is what git itself keys identity on.\n const churn = new Map(); // path -> commit count\n const authorsOf = new Map(); // path -> Map(email -> display name)\n const firstSeen = new Map(); // path -> earliest commit date (ISO strings sort)\n const lastTouched = new Map();\n const authorCommits = new Map(); // email -> { name, commits }\n let pathsGone = 0;\n\n for (const c of commits) {\n const date = c.date || '';\n const known = authorCommits.get(c.author?.email);\n if (known) known.commits++;\n else if (c.author?.email) authorCommits.set(c.author.email, { name: c.author.name || c.author.email, commits: 1 });\n\n for (const p of c.files || []) {\n churn.set(p, (churn.get(p) || 0) + 1);\n if (!firstSeen.has(p) || date < firstSeen.get(p)) firstSeen.set(p, date);\n if (!lastTouched.has(p) || date > lastTouched.get(p)) lastTouched.set(p, date);\n let byEmail = authorsOf.get(p);\n if (!byEmail) { byEmail = new Map(); authorsOf.set(p, byEmail); }\n if (!byEmail.has(c.author?.email)) byEmail.set(c.author?.email, c.author?.name || c.author?.email || '?');\n }\n }\n\n // ---- join with the scan ---------------------------------------------------\n // Complexity lives on scan.files; history paths that are not in the scan are\n // files deleted or renamed inside the window. They had churn once — counted,\n // then set aside rather than silently mixed into rankings of files that no\n // longer exist.\n const cxOf = new Map<string, number>(scan.files.map((f) => [f.path, f.complexity || 1]));\n for (const p of churn.keys()) if (!cxOf.has(p)) pathsGone++;\n\n const live = [...churn.entries()].filter(([p]) => cxOf.has(p));\n const maxChurn = Math.max(1, ...live.map(([, n]) => n));\n const maxCx = Math.max(1, ...live.map(([p]) => cxOf.get(p)));\n\n const perFile = live.map(([p, n]) => {\n const authors = authorsOf.get(p).size;\n return {\n path: p,\n name: baseName(p),\n churn: n,\n authors,\n solo: authors === 1,\n firstSeen: firstSeen.get(p),\n lastTouched: lastTouched.get(p),\n complexity: cxOf.get(p),\n // The cross-tab: both axes normalized against the repo's own worst, so\n // the score reads \"how far up both columns at once\" and a file maxing\n // one axis but not the other cannot outrank one high on both.\n hotspot: Math.round(100 * (n / maxChurn) * (cxOf.get(p) / maxCx)),\n };\n });\n perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));\n\n const byPath: Record<string, any> = {};\n for (const row of perFile) byPath[row.path] = row;\n const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;\n\n const dates = commits.map((c) => c.date).filter(Boolean);\n\n return {\n available: true,\n commitCount: commits.length,\n totalCommits,\n truncated,\n // The raw commit list, not just the rollups. The insights view draws a\n // 52-week heatmap and a punch card from per-commit dates; without the\n // original array, that view rendered an empty calendar even when history\n // was fully available. The cost is the same parsed commits we already hold\n // — `parseGitLog` capped at 2,000 — so duplication here is honest.\n commits,\n authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),\n firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,\n lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,\n perFile,\n byPath,\n soloFiles,\n coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),\n pathsGone,\n };\n}\n\n// Files that keep turning up in the same commit without importing each other.\n// The structural coupling matrix already shows what imports what; this is the\n// contrast — changes that travel together for reasons the import graph does\n// not know about (shared schema, feature pairs, copy-paste siblings).\nfunction coChangePairs(commits, structuralEdges) {\n const pairs = new Map(); // 'a|b' (sorted) -> count\n\n for (const c of commits) {\n const files = c.files || [];\n if (files.length < 2 || files.length > MAX_COMMIT_FILES_FOR_PAIRS) continue;\n for (let i = 0; i < files.length; i++) {\n for (let j = i + 1; j < files.length; j++) {\n const a = files[i] < files[j] ? files[i] : files[j];\n const b = files[i] < files[j] ? files[j] : files[i];\n if (structuralEdges.has(a + '->' + b) || structuralEdges.has(b + '->' + a)) continue;\n const key = a + '|' + b;\n const seen = pairs.get(key);\n if (seen) seen.count++;\n else if (pairs.size < MAX_TRACKED_PAIRS) pairs.set(key, { a, b, count: 1 });\n }\n }\n }\n\n return [...pairs.values()]\n .filter((p) => p.count >= 2) // once is coincidence; the plan is about habits\n .sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))\n .slice(0, 20);\n}\n\n// The shape callers should hold when there is nothing to analyze — same fields,\n// honest reason. Views render this instead of inventing zeros.\nexport function unavailableHistory(reason) {\n return { available: false, reason };\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Parse TypeScript's JSON-with-comments configs without altering quoted paths. */
|
|
2
|
+
export function parseJsonConfig(text) {
|
|
3
|
+
const characters = text.split('');
|
|
4
|
+
let quoted = false;
|
|
5
|
+
for (let i = 0; i < text.length; i++) {
|
|
6
|
+
const character = text[i];
|
|
7
|
+
if (quoted) {
|
|
8
|
+
if (character === '\\')
|
|
9
|
+
i++;
|
|
10
|
+
else if (character === '"')
|
|
11
|
+
quoted = false;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (character === '"') {
|
|
15
|
+
quoted = true;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (character === '/' && text[i + 1] === '/') {
|
|
19
|
+
while (i < text.length && text[i] !== '\n')
|
|
20
|
+
characters[i++] = ' ';
|
|
21
|
+
}
|
|
22
|
+
else if (character === '/' && text[i + 1] === '*') {
|
|
23
|
+
characters[i++] = ' ';
|
|
24
|
+
characters[i++] = ' ';
|
|
25
|
+
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) {
|
|
26
|
+
if (text[i] !== '\n' && text[i] !== '\r')
|
|
27
|
+
characters[i] = ' ';
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
if (i >= text.length)
|
|
31
|
+
throw new SyntaxError('Unterminated config comment.');
|
|
32
|
+
characters[i++] = ' ';
|
|
33
|
+
characters[i] = ' ';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// JSONC also accepts trailing commas. Handle them only outside strings.
|
|
37
|
+
quoted = false;
|
|
38
|
+
for (let i = 0; i < characters.length; i++) {
|
|
39
|
+
const character = characters[i];
|
|
40
|
+
if (quoted) {
|
|
41
|
+
if (character === '\\')
|
|
42
|
+
i++;
|
|
43
|
+
else if (character === '"')
|
|
44
|
+
quoted = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (character === '"') {
|
|
48
|
+
quoted = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (character !== ',')
|
|
52
|
+
continue;
|
|
53
|
+
let next = i + 1;
|
|
54
|
+
while (next < characters.length && /\s/.test(characters[next]))
|
|
55
|
+
next++;
|
|
56
|
+
if (characters[next] === '}' || characters[next] === ']')
|
|
57
|
+
characters[i] = ' ';
|
|
58
|
+
}
|
|
59
|
+
return JSON.parse(characters.join(''));
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=jsonConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonConfig.js","sourceRoot":"","sources":["../../src/shared/analyzer/jsonConfig.ts"],"names":[],"mappings":"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","sourcesContent":["/** Parse TypeScript's JSON-with-comments configs without altering quoted paths. */\nexport function parseJsonConfig(text: string): unknown {\n const characters = text.split('');\n let quoted = false;\n for (let i = 0; i < text.length; i++) {\n const character = text[i];\n if (quoted) {\n if (character === '\\\\') i++;\n else if (character === '\"') quoted = false;\n continue;\n }\n if (character === '\"') { quoted = true; continue; }\n if (character === '/' && text[i + 1] === '/') {\n while (i < text.length && text[i] !== '\\n') characters[i++] = ' ';\n } else if (character === '/' && text[i + 1] === '*') {\n characters[i++] = ' ';\n characters[i++] = ' ';\n while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) {\n if (text[i] !== '\\n' && text[i] !== '\\r') characters[i] = ' ';\n i++;\n }\n if (i >= text.length) throw new SyntaxError('Unterminated config comment.');\n characters[i++] = ' ';\n characters[i] = ' ';\n }\n }\n // JSONC also accepts trailing commas. Handle them only outside strings.\n quoted = false;\n for (let i = 0; i < characters.length; i++) {\n const character = characters[i];\n if (quoted) {\n if (character === '\\\\') i++;\n else if (character === '\"') quoted = false;\n continue;\n }\n if (character === '\"') { quoted = true; continue; }\n if (character !== ',') continue;\n let next = i + 1;\n while (next < characters.length && /\\s/.test(characters[next])) next++;\n if (characters[next] === '}' || characters[next] === ']') characters[i] = ' ';\n }\n return JSON.parse(characters.join('')) as unknown;\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const extensions: string[];
|
|
2
|
+
export declare function analyze(source: any, path: any): {
|
|
3
|
+
imports: any[];
|
|
4
|
+
exports: {
|
|
5
|
+
name: any;
|
|
6
|
+
kind: string;
|
|
7
|
+
}[];
|
|
8
|
+
functions: any[];
|
|
9
|
+
classes: any[];
|
|
10
|
+
hasMain: boolean;
|
|
11
|
+
namespace: any;
|
|
12
|
+
};
|
|
13
|
+
export declare function sourceRootFor(fromPath: any, namespace: any): string;
|
|
14
|
+
export declare function resolveImport(spec: any, fromPath: any, has: any, context?: Record<string, any>, meta?: Record<string, any>): {
|
|
15
|
+
path?: undefined;
|
|
16
|
+
unresolved: any;
|
|
17
|
+
packageDir?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
unresolved?: undefined;
|
|
20
|
+
path: string;
|
|
21
|
+
packageDir?: undefined;
|
|
22
|
+
} | {
|
|
23
|
+
path?: undefined;
|
|
24
|
+
unresolved?: undefined;
|
|
25
|
+
packageDir: any;
|
|
26
|
+
};
|
|
@@ -8,54 +8,45 @@
|
|
|
8
8
|
// directory and the scanner links to every file in it — the same shape Go uses
|
|
9
9
|
// for packages.
|
|
10
10
|
import { blankComments, lineCounter } from '../util.js';
|
|
11
|
-
|
|
12
11
|
export const extensions = ['.cs'];
|
|
13
|
-
|
|
14
12
|
const NAMESPACE_RE = /^\s*namespace\s+([A-Za-z0-9_.]+)\s*[;{]/m;
|
|
15
|
-
|
|
16
13
|
export function analyze(source, path) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (const m of clean.matchAll(/\busing\s+(static\s+)?([A-Za-z0-9_.]+)\s*;/g)) {
|
|
31
|
-
imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });
|
|
32
|
-
}
|
|
33
|
-
// `using X = Y;` aliases and global usings are out of scope; a spec that fails
|
|
34
|
-
// to resolve is reported as unresolved rather than guessed at.
|
|
35
|
-
|
|
36
|
-
const classes = [];
|
|
37
|
-
for (const m of clean.matchAll(/(\[[^\]]+\]\s*)*\b(?:public\s+|private\s+|protected\s+|internal\s+)*(?:abstract\s+|sealed\s+|static\s+|partial\s+)*(?:class|interface|struct|record|enum)\s+([a-zA-Z0-9_]+)/g)) {
|
|
38
|
-
const attributes = [];
|
|
39
|
-
if (m[1]) {
|
|
40
|
-
for (const am of m[1].matchAll(/\[([A-Za-z0-9_]+)/g)) {
|
|
41
|
-
attributes.push(am[1]);
|
|
42
|
-
}
|
|
14
|
+
const clean = blankComments(source);
|
|
15
|
+
const lineAt = lineCounter(clean);
|
|
16
|
+
// The file's own namespace. Captured first because every import is resolved
|
|
17
|
+
// relative to it — this declaration is the only place the root is written down.
|
|
18
|
+
// Block-scoped namespaces (`namespace A.B { }`) are the norm, and the regex
|
|
19
|
+
// accepts both the `;` and `{` forms.
|
|
20
|
+
const namespace = (clean.match(NAMESPACE_RE) || [])[1] || '';
|
|
21
|
+
const imports = [];
|
|
22
|
+
// `using static A.B.C;` names a type (and optionally a member of it); plain
|
|
23
|
+
// `using A.B;` names a namespace. The distinction decides whether we look for
|
|
24
|
+
// a file or a directory.
|
|
25
|
+
for (const m of clean.matchAll(/\busing\s+(static\s+)?([A-Za-z0-9_.]+)\s*;/g)) {
|
|
26
|
+
imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });
|
|
43
27
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
28
|
+
// `using X = Y;` aliases and global usings are out of scope; a spec that fails
|
|
29
|
+
// to resolve is reported as unresolved rather than guessed at.
|
|
30
|
+
const classes = [];
|
|
31
|
+
for (const m of clean.matchAll(/(\[[^\]]+\]\s*)*\b(?:public\s+|private\s+|protected\s+|internal\s+)*(?:abstract\s+|sealed\s+|static\s+|partial\s+)*(?:class|interface|struct|record|enum)\s+([a-zA-Z0-9_]+)/g)) {
|
|
32
|
+
const attributes = [];
|
|
33
|
+
if (m[1]) {
|
|
34
|
+
for (const am of m[1].matchAll(/\[([A-Za-z0-9_]+)/g)) {
|
|
35
|
+
attributes.push(am[1]);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
classes.push({ name: m[2], attributes, line: lineAt(m.index) });
|
|
39
|
+
}
|
|
40
|
+
const hasMain = /static\s+(?:async\s+Task|void|int|async\s+Task<int>)\s+Main\s*\(/.test(clean);
|
|
41
|
+
return {
|
|
42
|
+
imports,
|
|
43
|
+
exports: classes.map(c => ({ name: c.name, kind: 'class' })),
|
|
44
|
+
functions: [],
|
|
45
|
+
classes,
|
|
46
|
+
hasMain,
|
|
47
|
+
namespace,
|
|
48
|
+
};
|
|
57
49
|
}
|
|
58
|
-
|
|
59
50
|
// A C# `using` is resolved by *path suffix*, not by a root prefix. See
|
|
60
51
|
// `resolveImport` below for why that distinction matters — it is the whole
|
|
61
52
|
// difference between C# and Java, and getting it backwards drops every import.
|
|
@@ -64,51 +55,55 @@ export function analyze(source, path) {
|
|
|
64
55
|
// where a project *does* mirror the full namespace (`Acme/Services/User.cs`).
|
|
65
56
|
// It is a fallback, not the main path.
|
|
66
57
|
export function sourceRootFor(fromPath, namespace) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
if (!namespace)
|
|
59
|
+
return null;
|
|
60
|
+
const suffix = namespace.replace(/\./g, '/');
|
|
61
|
+
const normalized = String(fromPath).replace(/\\/g, '/');
|
|
62
|
+
const nested = normalized.lastIndexOf('/' + suffix + '/');
|
|
63
|
+
if (nested >= 0)
|
|
64
|
+
return normalized.slice(0, nested + 1);
|
|
65
|
+
if (normalized === suffix || normalized.startsWith(suffix + '/'))
|
|
66
|
+
return '';
|
|
67
|
+
return null;
|
|
74
68
|
}
|
|
75
|
-
|
|
76
69
|
export function resolveImport(spec, fromPath, has, context = {}, meta = {}) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
70
|
+
const parts = spec.split('.').filter(Boolean);
|
|
71
|
+
const findDir = (segments) => (segments.length ? context.findDirEndingWith?.(segments.join('/')) : null);
|
|
72
|
+
if (!parts.length)
|
|
73
|
+
return { unresolved: spec };
|
|
74
|
+
// A C# `using` is ambiguous from the string alone: in `using Acme.Models;`,
|
|
75
|
+
// `Models` might be a namespace (link its directory) or a type (link the
|
|
76
|
+
// file). Both readings are tried, most specific first.
|
|
77
|
+
//
|
|
78
|
+
// The other half of the puzzle is that C# resolves by *path suffix*, not by a
|
|
79
|
+
// root prefix the way Java does. `namespace Acme.Services` conventionally
|
|
80
|
+
// lives in `src/Services/`: the folders mirror the namespace, but the leading
|
|
81
|
+
// `Acme` — the project or company root — is not a directory. So the namespace
|
|
82
|
+
// segments are matched against the *tail* of a real directory path, which is
|
|
83
|
+
// what `findDirEndingWith` answers. Stripping the whole namespace instead, the
|
|
84
|
+
// Java approach, matches nothing here and silently drops every import.
|
|
85
|
+
// Reading 1: the last segment is a type. Find the directory holding its
|
|
86
|
+
// namespace, then the file named after it.
|
|
87
|
+
const namespaceParts = parts.slice(0, -1);
|
|
88
|
+
for (let take = namespaceParts.length; take > 0; take -= 1) {
|
|
89
|
+
const dir = findDir(namespaceParts.slice(namespaceParts.length - take));
|
|
90
|
+
if (!dir)
|
|
91
|
+
continue;
|
|
92
|
+
const exact = dir + '/' + parts[parts.length - 1] + '.cs';
|
|
93
|
+
if (has(exact))
|
|
94
|
+
return { path: exact };
|
|
95
|
+
if (meta.static)
|
|
96
|
+
return { unresolved: spec }; // `using static A.B.C;` is always a type
|
|
97
|
+
}
|
|
98
|
+
// Reading 2: the whole spec is a namespace, so link its directory.
|
|
99
|
+
for (let take = parts.length; take > 0; take -= 1) {
|
|
100
|
+
const dir = findDir(parts.slice(parts.length - take));
|
|
101
|
+
if (dir)
|
|
102
|
+
return { packageDir: dir };
|
|
103
|
+
}
|
|
104
|
+
// The BCL (`System`, `System.Collections.Generic`) and every NuGet package land
|
|
105
|
+
// here. Staying unresolved is the honest answer: a file we invent would be a
|
|
106
|
+
// node in the graph that does not exist.
|
|
107
|
+
return { unresolved: spec };
|
|
114
108
|
}
|
|
109
|
+
//# sourceMappingURL=csharp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csharp.js","sourceRoot":"","sources":["../../../src/shared/analyzer/languages/csharp.ts"],"names":[],"mappings":"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","sourcesContent":["// C# analyzer\n//\n// C# is namespace-rooted exactly as Java is package-rooted: a file's path is its\n// namespace spelled with slashes, under a project root. The difference that\n// matters is the import. Java's `import a.b.C;` names a *class*, so one path\n// answers it; C#'s `using A.B;` names a *namespace*, and the classes inside it\n// are not knowable from the import string. So a namespace import resolves to a\n// directory and the scanner links to every file in it — the same shape Go uses\n// for packages.\nimport { blankComments, lineCounter } from '../util.js';\n\nexport const extensions = ['.cs'];\n\nconst NAMESPACE_RE = /^\\s*namespace\\s+([A-Za-z0-9_.]+)\\s*[;{]/m;\n\nexport function analyze(source, path) {\n const clean = blankComments(source);\n const lineAt = lineCounter(clean);\n\n // The file's own namespace. Captured first because every import is resolved\n // relative to it — this declaration is the only place the root is written down.\n // Block-scoped namespaces (`namespace A.B { }`) are the norm, and the regex\n // accepts both the `;` and `{` forms.\n const namespace = (clean.match(NAMESPACE_RE) || [])[1] || '';\n\n const imports = [];\n // `using static A.B.C;` names a type (and optionally a member of it); plain\n // `using A.B;` names a namespace. The distinction decides whether we look for\n // a file or a directory.\n for (const m of clean.matchAll(/\\busing\\s+(static\\s+)?([A-Za-z0-9_.]+)\\s*;/g)) {\n imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });\n }\n // `using X = Y;` aliases and global usings are out of scope; a spec that fails\n // to resolve is reported as unresolved rather than guessed at.\n\n const classes = [];\n for (const m of clean.matchAll(/(\\[[^\\]]+\\]\\s*)*\\b(?:public\\s+|private\\s+|protected\\s+|internal\\s+)*(?:abstract\\s+|sealed\\s+|static\\s+|partial\\s+)*(?:class|interface|struct|record|enum)\\s+([a-zA-Z0-9_]+)/g)) {\n const attributes = [];\n if (m[1]) {\n for (const am of m[1].matchAll(/\\[([A-Za-z0-9_]+)/g)) {\n attributes.push(am[1]);\n }\n }\n classes.push({ name: m[2], attributes, line: lineAt(m.index) });\n }\n\n const hasMain = /static\\s+(?:async\\s+Task|void|int|async\\s+Task<int>)\\s+Main\\s*\\(/.test(clean);\n\n return {\n imports,\n exports: classes.map(c => ({ name: c.name, kind: 'class' })),\n functions: [],\n classes,\n hasMain,\n namespace,\n };\n}\n\n// A C# `using` is resolved by *path suffix*, not by a root prefix. See\n// `resolveImport` below for why that distinction matters — it is the whole\n// difference between C# and Java, and getting it backwards drops every import.\n//\n// `sourceRootFor` is kept and exported because it is the answer for the layouts\n// where a project *does* mirror the full namespace (`Acme/Services/User.cs`).\n// It is a fallback, not the main path.\nexport function sourceRootFor(fromPath, namespace) {\n if (!namespace) return null;\n const suffix = namespace.replace(/\\./g, '/');\n const normalized = String(fromPath).replace(/\\\\/g, '/');\n const nested = normalized.lastIndexOf('/' + suffix + '/');\n if (nested >= 0) return normalized.slice(0, nested + 1);\n if (normalized === suffix || normalized.startsWith(suffix + '/')) return '';\n return null;\n}\n\nexport function resolveImport(spec, fromPath, has, context: Record<string, any> = {}, meta: Record<string, any> = {}) {\n const parts = spec.split('.').filter(Boolean);\n const findDir = (segments) => (segments.length ? context.findDirEndingWith?.(segments.join('/')) : null);\n if (!parts.length) return { unresolved: spec };\n\n // A C# `using` is ambiguous from the string alone: in `using Acme.Models;`,\n // `Models` might be a namespace (link its directory) or a type (link the\n // file). Both readings are tried, most specific first.\n //\n // The other half of the puzzle is that C# resolves by *path suffix*, not by a\n // root prefix the way Java does. `namespace Acme.Services` conventionally\n // lives in `src/Services/`: the folders mirror the namespace, but the leading\n // `Acme` — the project or company root — is not a directory. So the namespace\n // segments are matched against the *tail* of a real directory path, which is\n // what `findDirEndingWith` answers. Stripping the whole namespace instead, the\n // Java approach, matches nothing here and silently drops every import.\n\n // Reading 1: the last segment is a type. Find the directory holding its\n // namespace, then the file named after it.\n const namespaceParts = parts.slice(0, -1);\n for (let take = namespaceParts.length; take > 0; take -= 1) {\n const dir = findDir(namespaceParts.slice(namespaceParts.length - take));\n if (!dir) continue;\n const exact = dir + '/' + parts[parts.length - 1] + '.cs';\n if (has(exact)) return { path: exact };\n if (meta.static) return { unresolved: spec }; // `using static A.B.C;` is always a type\n }\n\n // Reading 2: the whole spec is a namespace, so link its directory.\n for (let take = parts.length; take > 0; take -= 1) {\n const dir = findDir(parts.slice(parts.length - take));\n if (dir) return { packageDir: dir };\n }\n\n // The BCL (`System`, `System.Collections.Generic`) and every NuGet package land\n // here. Staying unresolved is the honest answer: a file we invent would be a\n // node in the graph that does not exist.\n return { unresolved: spec };\n}\n"]}
|