mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-03 21:15:30 +08:00
Rust command attributes were not retained as decorators, so runtime entry points could appear as dead code. Preserve tauri::command metadata in both native and fallback extraction and reuse the existing decorated exclusion. Add SQLite regression coverage and native/fallback parity checks, including negative cases. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
2.5 KiB
TypeScript
61 lines
2.5 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest';
|
|
import fs from 'fs';
|
|
import os from 'os';
|
|
import path from 'path';
|
|
import CodeGraph from '../src/index';
|
|
import { buildDeadCodeReport } from '../src/graph/dead-code';
|
|
import { buildDeadCode } from '../src/ui-server/api/deadcode';
|
|
|
|
let root: string;
|
|
let cg: CodeGraph | undefined;
|
|
afterEach(() => {
|
|
cg?.close();
|
|
cg = undefined;
|
|
if (root) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('Tauri command dead-code exclusion (#1543)', () => {
|
|
it('counts commands as decorated entry points while retaining unannotated helpers', async () => {
|
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-tauri-dead-'));
|
|
fs.writeFileSync(path.join(root, 'commands.rs'), `
|
|
pub fn reached() {}
|
|
#[tauri::command]
|
|
fn get_mcp_port() -> u16 { 4000 }
|
|
#[tauri :: command(rename_all = "snake_case")]
|
|
// A comment between attributes and the function is allowed.
|
|
#[allow(non_snake_case)]
|
|
pub async fn readSettings() {}
|
|
fn unused_helper() {}
|
|
// #[tauri::command]
|
|
fn comment_only() {}
|
|
#[other::command]
|
|
fn unrelated_attribute() {}
|
|
const TEXT: &str = r#"#[tauri::command]"#;
|
|
fn string_only() {}
|
|
#[tauri::command]
|
|
fn outer_command() { fn nested_helper() {} }
|
|
fn next_function() {}
|
|
`);
|
|
fs.writeFileSync(path.join(root, 'main.rs'), 'mod commands;\nfn main() { commands::reached(); }\n');
|
|
cg = CodeGraph.initSync(root);
|
|
await cg.indexAll();
|
|
const nodes = cg.getNodesInFile('commands.rs');
|
|
for (const name of ['get_mcp_port', 'readSettings', 'outer_command']) {
|
|
expect(nodes.find(n => n.name === name)?.decorators, name).toEqual(['tauri::command']);
|
|
}
|
|
// Rust export coverage is incomplete; includeExported exercises the actual
|
|
// decorator rule rather than letting exportsUnknown hide the regression.
|
|
const report = buildDeadCodeReport(cg, { includeExported: true });
|
|
expect(report.excluded.decorated).toBe(3);
|
|
const wire = buildDeadCode(cg, root, new URLSearchParams('exported=1'));
|
|
expect(wire.excluded).toContainEqual({
|
|
reason: 'decorated', count: 3, label: 'carrying a decorator, so a framework registers them',
|
|
});
|
|
expect(buildDeadCodeReport(cg, { includeExported: true, readSource: null }).excluded.decorated).toBe(3);
|
|
expect(report.entries.map(e => e.node.name)).toEqual(expect.arrayContaining([
|
|
'unused_helper', 'comment_only', 'unrelated_attribute', 'string_only', 'nested_helper', 'next_function',
|
|
]));
|
|
expect(report.entries.some(e => ['get_mcp_port', 'readSettings', 'outer_command'].includes(e.node.name))).toBe(false);
|
|
});
|
|
});
|