chrome-extension-test-framework
Version:
Universal testing framework for Chrome extensions - Fast static analysis without browser dependencies
355 lines (309 loc) • 12.3 kB
JavaScript
/**
* StorageAnalyzer - Chrome拡張機能のストレージ使用パターンを分析
* localStorageの使用検出とchrome.storage APIへの移行推奨
*/
const fs = require('fs');
const path = require('path');
class StorageAnalyzer {
constructor(config = {}) {
this.config = config;
this.issues = [];
this.storageUsage = {
localStorage: [],
sessionStorage: [],
chromeStorageLocal: [],
chromeStorageSync: [],
chromeStorageSession: []
};
}
/**
* ストレージ使用パターンを分析
*/
async analyze(extensionPath) {
this.issues = [];
this.storageUsage = {
localStorage: [],
sessionStorage: [],
chromeStorageLocal: [],
chromeStorageSync: [],
chromeStorageSession: []
};
// JavaScriptファイルを検索
const jsFiles = this.findJavaScriptFiles(extensionPath);
for (const file of jsFiles) {
await this.analyzeFile(file, extensionPath);
}
return {
issues: this.issues,
usage: this.storageUsage,
summary: this.generateSummary()
};
}
/**
* JavaScriptファイルを検索
*/
findJavaScriptFiles(extensionPath) {
const files = [];
function traverse(dir) {
const items = fs.readdirSync(dir);
for (const item of items) {
const itemPath = path.join(dir, item);
const stat = fs.statSync(itemPath);
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
traverse(itemPath);
} else if (stat.isFile() && (item.endsWith('.js') || item.endsWith('.ts'))) {
files.push(itemPath);
}
}
}
traverse(extensionPath);
return files;
}
/**
* ファイルを分析
*/
async analyzeFile(filePath, extensionPath) {
const content = fs.readFileSync(filePath, 'utf8');
const relativePath = path.relative(extensionPath, filePath);
// localStorageの使用検出
this.detectLocalStorage(content, relativePath);
// sessionStorageの使用検出
this.detectSessionStorage(content, relativePath);
// chrome.storage APIの使用検出
this.detectChromeStorage(content, relativePath);
// ストレージサイズの問題検出
this.detectStorageSizeIssues(content, relativePath);
}
/**
* localStorageの使用を検出
*/
detectLocalStorage(content, filePath) {
const patterns = [
/localStorage\.setItem\s*\(/g,
/localStorage\.getItem\s*\(/g,
/localStorage\.removeItem\s*\(/g,
/localStorage\.clear\s*\(/g,
/localStorage\[['"`][\w-]+['"`]\]/g,
/window\.localStorage/g
];
const matches = [];
patterns.forEach(pattern => {
let match;
while ((match = pattern.exec(content)) !== null) {
const line = content.substring(0, match.index).split('\n').length;
matches.push({
line,
code: match[0]
});
}
});
if (matches.length > 0) {
this.storageUsage.localStorage.push({
file: filePath,
occurrences: matches.length,
locations: matches
});
this.issues.push({
type: 'DEPRECATED_STORAGE',
severity: 'warning',
file: filePath,
message: `localStorage is deprecated in extensions. Use chrome.storage.local instead`,
occurrences: matches.length,
suggestion: 'Migrate to chrome.storage.local for better performance and cross-device sync capability'
});
}
}
/**
* sessionStorageの使用を検出
*/
detectSessionStorage(content, filePath) {
const patterns = [
/sessionStorage\.setItem\s*\(/g,
/sessionStorage\.getItem\s*\(/g,
/sessionStorage\.removeItem\s*\(/g,
/sessionStorage\.clear\s*\(/g,
/sessionStorage\[['"`][\w-]+['"`]\]/g
];
const matches = [];
patterns.forEach(pattern => {
let match;
while ((match = pattern.exec(content)) !== null) {
const line = content.substring(0, match.index).split('\n').length;
matches.push({
line,
code: match[0]
});
}
});
if (matches.length > 0) {
this.storageUsage.sessionStorage.push({
file: filePath,
occurrences: matches.length,
locations: matches
});
this.issues.push({
type: 'DEPRECATED_STORAGE',
severity: 'warning',
file: filePath,
message: `sessionStorage is deprecated in extensions. Use chrome.storage.session instead`,
occurrences: matches.length,
suggestion: 'Migrate to chrome.storage.session for tab-specific storage'
});
}
}
/**
* chrome.storage APIの使用を検出
*/
detectChromeStorage(content, filePath) {
// chrome.storage.local
const localPatterns = [
/chrome\.storage\.local\.set\s*\(/g,
/chrome\.storage\.local\.get\s*\(/g,
/chrome\.storage\.local\.remove\s*\(/g,
/chrome\.storage\.local\.clear\s*\(/g
];
let localMatches = 0;
localPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) localMatches += matches.length;
});
if (localMatches > 0) {
this.storageUsage.chromeStorageLocal.push({
file: filePath,
occurrences: localMatches
});
}
// chrome.storage.sync
const syncPatterns = [
/chrome\.storage\.sync\.set\s*\(/g,
/chrome\.storage\.sync\.get\s*\(/g,
/chrome\.storage\.sync\.remove\s*\(/g,
/chrome\.storage\.sync\.clear\s*\(/g
];
let syncMatches = 0;
syncPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) syncMatches += matches.length;
});
if (syncMatches > 0) {
this.storageUsage.chromeStorageSync.push({
file: filePath,
occurrences: syncMatches
});
}
// chrome.storage.session
const sessionPatterns = [
/chrome\.storage\.session\.set\s*\(/g,
/chrome\.storage\.session\.get\s*\(/g,
/chrome\.storage\.session\.remove\s*\(/g,
/chrome\.storage\.session\.clear\s*\(/g
];
let sessionMatches = 0;
sessionPatterns.forEach(pattern => {
const matches = content.match(pattern);
if (matches) sessionMatches += matches.length;
});
if (sessionMatches > 0) {
this.storageUsage.chromeStorageSession.push({
file: filePath,
occurrences: sessionMatches
});
}
}
/**
* ストレージサイズの問題を検出
*/
detectStorageSizeIssues(content, filePath) {
// 大きなデータを保存しようとしているパターンを検出
const patterns = [
{
pattern: /chrome\.storage\.sync\.set\s*\(\s*\{[^}]{1000,}/g,
message: 'Large data in chrome.storage.sync detected. Sync storage has strict size limits',
suggestion: 'Use chrome.storage.local for large data. Sync storage is limited to 8KB per item'
},
{
pattern: /JSON\.stringify\s*\([^)]+\)\.length\s*>\s*8192/g,
message: 'Checking for 8KB limit suggests large data storage',
suggestion: 'Consider chunking large data or using chrome.storage.local'
}
];
patterns.forEach(({ pattern, message, suggestion }) => {
if (pattern.test(content)) {
this.issues.push({
type: 'STORAGE_SIZE_WARNING',
severity: 'warning',
file: filePath,
message,
suggestion
});
}
});
// chrome.storage.sync の制限に関する警告
if (content.includes('chrome.storage.sync')) {
const quotaChecks = [
/QUOTA_BYTES_PER_ITEM/,
/QUOTA_BYTES/,
/MAX_ITEMS/
];
const hasQuotaCheck = quotaChecks.some(check => check.test(content));
if (!hasQuotaCheck) {
this.issues.push({
type: 'MISSING_QUOTA_CHECK',
severity: 'info',
file: filePath,
message: 'chrome.storage.sync usage detected without quota checks',
suggestion: 'Consider implementing quota checks. Sync storage limits: 8KB/item, 100KB total, 512 items max'
});
}
}
}
/**
* サマリーを生成
*/
generateSummary() {
const totalLocalStorage = this.storageUsage.localStorage.reduce((sum, item) => sum + item.occurrences, 0);
const totalSessionStorage = this.storageUsage.sessionStorage.reduce((sum, item) => sum + item.occurrences, 0);
const totalChromeStorage =
this.storageUsage.chromeStorageLocal.reduce((sum, item) => sum + item.occurrences, 0) +
this.storageUsage.chromeStorageSync.reduce((sum, item) => sum + item.occurrences, 0) +
this.storageUsage.chromeStorageSession.reduce((sum, item) => sum + item.occurrences, 0);
return {
deprecatedStorageUsage: totalLocalStorage + totalSessionStorage,
chromeStorageUsage: totalChromeStorage,
migrationNeeded: totalLocalStorage + totalSessionStorage > 0,
recommendations: this.generateRecommendations()
};
}
/**
* 推奨事項を生成
*/
generateRecommendations() {
const recommendations = [];
if (this.storageUsage.localStorage.length > 0) {
recommendations.push({
priority: 'high',
type: 'migration',
message: 'Migrate from localStorage to chrome.storage.local',
details: 'localStorage is synchronous and can block the main thread. chrome.storage.local is asynchronous and more performant.'
});
}
if (this.storageUsage.sessionStorage.length > 0) {
recommendations.push({
priority: 'high',
type: 'migration',
message: 'Migrate from sessionStorage to chrome.storage.session',
details: 'chrome.storage.session provides tab-specific storage with better integration with the extension lifecycle.'
});
}
if (this.storageUsage.chromeStorageSync.length > 0) {
recommendations.push({
priority: 'medium',
type: 'optimization',
message: 'Implement proper quota management for chrome.storage.sync',
details: 'Monitor storage quota usage and implement fallback to local storage when approaching limits.'
});
}
return recommendations;
}
}
module.exports = StorageAnalyzer;