作为开发者,我经常需要回答这些问题:这个项目到底有多少行代码?最近一周我写了多少代码?团队成员的代码贡献量如何?这些看似简单的问题,如果没有合适的工具,回答起来会非常麻烦。
代码统计工具能帮我们:
在VS Code中创建这样的工具特别有意义,因为:
我设计的这个工具需要实现以下核心功能:
基于VS Code的扩展开发,我们主要使用:
选择这些技术的原因是:
首先,我们需要搭建基础的扩展开发环境:
bash复制# 安装Yeoman和VS Code扩展生成器
npm install -g yo generator-code
# 创建新项目
yo code
# 选择TypeScript项目模板
# 填写扩展的基本信息
项目结构说明:
src/extension.ts:扩展入口文件package.json:扩展的配置清单tsconfig.json:TypeScript配置核心统计功能的实现逻辑:
typescript复制interface CodeStats {
totalLines: number;
codeLines: number;
commentLines: number;
blankLines: number;
}
function countLines(content: string): CodeStats {
const lines = content.split('\n');
let stats: CodeStats = {
totalLines: lines.length,
codeLines: 0,
commentLines: 0,
blankLines: 0
};
lines.forEach(line => {
const trimmed = line.trim();
if (!trimmed) {
stats.blankLines++;
} else if (trimmed.startsWith('//') || trimmed.startsWith('/*')) {
stats.commentLines++;
} else {
stats.codeLines++;
}
});
return stats;
}
为了统计整个项目,我们需要递归遍历工作区:
typescript复制import * as fs from 'fs';
import * as path from 'path';
function scanProject(projectPath: string): CodeStats {
let totalStats: CodeStats = {
totalLines: 0,
codeLines: 0,
commentLines: 0,
blankLines: 0
};
const files = fs.readdirSync(projectPath);
files.forEach(file => {
const fullPath = path.join(projectPath, file);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
const dirStats = scanProject(fullPath);
// 合并目录统计结果
} else if (stat.isFile() && isCodeFile(file)) {
const content = fs.readFileSync(fullPath, 'utf-8');
const fileStats = countLines(content);
// 合并文件统计结果
}
});
return totalStats;
}
使用Webview API创建可视化面板:
typescript复制vscode.commands.registerCommand('extension.showStats', () => {
const panel = vscode.window.createWebviewPanel(
'codeStats',
'代码统计',
vscode.ViewColumn.Two,
{}
);
const stats = scanProject(vscode.workspace.rootPath || '');
panel.webview.html = getWebviewContent(stats);
});
function getWebviewContent(stats: CodeStats): string {
return `
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<canvas id="statsChart"></canvas>
<script>
const ctx = document.getElementById('statsChart');
new Chart(ctx, {
type: 'pie',
data: {
labels: ['代码行', '注释行', '空行'],
datasets: [{
data: [${stats.codeLines}, ${stats.commentLines}, ${stats.blankLines}],
backgroundColor: [
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(201, 203, 207)'
]
}]
}
});
</script>
</body>
</html>
`;
}
实际项目中,我们可能只想统计特定类型的文件:
typescript复制const SUPPORTED_EXTENSIONS = ['.ts', '.js', '.html', '.css'];
function isCodeFile(filename: string): boolean {
return SUPPORTED_EXTENSIONS.includes(path.extname(filename).toLowerCase());
}
让用户可以通过设置自定义:
json复制// package.json
"contributes": {
"configuration": {
"title": "代码统计",
"properties": {
"codeStats.supportedExtensions": {
"type": "array",
"default": [".ts", ".js", ".html", ".css"],
"description": "需要统计的文件扩展名"
},
"codeStats.ignorePatterns": {
"type": "array",
"default": ["node_modules", ".git"],
"description": "需要忽略的目录模式"
}
}
}
}
在状态栏显示简略统计信息:
typescript复制const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
function updateStatusBar() {
const stats = scanProject(vscode.workspace.rootPath || '');
statusBarItem.text = `代码: ${stats.codeLines} | 注释: ${stats.commentLines} | 空行: ${stats.blankLines}`;
statusBarItem.show();
}
// 注册文件保存事件
vscode.workspace.onDidSaveTextDocument(updateStatusBar);
全量扫描大项目可能很耗时,我们可以实现增量统计:
typescript复制const fileStatsCache = new Map<string, CodeStats>();
function getFileStats(filePath: string): CodeStats {
if (fileStatsCache.has(filePath)) {
return fileStatsCache.get(filePath)!;
}
const content = fs.readFileSync(filePath, 'utf-8');
const stats = countLines(content);
fileStatsCache.set(filePath, stats);
return stats;
}
// 监听文件变化事件
vscode.workspace.onDidChangeTextDocument(e => {
fileStatsCache.delete(e.document.uri.fsPath);
});
对于超大项目,可以使用Worker线程:
typescript复制import { Worker } from 'worker_threads';
function countLinesInWorker(filePath: string): Promise<CodeStats> {
return new Promise((resolve, reject) => {
const worker = new Worker('./lineCounter.js', {
workerData: { filePath }
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
遇到超大文件时,直接读取整个文件可能导致内存问题:
typescript复制function countLinesSafely(filePath: string): CodeStats {
const stats: CodeStats = {
totalLines: 0,
codeLines: 0,
commentLines: 0,
blankLines: 0
};
const stream = fs.createReadStream(filePath, {
encoding: 'utf-8',
highWaterMark: 1024 * 1024 // 1MB chunks
});
let remaining = '';
stream.on('data', chunk => {
const content = remaining + chunk;
const lines = content.split('\n');
// 最后一个可能是不完整的行
remaining = lines.pop() || '';
lines.forEach(line => {
// 统计逻辑...
});
});
stream.on('end', () => {
if (remaining) {
// 处理最后剩余的行
}
});
return stats;
}
简单的单行注释检测不够准确,需要处理多行注释:
typescript复制let inBlockComment = false;
lines.forEach(line => {
const trimmed = line.trim();
if (inBlockComment) {
stats.commentLines++;
if (trimmed.endsWith('*/')) {
inBlockComment = false;
}
return;
}
if (trimmed.startsWith('/*')) {
stats.commentLines++;
if (!trimmed.endsWith('*/')) {
inBlockComment = true;
}
} else if (trimmed.startsWith('//')) {
stats.commentLines++;
} else if (!trimmed) {
stats.blankLines++;
} else {
stats.codeLines++;
}
});
项目中可能有自动生成的文件需要排除:
typescript复制function shouldIgnoreFile(filePath: string): boolean {
const ignorePatterns = vscode.workspace.getConfiguration('codeStats').get<string[]>('ignorePatterns') || [];
return ignorePatterns.some(pattern => {
if (pattern.startsWith('*')) {
return filePath.endsWith(pattern.slice(1));
}
return filePath.includes(pattern);
});
}
为统计功能添加单元测试:
typescript复制import assert from 'assert';
describe('代码统计功能', () => {
it('应该正确统计各种行数', () => {
const content = `
// 注释行
const a = 1; // 代码行
/* 多行
注释 */
function test() {
return a; // 代码行
}
`;
const stats = countLines(content);
assert.strictEqual(stats.totalLines, 8);
assert.strictEqual(stats.codeLines, 2);
assert.strictEqual(stats.commentLines, 4);
assert.strictEqual(stats.blankLines, 2);
});
});
VS Code提供了专门的调试配置:
json复制// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "运行扩展",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
]
}
]
}
调试技巧:
使用VS Code提供的工具打包:
bash复制# 安装打包工具
npm install -g vsce
# 打包
vsce package
这会生成一个.vsix文件,可以直接分享给其他人安装。
要将扩展发布到VS Code市场,需要:
bash复制vsce login <publisher-name>
vsce publish
发布后,用户就可以直接在VS Code扩展市场中搜索并安装你的工具了。
这个基础版本还可以进一步扩展:
我在实际使用中发现,这个工具特别适合在代码审查前快速了解改动规模,或者在项目里程碑时评估整体进度。通过持续记录统计结果,还能生成有趣的个人编码活动报告。