') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - pureooze/DebtCollector.NET · GitHub
Skip to content

Repository files navigation

DebtCollector.NET

This is a simple Modern .NET tool that lets you perform analysis on your code to quickly discover potential technical debt hotspots. The goal is to help prioritize work, so instead of trying to focus on 1000000 lines of code for refactors we can narrow down our focus to 500 lines (these numbers are just an example, the point is we want to reduce the focus).

The tool leverages LibGit2Sharp and Roslyn to perform analysis on C# code in a git repo and was inspired by Adam Tornhills talk Prioritizing Technical Debt as If Time & Money Matters.

How To Use It

Create an appsettings.json file in the DebtCollector.NET directory with a PathToRepo property, for example:

{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"PathToRepo": "C:\\My\\Code\\Projects\\TwitchEverywhere",
"DaysSince": 365,
"Modes": ["hotspot"],
"GitClient": "cli",
"GroupDepth": 1
}
FieldDescriptionValid Values
PathToRepoThe path to the git repo you want to analyzeAny valid string path on the filesystem
DaysSinceThe number of days since the last commit to considerAny valid int
ModesThe analysis modes to runmethodXray, hotspotGrouped, lastModified
GitClientThe git client to usecli, libgit2sharp
GroupDepthThe depth to group paths toAny valid int

Run the DebtCollector.NET project and it will output two CSV files:

  • mostCommittedFiles.csv: A list of files sorted by most committed
  • xray.csv: A list of all methods from the first file in the previous list sorted by most committed

For example running this on the TwitchEverywhere code we get an output like this:

TwitchEverywhere-commit-count-per-file-cs.webp

Notice the outlier? It has almost double the commits as each of the other files! To get more details on why that might be, we can look at the Xray results and see this:

TwitchEverywhere-commit-count-per-file-cs.webp

Now it's clear that the MessageCallback method is something that developers work with extremely often! So we can focus on that when looking to do refactors. 🎉

Path Grouping

DebtCollector.NET can also group paths to a certain depth through the GroupDepth setting. For example if we have the following paths:

[
{ "path": "first/first/path/to/file1", "count": 10 },
{ "path": "first/first/path/to/file2", "count": 2 },
{ "path": "first/second/path/to/file3", "count": 50 },
{ "path": "first/second/path/to/file4", "count": 4 },
{ "path": "first/third/path/to/file5", "count": 9 }
]

And we set GroupDepth to 1, we will get the following output:

[
{ "path": "first", "count": 75 }
]

If no GroupDepth is set, the output will be the same as the full paths list.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages