feat: Add tech-debt-tracker POWERFUL-tier skill
Complete technical debt management system with three interconnected tools: • debt_scanner.py - AST-based Python analysis + regex patterns for multi-language debt detection • debt_prioritizer.py - Multiple prioritization frameworks (CoD, WSJF, RICE) with sprint planning • debt_dashboard.py - Historical trend analysis, health scoring, and executive reporting Features: - 15+ debt types detected (complexity, duplicates, security, architecture, etc.) - Business impact analysis with ROI calculations - Health scoring (0-100) with trend forecasting - Executive and engineering stakeholder reports - Zero external dependencies, stdlib only - Comprehensive documentation and sample data Addresses: tech debt identification, prioritization, tracking, and stakeholder communication
This commit is contained in:
@@ -0,0 +1,395 @@
|
||||
// Frontend JavaScript with various technical debt examples
|
||||
|
||||
// TODO: Move configuration to separate file
|
||||
const API_BASE_URL = "https://api.example.com";
|
||||
const API_KEY = "abc123def456"; // FIXME: Should be in environment
|
||||
|
||||
// Global variables - should be encapsulated
|
||||
var userCache = {};
|
||||
var authToken = null;
|
||||
var currentUser = null;
|
||||
|
||||
// HACK: Polyfill for older browsers - should use proper build system
|
||||
if (!String.prototype.includes) {
|
||||
String.prototype.includes = function(search) {
|
||||
return this.indexOf(search) !== -1;
|
||||
};
|
||||
}
|
||||
|
||||
class UserInterface {
|
||||
constructor() {
|
||||
this.components = {};
|
||||
this.eventHandlers = [];
|
||||
|
||||
// Long parameter list in constructor
|
||||
this.init(document, window, localStorage, sessionStorage, navigator, history, location);
|
||||
}
|
||||
|
||||
// Function with too many parameters
|
||||
init(doc, win, localStorage, sessionStorage, nav, hist, loc) {
|
||||
this.document = doc;
|
||||
this.window = win;
|
||||
this.localStorage = localStorage;
|
||||
this.sessionStorage = sessionStorage;
|
||||
this.navigator = nav;
|
||||
this.history = hist;
|
||||
this.location = loc;
|
||||
|
||||
// Deep nesting example
|
||||
if (this.localStorage) {
|
||||
if (this.localStorage.getItem('user')) {
|
||||
if (JSON.parse(this.localStorage.getItem('user'))) {
|
||||
if (JSON.parse(this.localStorage.getItem('user')).preferences) {
|
||||
if (JSON.parse(this.localStorage.getItem('user')).preferences.theme) {
|
||||
if (JSON.parse(this.localStorage.getItem('user')).preferences.theme === 'dark') {
|
||||
document.body.classList.add('dark-theme');
|
||||
} else if (JSON.parse(this.localStorage.getItem('user')).preferences.theme === 'light') {
|
||||
document.body.classList.add('light-theme');
|
||||
} else {
|
||||
document.body.classList.add('default-theme');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Large function that does too many things
|
||||
renderUserDashboard(userId, includeStats, includeRecent, includeNotifications, includeSettings, includeHelp) {
|
||||
let user = this.getUser(userId);
|
||||
|
||||
if (!user) {
|
||||
console.log("User not found"); // Should use proper logging
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="dashboard">';
|
||||
|
||||
// Inline HTML generation - should use templates
|
||||
html += '<header class="dashboard-header">';
|
||||
html += '<h1>Welcome, ' + user.name + '</h1>';
|
||||
html += '<div class="user-avatar">';
|
||||
html += '<img src="' + user.avatar + '" alt="Avatar" />';
|
||||
html += '</div>';
|
||||
html += '</header>';
|
||||
|
||||
// Repeated validation pattern
|
||||
if (includeStats && includeStats === true) {
|
||||
html += '<section class="stats">';
|
||||
html += '<h2>Your Statistics</h2>';
|
||||
|
||||
// Magic numbers everywhere
|
||||
if (user.loginCount > 100) {
|
||||
html += '<div class="stat-item">Frequent User (100+ logins)</div>';
|
||||
} else if (user.loginCount > 50) {
|
||||
html += '<div class="stat-item">Regular User (50+ logins)</div>';
|
||||
} else if (user.loginCount > 10) {
|
||||
html += '<div class="stat-item">Casual User (10+ logins)</div>';
|
||||
} else {
|
||||
html += '<div class="stat-item">New User</div>';
|
||||
}
|
||||
|
||||
html += '</section>';
|
||||
}
|
||||
|
||||
if (includeRecent && includeRecent === true) {
|
||||
html += '<section class="recent">';
|
||||
html += '<h2>Recent Activity</h2>';
|
||||
|
||||
// No error handling for API calls
|
||||
let recentActivity = this.fetchRecentActivity(userId);
|
||||
|
||||
if (recentActivity && recentActivity.length > 0) {
|
||||
html += '<ul class="activity-list">';
|
||||
for (let i = 0; i < recentActivity.length; i++) {
|
||||
let activity = recentActivity[i];
|
||||
html += '<li class="activity-item">';
|
||||
html += '<span class="activity-type">' + activity.type + '</span>';
|
||||
html += '<span class="activity-description">' + activity.description + '</span>';
|
||||
html += '<span class="activity-time">' + this.formatTime(activity.timestamp) + '</span>';
|
||||
html += '</li>';
|
||||
}
|
||||
html += '</ul>';
|
||||
} else {
|
||||
html += '<p>No recent activity</p>';
|
||||
}
|
||||
|
||||
html += '</section>';
|
||||
}
|
||||
|
||||
if (includeNotifications && includeNotifications === true) {
|
||||
html += '<section class="notifications">';
|
||||
html += '<h2>Notifications</h2>';
|
||||
|
||||
let notifications = this.getNotifications(userId);
|
||||
|
||||
// Duplicate HTML generation pattern
|
||||
if (notifications && notifications.length > 0) {
|
||||
html += '<ul class="notification-list">';
|
||||
for (let i = 0; i < notifications.length; i++) {
|
||||
let notification = notifications[i];
|
||||
html += '<li class="notification-item">';
|
||||
html += '<span class="notification-title">' + notification.title + '</span>';
|
||||
html += '<span class="notification-message">' + notification.message + '</span>';
|
||||
html += '<span class="notification-time">' + this.formatTime(notification.timestamp) + '</span>';
|
||||
html += '</li>';
|
||||
}
|
||||
html += '</ul>';
|
||||
} else {
|
||||
html += '<p>No notifications</p>';
|
||||
}
|
||||
|
||||
html += '</section>';
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Direct DOM manipulation without cleanup
|
||||
document.getElementById('main-content').innerHTML = html;
|
||||
|
||||
// Event handler attachment without cleanup
|
||||
let buttons = document.querySelectorAll('.action-button');
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
buttons[i].addEventListener('click', function(event) {
|
||||
// Nested event handlers - memory leak risk
|
||||
let buttonType = event.target.getAttribute('data-type');
|
||||
if (buttonType === 'edit') {
|
||||
// Inline event handling - should be separate methods
|
||||
let modal = document.createElement('div');
|
||||
modal.className = 'modal';
|
||||
modal.innerHTML = '<div class="modal-content"><h3>Edit Profile</h3><button onclick="closeModal()">Close</button></div>';
|
||||
document.body.appendChild(modal);
|
||||
} else if (buttonType === 'delete') {
|
||||
if (confirm('Are you sure?')) { // Using confirm - poor UX
|
||||
// No error handling
|
||||
fetch(API_BASE_URL + '/users/' + userId, {
|
||||
method: 'DELETE',
|
||||
headers: {'Authorization': 'Bearer ' + authToken}
|
||||
});
|
||||
}
|
||||
} else if (buttonType === 'share') {
|
||||
// Hardcoded share logic
|
||||
if (navigator.share) {
|
||||
navigator.share({
|
||||
title: 'Check out my profile',
|
||||
url: window.location.href
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers without Web Share API
|
||||
let shareUrl = 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(window.location.href);
|
||||
window.open(shareUrl, '_blank');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicate code - similar to above but for admin dashboard
|
||||
renderAdminDashboard(adminId) {
|
||||
let admin = this.getUser(adminId);
|
||||
|
||||
if (!admin) {
|
||||
console.log("Admin not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="admin-dashboard">';
|
||||
|
||||
html += '<header class="dashboard-header">';
|
||||
html += '<h1>Admin Panel - Welcome, ' + admin.name + '</h1>';
|
||||
html += '<div class="user-avatar">';
|
||||
html += '<img src="' + admin.avatar + '" alt="Avatar" />';
|
||||
html += '</div>';
|
||||
html += '</header>';
|
||||
|
||||
// Same pattern repeated
|
||||
html += '<section class="admin-stats">';
|
||||
html += '<h2>System Statistics</h2>';
|
||||
|
||||
let stats = this.getSystemStats();
|
||||
if (stats) {
|
||||
html += '<div class="stat-grid">';
|
||||
html += '<div class="stat-item">Total Users: ' + stats.totalUsers + '</div>';
|
||||
html += '<div class="stat-item">Active Users: ' + stats.activeUsers + '</div>';
|
||||
html += '<div class="stat-item">New Today: ' + stats.newToday + '</div>';
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
html += '</section>';
|
||||
html += '</div>';
|
||||
|
||||
document.getElementById('main-content').innerHTML = html;
|
||||
}
|
||||
|
||||
getUser(userId) {
|
||||
// Check cache first - but cache never expires
|
||||
if (userCache[userId]) {
|
||||
return userCache[userId];
|
||||
}
|
||||
|
||||
// Synchronous AJAX - blocks UI
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', API_BASE_URL + '/users/' + userId, false);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
let user = JSON.parse(xhr.responseText);
|
||||
userCache[userId] = user;
|
||||
return user;
|
||||
} else {
|
||||
// Generic error handling
|
||||
console.error('Failed to fetch user');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fetchRecentActivity(userId) {
|
||||
// Another synchronous call
|
||||
try {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', API_BASE_URL + '/users/' + userId + '/activity', false);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
return JSON.parse(xhr.responseText);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} catch (error) {
|
||||
// Swallowing errors
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
getNotifications(userId) {
|
||||
// Yet another sync call - should be async
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', API_BASE_URL + '/users/' + userId + '/notifications', false);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + authToken);
|
||||
xhr.send();
|
||||
|
||||
if (xhr.status === 200) {
|
||||
return JSON.parse(xhr.responseText);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
formatTime(timestamp) {
|
||||
// Basic time formatting - should use proper library
|
||||
let date = new Date(timestamp);
|
||||
return date.getMonth() + '/' + date.getDate() + '/' + date.getFullYear();
|
||||
}
|
||||
|
||||
// XXX: This method is never used
|
||||
formatCurrency(amount, currency) {
|
||||
if (currency === 'USD') {
|
||||
return '$' + amount.toFixed(2);
|
||||
} else if (currency === 'EUR') {
|
||||
return '€' + amount.toFixed(2);
|
||||
} else {
|
||||
return amount.toFixed(2) + ' ' + currency;
|
||||
}
|
||||
}
|
||||
|
||||
getSystemStats() {
|
||||
// Hardcoded test data - should come from API
|
||||
return {
|
||||
totalUsers: 12534,
|
||||
activeUsers: 8765,
|
||||
newToday: 23
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Global functions - should be methods or modules
|
||||
function closeModal() {
|
||||
// Assumes modal exists - no error checking
|
||||
document.querySelector('.modal').remove();
|
||||
}
|
||||
|
||||
function validateEmail(email) {
|
||||
// Regex without explanation - magic pattern
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||||
}
|
||||
|
||||
function validatePassword(password) {
|
||||
// Duplicate validation logic from backend
|
||||
if (password.length < 8) return false;
|
||||
if (!/[A-Z]/.test(password)) return false;
|
||||
if (!/[a-z]/.test(password)) return false;
|
||||
if (!/\d/.test(password)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// jQuery-style utility - reinventing the wheel
|
||||
function $(selector) {
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
function $all(selector) {
|
||||
return document.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
// Global event handlers - should be encapsulated
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Inline anonymous function
|
||||
let ui = new UserInterface();
|
||||
|
||||
// Event delegation would be better
|
||||
document.body.addEventListener('click', function(event) {
|
||||
if (event.target.classList.contains('login-button')) {
|
||||
// Inline login logic
|
||||
let username = $('#username').value;
|
||||
let password = $('#password').value;
|
||||
|
||||
if (!username || !password) {
|
||||
alert('Please enter username and password'); // Poor UX
|
||||
return;
|
||||
}
|
||||
|
||||
// No CSRF protection
|
||||
fetch(API_BASE_URL + '/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({username: username, password: password})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
authToken = data.token;
|
||||
currentUser = data.user;
|
||||
localStorage.setItem('authToken', authToken); // Storing sensitive data
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
window.location.reload(); // Poor navigation
|
||||
} else {
|
||||
alert('Login failed: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Login error:', error);
|
||||
alert('Login failed');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// // Old code left as comments - should be removed
|
||||
// function oldRenderFunction() {
|
||||
// var html = '<div>Old implementation</div>';
|
||||
// document.body.innerHTML = html;
|
||||
// }
|
||||
|
||||
// Commented out feature - should be removed or implemented
|
||||
// function darkModeToggle() {
|
||||
// if (document.body.classList.contains('dark-theme')) {
|
||||
// document.body.classList.remove('dark-theme');
|
||||
// document.body.classList.add('light-theme');
|
||||
// } else {
|
||||
// document.body.classList.remove('light-theme');
|
||||
// document.body.classList.add('dark-theme');
|
||||
// }
|
||||
// }
|
||||
Reference in New Issue
Block a user