diff --git a/analyze_nesting.js b/analyze_nesting.js new file mode 100644 index 0000000..d9d6dcf --- /dev/null +++ b/analyze_nesting.js @@ -0,0 +1,116 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * コードのネスト深度を解析するスクリプト。 + * Rust (.rs), JavaScript (.js, .mjs), TypeScript (.ts) に対応。 + */ + +const MAX_NESTING = 4; +const TARGET_EXTENSIONS = ['.js', '.mjs', '.ts', '.rs']; + +function analyzeNesting(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + let currentLevel = 0; + let maxLevelInFile = 0; + let inComment = false; + let inString = false; + let stringChar = ''; + + console.log(`\nAnalyzing: ${filePath}`); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + let lineLevel = currentLevel; + + for (let j = 0; j < line.length; j++) { + const char = line[j]; + const nextChar = line[j + 1]; + + // 文字列中 + if (inString) { + if (char === stringChar && line[j - 1] !== '\\') { + inString = false; + } + continue; + } + + // コメント(ブロック) + if (inComment) { + if (char === '*' && nextChar === '/') { + inComment = false; + j++; + } + continue; + } + + // 新しい文字列の開始 + if (char === '"' || char === "'" || char === '`') { + inString = true; + stringChar = char; + continue; + } + + // コメントの開始 + if (char === '/' && nextChar === '/') { + break; // 行末までコメント + } + if (char === '/' && nextChar === '*') { + inComment = true; + j++; + continue; + } + + // ネスト + if (char === '{') { + currentLevel++; + if (currentLevel > maxLevelInFile) maxLevelInFile = currentLevel; + } else if (char === '}') { + currentLevel--; + } + } + + if (lineLevel >= MAX_NESTING || currentLevel >= MAX_NESTING) { + console.log(` [WARN] Line ${i + 1} (Level ${Math.max(lineLevel, currentLevel)}): ${line.trim().substring(0, 50)}...`); + } + } + + if (maxLevelInFile >= MAX_NESTING) { + console.log(` [RESULT] Max Nesting Level: ${maxLevelInFile} (REFACTOR NEEDED)`); + } else { + console.log(` [RESULT] Max Nesting Level: ${maxLevelInFile} (OK)`); + } + return maxLevelInFile; +} + +function walkDir(dir) { + const files = fs.readdirSync(dir); + files.forEach(file => { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + if (file !== 'node_modules' && file !== 'target' && file !== '.git') { + walkDir(fullPath); + } + } else if (TARGET_EXTENSIONS.includes(path.extname(file))) { + analyzeNesting(fullPath); + } + }); +} + +const args = process.argv.slice(2); +if (args.length > 0) { + args.forEach(arg => { + if (fs.existsSync(arg)) { + if (fs.statSync(arg).isDirectory()) { + walkDir(arg); + } else { + analyzeNesting(arg); + } + } else { + console.error(`File or directory not found: ${arg}`); + } + }); +} else { + walkDir('.'); +} diff --git a/count_lines.js b/count_lines.js new file mode 100644 index 0000000..fb99e83 --- /dev/null +++ b/count_lines.js @@ -0,0 +1,85 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * ファイルの行数をカウントするスクリプト。 + * 物理行数と、空行・コメントを除いた実効行数を計測。 + * Rust (.rs), JavaScript (.js, .mjs), TypeScript (.ts) に対応。 + */ + +const WARN_FILE_LINES = 200; +const TARGET_EXTENSIONS = ['.js', '.mjs', '.ts', '.rs']; + +function countLines(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + let physicalLines = lines.length; + let logicalLines = 0; + let inComment = false; + + for (let line of lines) { + const trimmed = line.trim(); + if (!trimmed) continue; + + // ブロックコメント対応 + if (inComment) { + if (trimmed.includes('*/')) { + inComment = false; + // */ 以降にコードがあるか(簡易) + if (trimmed.split('*/')[1].trim()) logicalLines++; + } + continue; + } + + if (trimmed.startsWith('//')) continue; + if (trimmed.startsWith('/*')) { + if (!trimmed.includes('*/')) { + inComment = true; + } else { + // /* */ が一行で完結し、かつ前後にコードがあるか(実運用上は稀だが考慮) + const contentWithoutComment = trimmed.replace(/\/\*.*?\*\//g, '').trim(); + if (contentWithoutComment) logicalLines++; + } + continue; + } + + logicalLines++; + } + + console.log(`\nLines Info: ${filePath}`); + console.log(` Physical: ${physicalLines}`); + console.log(` Logical : ${logicalLines} ${logicalLines > WARN_FILE_LINES ? '[WARN!]' : '[OK]'}`); + + return { physicalLines, logicalLines }; +} + +function walkDir(dir) { + const files = fs.readdirSync(dir); + files.forEach(file => { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + if (file !== 'node_modules' && file !== 'target' && file !== '.git') { + walkDir(fullPath); + } + } else if (TARGET_EXTENSIONS.includes(path.extname(file))) { + countLines(fullPath); + } + }); +} + +const args = process.argv.slice(2); +if (args.length > 0) { + args.forEach(arg => { + if (fs.existsSync(arg)) { + if (fs.statSync(arg).isDirectory()) { + walkDir(arg); + } else { + countLines(arg); + } + } else { + console.error(`File or directory not found: ${arg}`); + } + }); +} else { + walkDir('.'); +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5d86108..531d2dc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,6 +7,7 @@ use dotenvy::dotenv; use sea_orm::DatabaseConnection; use std::env; +use std::path::PathBuf; use std::sync::Arc; use tauri::menu::{Menu, MenuItem}; use tauri::tray::{TrayIconBuilder, TrayIconEvent}; @@ -38,9 +39,28 @@ let app_handle = app.handle().clone(); tauri::async_runtime::block_on(async move { let db_path = "vector.db"; - let extension_path = "../node_modules/sqlite-vec-windows-x64/vec0.dll"; + + // 拡張機能 (vec0.dll) のパスを動的に解決する + let exe_dir = env::current_exe().map(|p| p.parent().unwrap().to_path_buf()).unwrap_or_else(|_| env::current_dir().unwrap()); + + let candidates = [ + exe_dir.join("vec0.dll"), // 実行ファイルと同階層 + exe_dir.join("../node_modules/sqlite-vec-windows-x64/vec0.dll"), // Tauri dev (target/debug/..) + exe_dir.join("../../node_modules/sqlite-vec-windows-x64/vec0.dll"), // target/debug/../.. + exe_dir.join("../../../node_modules/sqlite-vec-windows-x64/vec0.dll"), // root + PathBuf::from("../node_modules/sqlite-vec-windows-x64/vec0.dll"), // 従来の相対パス + ]; - let conn = db::init_db(db_path, extension_path) + let mut extension_path = candidates[0].to_str().unwrap().to_string(); + for cand in &candidates { + if cand.exists() { + extension_path = cand.to_str().unwrap().to_string(); + println!("Found sqlite-vec at: {}", extension_path); + break; + } + } + + let conn = db::init_db(db_path, &extension_path) .await .expect("Failed to init database"); let llama_base_url = env::var("LLAMA_CPP_BASE_URL") @@ -98,7 +118,7 @@ _ => {} }) .on_tray_icon_event(|tray, event| { - if let TrayIconEvent::Click { .. } = event { + if let TrayIconEvent::DoubleClick { .. } = event { let app = tray.app_handle(); if let Some(window) = app.get_webview_window("main") { let _ = window.show(); diff --git a/test/scripts.test.js b/test/scripts.test.js new file mode 100644 index 0000000..2cea7de --- /dev/null +++ b/test/scripts.test.js @@ -0,0 +1,58 @@ +import { describe, expect, test } from "bun:test"; +import { spawnSync } from "child_process"; +import fs from "fs"; +import path from "path"; + +const TMP_TEST_DIR = "test/tmp_scripts"; + +describe("Scripts Rust Support Test", () => { + // テスト用ディレクトリ作成 + if (!fs.existsSync(TMP_TEST_DIR)) { + fs.mkdirSync(TMP_TEST_DIR, { recursive: true }); + } + + test("analyze_nesting.js identifies deep nesting in Rust", () => { + const rustCode = ` + fn nested() { + if true { + if true { + if true { + match Some(1) { + Some(_) => { + println!("Deep!"); + } + None => {} + } + } + } + } + } + `; + const testFile = path.join(TMP_TEST_DIR, "test_deep.rs"); + fs.writeFileSync(testFile, rustCode); + + const result = spawnSync("node", ["analyze_nesting.js", testFile], { encoding: "utf-8" }); + expect(result.stdout).toContain("Level 5"); + expect(result.stdout).toContain("REFACTOR NEEDED"); + }); + + test("count_lines.js counts logical lines in Rust properly", () => { + const rustCode = ` + // Comment + fn main() { + /* Block + Comment */ + let x = 1; + + println!("{}", x); + } + `; + const testFile = path.join(TMP_TEST_DIR, "test_count.rs"); + fs.writeFileSync(testFile, rustCode); + + const result = spawnSync("node", ["count_lines.js", testFile], { encoding: "utf-8" }); + // fn main, let x, println, } = 4 logical lines (概算) + // 実際の実装にあわせたアサーション + expect(result.stdout).toContain("Logical : 4"); + }); +});