UNPKG

chrome-extension-test-framework

Version:

Universal testing framework for Chrome extensions - Fast static analysis without browser dependencies

320 lines (287 loc) 11.3 kB
/** * EnvironmentDetector - 環境を考慮した検出 * * 開発環境、テスト環境、本番環境を識別し、 * 環境に応じて適切な検証レベルを適用する */ class EnvironmentDetector { constructor() { // 環境を示すパターン this.environmentPatterns = { development: { files: [ /\.dev\.(js|ts)$/, /\.development\.(js|ts)$/, /dev-.*\.(js|ts)$/, /webpack\.dev\.config\.js$/ ], directories: [ /\/dev\//, /\/development\//, /\/sandbox\//, /\/playground\// ], code: [ /process\.env\.NODE_ENV\s*===?\s*['"]development['"]/, /DEBUG\s*=\s*true/, /\/\/\s*@dev-only/, /\/\*\s*dev-only\s*\*\// ] }, test: { files: [ /\.test\.(js|ts)$/, /\.spec\.(js|ts)$/, /\.e2e\.(js|ts)$/, /__tests__\//, /test-.*\.(js|ts)$/ ], directories: [ /\/test\//, /\/tests\//, /\/spec\//, /\/__tests__\//, /\/e2e\// ], code: [ /describe\s*\(/, /it\s*\(/, /test\s*\(/, /expect\s*\(/, /jest\./, /mocha\./ ] }, production: { files: [ /\.min\.(js|css)$/, /\.prod\.(js|ts)$/, /\.production\.(js|ts)$/, /bundle\.(js|css)$/ ], directories: [ /\/dist\//, /\/build\//, /\/release\//, /\/production\// ], code: [ /process\.env\.NODE_ENV\s*===?\s*['"]production['"]/, /\/\/\s*@prod-only/, /\/\*\s*prod-only\s*\*\// ] } }; // 環境固有の許可パターン this.environmentAllowances = { development: { console: true, // console使用OK debugCode: true, // デバッグコードOK localStorage: 'warning', // localStorageは警告レベル hardcodedValues: 'warning' // ハードコードされた値は警告 }, test: { console: true, // テストでのconsole使用OK mockData: true, // モックデータOK hardcodedValues: true, // テスト用の固定値OK dangerousFunctions: 'warning' // 危険な関数は警告 }, production: { console: false, // console使用NG debugCode: false, // デバッグコードNG localStorage: false, // localStorage使用NG hardcodedValues: false // ハードコードされた値NG } }; } /** * ファイルの環境を検出 */ detectEnvironment(filePath, content) { const environments = []; // ファイルパスから判定 for (const [env, patterns] of Object.entries(this.environmentPatterns)) { // ファイル名パターン if (patterns.files.some(pattern => pattern.test(filePath))) { environments.push(env); } // ディレクトリパターン if (patterns.directories.some(pattern => pattern.test(filePath))) { environments.push(env); } // コード内容から判定 if (content && patterns.code.some(pattern => pattern.test(content))) { environments.push(env); } } // 複数の環境が検出された場合の優先順位 if (environments.includes('test')) return 'test'; if (environments.includes('development')) return 'development'; if (environments.includes('production')) return 'production'; // デフォルトは本番環境として扱う(安全側に倒す) return 'production'; } /** * 環境に基づいて検証レベルを調整 */ adjustSeverityForEnvironment(issue, environment) { const allowances = this.environmentAllowances[environment]; // console使用 if (issue.type === 'console') { if (allowances.console) { return 'ignore'; } } // localStorage使用 if (issue.type === 'localStorage') { if (allowances.localStorage === true) { return 'ignore'; } else if (allowances.localStorage === 'warning') { return 'low'; } } // ハードコードされた値 if (issue.type === 'hardcoded-secret' || issue.type === 'api-key') { if (environment === 'test' && issue.context && issue.context.includes('mock')) { return 'ignore'; // テストのモックデータ } if (allowances.hardcodedValues === true) { return 'ignore'; } else if (allowances.hardcodedValues === 'warning') { return 'medium'; } } // デバッグコード if (issue.type === 'debug-code') { if (allowances.debugCode) { return 'ignore'; } } // 元の深刻度を返す return issue.severity; } /** * 条件付きコードかどうかを判定 */ isConditionalCode(content, position) { // positionの前後のコードを取得 const before = content.substring(Math.max(0, position - 500), position); const after = content.substring(position, Math.min(content.length, position + 200)); // 環境チェックの条件文 const envCheckPatterns = [ /if\s*\(\s*process\.env\.NODE_ENV/, /if\s*\(\s*DEBUG\s*\)/, /if\s*\(\s*__DEV__\s*\)/, /if\s*\(\s*DEVELOPMENT\s*\)/, /if\s*\(\s*!PRODUCTION\s*\)/ ]; return envCheckPatterns.some(pattern => { const matchBefore = before.match(pattern); if (matchBefore) { // 対応する閉じ括弧がposition以降にあるか確認 const openBraces = (before.match(/{/g) || []).length; const closeBraces = (before.match(/}/g) || []).length; return openBraces > closeBraces; } return false; }); } /** * ビルドツールの設定ファイルかどうか */ isBuildConfig(filePath) { const buildConfigPatterns = [ /webpack\.config\.(js|ts)$/, /rollup\.config\.(js|ts)$/, /vite\.config\.(js|ts)$/, /gulpfile\.(js|ts)$/, /Gruntfile\.(js|ts)$/, /\.babelrc(\.js)?$/, /tsconfig\.json$/, /jest\.config\.(js|ts)$/ ]; return buildConfigPatterns.some(pattern => pattern.test(filePath)); } /** * CI/CD設定ファイルかどうか */ isCIConfig(filePath) { const ciPatterns = [ /\.github\/workflows\//, /\.gitlab-ci\.yml$/, /\.travis\.yml$/, /\.circleci\/config\.yml$/, /Jenkinsfile$/, /\.drone\.yml$/ ]; return ciPatterns.some(pattern => pattern.test(filePath)); } /** * 環境固有のファイルかどうか */ isEnvironmentSpecificFile(filePath) { const envFilePatterns = [ /\.env(\.\w+)?$/, /config\/(development|test|production)\.(js|json)$/, /environments\// ]; return envFilePatterns.some(pattern => pattern.test(filePath)); } /** * コンテキストに基づいた推奨事項を生成 */ getEnvironmentRecommendation(issue, environment) { const recommendations = { console: { development: 'Consider using a proper logging library with log levels', test: 'Console output is acceptable in tests', production: 'Remove all console statements or use a build tool to strip them' }, localStorage: { development: 'Consider using chrome.storage API even in development', test: 'Use mock storage in tests instead of real localStorage', production: 'Must use chrome.storage API in production' }, 'hardcoded-secret': { development: 'Use environment variables or a config file', test: 'Use mock values that are clearly marked as test data', production: 'Never hardcode secrets in production code' }, 'debug-code': { development: 'Wrap debug code in environment checks', test: 'Debug code is acceptable in test files', production: 'All debug code must be removed for production' } }; return recommendations[issue.type]?.[environment] || issue.suggestion; } /** * ファイルの役割を推測 */ inferFileRole(filePath, content) { const roles = []; // 設定ファイル if (this.isBuildConfig(filePath) || this.isCIConfig(filePath)) { roles.push('config'); } // テストファイル if (this.environmentPatterns.test.files.some(p => p.test(filePath))) { roles.push('test'); } // エントリーポイント if (filePath.includes('index.') || filePath.includes('main.') || filePath.includes('app.')) { roles.push('entry-point'); } // ユーティリティ if (filePath.includes('util') || filePath.includes('helper') || filePath.includes('lib')) { roles.push('utility'); } // Chrome拡張機能固有 if (filePath.includes('background')) roles.push('background-script'); if (filePath.includes('content')) roles.push('content-script'); if (filePath.includes('popup')) roles.push('popup-script'); if (filePath.includes('options')) roles.push('options-page'); return roles; } } module.exports = EnvironmentDetector;