mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
- 创建用户表(users)包含基本信息和认证字段 - 创建角色表(roles)用于权限控制 - 创建权限表(permissions)定义系统权限 - 创建用户角色关联表(user_roles)建立用户与角色关系 - 创建角色权限关联表(role_permissions)建立角色与权限关系 - 创建迁移记录表(migrations)追踪数据库变更 - 添加AdminController提供管理员面板功能 - 实现系统监控、配置管理、缓存清理等功能 - 添加AOP切面编程支持的各种通知类型 - 实现告警管理AlertManager支持多渠道告警 - 添加文档注解接口规范
726 lines
21 KiB
PHP
726 lines
21 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Fendx\I18n\Tools\Extractor;
|
|
|
|
use Fendx\I18n\Tools\Extractor\Parser\PhpParser;
|
|
use Fendx\I18n\Tools\Extractor\Parser\JsParser;
|
|
use Fendx\I18n\Tools\Extractor\Parser\HtmlParser;
|
|
use Fendx\I18n\Tools\Extractor\Parser\TwigParser;
|
|
use Fendx\I18n\Tools\Extractor\Parser\VueParser;
|
|
|
|
class TranslationKeyExtractor
|
|
{
|
|
protected PhpParser $phpParser;
|
|
protected JsParser $jsParser;
|
|
protected HtmlParser $htmlParser;
|
|
protected TwigParser $twigParser;
|
|
protected VueParser $vueParser;
|
|
protected array $config = [];
|
|
protected array $extractedKeys = [];
|
|
protected array $fileScanned = [];
|
|
|
|
public function __construct(array $config = [])
|
|
{
|
|
$this->config = array_merge($this->getDefaultConfig(), $config);
|
|
$this->phpParser = new PhpParser($this->config);
|
|
$this->jsParser = new JsParser($this->config);
|
|
$this->htmlParser = new HtmlParser($this->config);
|
|
$this->twigParser = new TwigParser($this->config);
|
|
$this->vueParser = new VueParser($this->config);
|
|
}
|
|
|
|
/**
|
|
* Extract translation keys from directory.
|
|
*/
|
|
public function extractFromDirectory(string $directory, array $options = []): array
|
|
{
|
|
$this->extractedKeys = [];
|
|
$this->fileScanned = [];
|
|
|
|
$recursive = $options['recursive'] ?? true;
|
|
$patterns = $options['patterns'] ?? $this->config['file_patterns'];
|
|
$excludePatterns = $options['exclude_patterns'] ?? $this->config['exclude_patterns'];
|
|
|
|
$this->scanDirectory($directory, $recursive, $patterns, $excludePatterns);
|
|
|
|
return $this->getExtractedKeys();
|
|
}
|
|
|
|
/**
|
|
* Extract translation keys from file.
|
|
*/
|
|
public function extractFromFile(string $filepath): array
|
|
{
|
|
$this->extractedKeys = [];
|
|
$this->fileScanned = [];
|
|
|
|
if (!file_exists($filepath)) {
|
|
throw new \InvalidArgumentException("File not found: {$filepath}");
|
|
}
|
|
|
|
$this->scanFile($filepath);
|
|
|
|
return $this->getExtractedKeys();
|
|
}
|
|
|
|
/**
|
|
* Extract translation keys from string content.
|
|
*/
|
|
public function extractFromString(string $content, string $filename = 'unknown'): array
|
|
{
|
|
$this->extractedKeys = [];
|
|
$this->fileScanned = [];
|
|
|
|
$this->parseContent($content, $filename);
|
|
|
|
return $this->getExtractedKeys();
|
|
}
|
|
|
|
/**
|
|
* Scan directory for files.
|
|
*/
|
|
protected function scanDirectory(string $directory, bool $recursive, array $patterns, array $excludePatterns): void
|
|
{
|
|
$iterator = new \RecursiveIteratorIterator(
|
|
$recursive ? new \RecursiveDirectoryIterator($directory) : new \DirectoryIterator($directory),
|
|
\RecursiveIteratorIterator::SELF_FIRST
|
|
);
|
|
|
|
foreach ($iterator as $file) {
|
|
if ($file->isDir()) {
|
|
continue;
|
|
}
|
|
|
|
$filepath = $file->getPathname();
|
|
|
|
// Check exclude patterns
|
|
if ($this->matchesExcludePatterns($filepath, $excludePatterns)) {
|
|
continue;
|
|
}
|
|
|
|
// Check include patterns
|
|
if ($this->matchesPatterns($filepath, $patterns)) {
|
|
$this->scanFile($filepath);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan single file.
|
|
*/
|
|
protected function scanFile(string $filepath): void
|
|
{
|
|
if (in_array($filepath, $this->fileScanned)) {
|
|
return;
|
|
}
|
|
|
|
$content = file_get_contents($filepath);
|
|
if ($content === false) {
|
|
return;
|
|
}
|
|
|
|
$this->fileScanned[] = $filepath;
|
|
$this->parseContent($content, $filepath);
|
|
}
|
|
|
|
/**
|
|
* Parse content based on file type.
|
|
*/
|
|
protected function parseContent(string $content, string $filename): void
|
|
{
|
|
$extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
|
|
|
|
switch ($extension) {
|
|
case 'php':
|
|
$this->extractFromPhp($content, $filename);
|
|
break;
|
|
case 'js':
|
|
case 'jsx':
|
|
case 'ts':
|
|
case 'tsx':
|
|
$this->extractFromJs($content, $filename);
|
|
break;
|
|
case 'html':
|
|
case 'htm':
|
|
$this->extractFromHtml($content, $filename);
|
|
break;
|
|
case 'twig':
|
|
$this->extractFromTwig($content, $filename);
|
|
break;
|
|
case 'vue':
|
|
$this->extractFromVue($content, $filename);
|
|
break;
|
|
default:
|
|
// Try to detect content type
|
|
if ($this->isPhpContent($content)) {
|
|
$this->extractFromPhp($content, $filename);
|
|
} elseif ($this->isJsContent($content)) {
|
|
$this->extractFromJs($content, $filename);
|
|
} elseif ($this->isHtmlContent($content)) {
|
|
$this->extractFromHtml($content, $filename);
|
|
} elseif ($this->isTwigContent($content)) {
|
|
$this->extractFromTwig($content, $filename);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract from PHP content.
|
|
*/
|
|
protected function extractFromPhp(string $content, string $filename): void
|
|
{
|
|
$keys = $this->phpParser->extract($content);
|
|
$this->addExtractedKeys($keys, $filename, 'php');
|
|
}
|
|
|
|
/**
|
|
* Extract from JavaScript content.
|
|
*/
|
|
protected function extractFromJs(string $content, string $filename): void
|
|
{
|
|
$keys = $this->jsParser->extract($content);
|
|
$this->addExtractedKeys($keys, $filename, 'js');
|
|
}
|
|
|
|
/**
|
|
* Extract from HTML content.
|
|
*/
|
|
protected function extractFromHtml(string $content, string $filename): void
|
|
{
|
|
$keys = $this->htmlParser->extract($content);
|
|
$this->addExtractedKeys($keys, $filename, 'html');
|
|
}
|
|
|
|
/**
|
|
* Extract from Twig content.
|
|
*/
|
|
protected function extractFromTwig(string $content, string $filename): void
|
|
{
|
|
$keys = $this->twigParser->extract($content);
|
|
$this->addExtractedKeys($keys, $filename, 'twig');
|
|
}
|
|
|
|
/**
|
|
* Extract from Vue content.
|
|
*/
|
|
protected function extractFromVue(string $content, string $filename): void
|
|
{
|
|
$keys = $this->vueParser->extract($content);
|
|
$this->addExtractedKeys($keys, $filename, 'vue');
|
|
}
|
|
|
|
/**
|
|
* Add extracted keys.
|
|
*/
|
|
protected function addExtractedKeys(array $keys, string $filename, string $type): void
|
|
{
|
|
foreach ($keys as $key => $info) {
|
|
if (!isset($this->extractedKeys[$key])) {
|
|
$this->extractedKeys[$key] = [
|
|
'key' => $key,
|
|
'files' => [],
|
|
'contexts' => [],
|
|
'parameters' => [],
|
|
'types' => [],
|
|
'line_numbers' => []
|
|
];
|
|
}
|
|
|
|
$this->extractedKeys[$key]['files'][] = $filename;
|
|
$this->extractedKeys[$key]['types'][] = $type;
|
|
|
|
if (isset($info['context'])) {
|
|
$this->extractedKeys[$key]['contexts'][] = $info['context'];
|
|
}
|
|
|
|
if (isset($info['parameters'])) {
|
|
$this->extractedKeys[$key]['parameters'] = array_merge(
|
|
$this->extractedKeys[$key]['parameters'],
|
|
$info['parameters']
|
|
);
|
|
}
|
|
|
|
if (isset($info['line'])) {
|
|
$this->extractedKeys[$key]['line_numbers'][] = $filename . ':' . $info['line'];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get extracted keys.
|
|
*/
|
|
public function getExtractedKeys(): array
|
|
{
|
|
// Remove duplicates and sort
|
|
$result = [];
|
|
foreach ($this->extractedKeys as $key => $info) {
|
|
$result[$key] = [
|
|
'key' => $key,
|
|
'files' => array_unique($info['files']),
|
|
'contexts' => array_unique($info['contexts']),
|
|
'parameters' => array_unique($info['parameters']),
|
|
'types' => array_unique($info['types']),
|
|
'line_numbers' => array_unique($info['line_numbers']),
|
|
'usage_count' => count($info['files'])
|
|
];
|
|
}
|
|
|
|
ksort($result);
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get keys by file.
|
|
*/
|
|
public function getKeysByFile(): array
|
|
{
|
|
$keysByFile = [];
|
|
|
|
foreach ($this->extractedKeys as $key => $info) {
|
|
foreach ($info['files'] as $file) {
|
|
if (!isset($keysByFile[$file])) {
|
|
$keysByFile[$file] = [];
|
|
}
|
|
$keysByFile[$file][] = $key;
|
|
}
|
|
}
|
|
|
|
return $keysByFile;
|
|
}
|
|
|
|
/**
|
|
* Get keys by type.
|
|
*/
|
|
public function getKeysByType(): array
|
|
{
|
|
$keysByType = [];
|
|
|
|
foreach ($this->extractedKeys as $key => $info) {
|
|
foreach ($info['types'] as $type) {
|
|
if (!isset($keysByType[$type])) {
|
|
$keysByType[$type] = [];
|
|
}
|
|
$keysByType[$type][] = $key;
|
|
}
|
|
}
|
|
|
|
return $keysByType;
|
|
}
|
|
|
|
/**
|
|
* Get unused keys (keys not found in any file).
|
|
*/
|
|
public function getUnusedKeys(array $existingKeys): array
|
|
{
|
|
$extractedKeySet = array_keys($this->extractedKeys);
|
|
$unusedKeys = array_diff($existingKeys, $extractedKeySet);
|
|
|
|
return array_values($unusedKeys);
|
|
}
|
|
|
|
/**
|
|
* Get missing keys (keys found but not in existing translations).
|
|
*/
|
|
public function getMissingKeys(array $existingKeys): array
|
|
{
|
|
$extractedKeySet = array_keys($this->extractedKeys);
|
|
$missingKeys = array_diff($extractedKeySet, $existingKeys);
|
|
|
|
return array_values($missingKeys);
|
|
}
|
|
|
|
/**
|
|
* Get keys with parameters.
|
|
*/
|
|
public function getKeysWithParameters(): array
|
|
{
|
|
$keysWithParams = [];
|
|
|
|
foreach ($this->extractedKeys as $key => $info) {
|
|
if (!empty($info['parameters'])) {
|
|
$keysWithParams[$key] = array_unique($info['parameters']);
|
|
}
|
|
}
|
|
|
|
return $keysWithParams;
|
|
}
|
|
|
|
/**
|
|
* Get duplicate keys (keys found in multiple contexts).
|
|
*/
|
|
public function getDuplicateKeys(): array
|
|
{
|
|
$duplicates = [];
|
|
|
|
foreach ($this->extractedKeys as $key => $info) {
|
|
if (count($info['contexts']) > 1) {
|
|
$duplicates[$key] = [
|
|
'contexts' => array_unique($info['contexts']),
|
|
'files' => array_unique($info['files'])
|
|
];
|
|
}
|
|
}
|
|
|
|
return $duplicates;
|
|
}
|
|
|
|
/**
|
|
* Generate translation template.
|
|
*/
|
|
public function generateTemplate(array $existingKeys = null, string $language = 'en'): array
|
|
{
|
|
$template = [];
|
|
$extractedKeys = array_keys($this->extractedKeys);
|
|
|
|
if ($existingKeys) {
|
|
// Merge with existing keys
|
|
$allKeys = array_unique(array_merge($extractedKeys, $existingKeys));
|
|
} else {
|
|
$allKeys = $extractedKeys;
|
|
}
|
|
|
|
foreach ($allKeys as $key) {
|
|
$template[$key] = $this->generatePlaceholder($key, $language);
|
|
}
|
|
|
|
return $template;
|
|
}
|
|
|
|
/**
|
|
* Generate placeholder for translation key.
|
|
*/
|
|
protected function generatePlaceholder(string $key, string $language): string
|
|
{
|
|
$parts = explode('.', $key);
|
|
$lastPart = end($parts);
|
|
|
|
// Convert to human readable format
|
|
$placeholder = str_replace(['_', '-'], ' ', $lastPart);
|
|
$placeholder = ucwords($placeholder);
|
|
|
|
// Add language-specific prefix/suffix if needed
|
|
switch ($language) {
|
|
case 'zh-CN':
|
|
case 'zh-TW':
|
|
return $placeholder; // Chinese doesn't need articles
|
|
case 'ja':
|
|
return $placeholder; // Japanese doesn't need articles
|
|
case 'ko':
|
|
return $placeholder; // Korean doesn't need articles
|
|
case 'fr':
|
|
return "La {$placeholder}"; // French article
|
|
case 'de':
|
|
return "Der {$placeholder}"; // German article
|
|
case 'es':
|
|
return "El {$placeholder}"; // Spanish article
|
|
case 'it':
|
|
return "Il {$placeholder}"; // Italian article
|
|
case 'ru':
|
|
return "{$placeholder} (русский)"; // Russian suffix
|
|
case 'ar':
|
|
return "{$placeholder} (عربي)"; // Arabic suffix
|
|
default:
|
|
return "The {$placeholder}"; // English article
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export extracted keys.
|
|
*/
|
|
public function export(string $format = 'json'): string
|
|
{
|
|
$data = [
|
|
'extracted_keys' => $this->getExtractedKeys(),
|
|
'statistics' => $this->getStatistics(),
|
|
'generated_at' => date('Y-m-d H:i:s'),
|
|
'config' => $this->config
|
|
];
|
|
|
|
return match ($format) {
|
|
'json' => json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE),
|
|
'csv' => $this->exportToCsv(),
|
|
'xlsx' => $this->exportToXlsx(),
|
|
'php' => '<?php return ' . var_export($this->getExtractedKeys(), true) . ';',
|
|
default => throw new \InvalidArgumentException("Unsupported export format: {$format}")
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Export to CSV format.
|
|
*/
|
|
protected function exportToCsv(): string
|
|
{
|
|
$csv = "Key,Files,Types,Contexts,Parameters,Usage Count\n";
|
|
|
|
foreach ($this->getExtractedKeys() as $key => $info) {
|
|
$csv .= '"' . $key . '",';
|
|
$csv .= '"' . implode('; ', $info['files']) . '",';
|
|
$csv .= '"' . implode('; ', $info['types']) . '",';
|
|
$csv .= '"' . implode('; ', $info['contexts']) . '",';
|
|
$csv .= '"' . implode('; ', $info['parameters']) . '",';
|
|
$csv .= $info['usage_count'] . "\n";
|
|
}
|
|
|
|
return $csv;
|
|
}
|
|
|
|
/**
|
|
* Export to XLSX format (basic implementation).
|
|
*/
|
|
protected function exportToXlsx(): string
|
|
{
|
|
// This would require a proper XLSX library
|
|
// For now, return CSV as placeholder
|
|
return $this->exportToCsv();
|
|
}
|
|
|
|
/**
|
|
* Get extraction statistics.
|
|
*/
|
|
public function getStatistics(): array
|
|
{
|
|
$keysByType = $this->getKeysByType();
|
|
$keysByFile = $this->getKeysByFile();
|
|
|
|
return [
|
|
'total_keys' => count($this->extractedKeys),
|
|
'total_files_scanned' => count($this->fileScanned),
|
|
'keys_by_type' => array_map('count', $keysByType),
|
|
'keys_by_file' => array_map('count', $keysByFile),
|
|
'keys_with_parameters' => count($this->getKeysWithParameters()),
|
|
'duplicate_keys' => count($this->getDuplicateKeys()),
|
|
'most_used_keys' => $this->getMostUsedKeys(10),
|
|
'file_types' => array_unique(array_merge(...array_values($keysByType)))
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get most used keys.
|
|
*/
|
|
protected function getMostUsedKeys(int $limit = 10): array
|
|
{
|
|
$keys = $this->getExtractedKeys();
|
|
uasort($keys, fn($a, $b) => $b['usage_count'] - $a['usage_count']);
|
|
|
|
return array_slice($keys, 0, $limit, true);
|
|
}
|
|
|
|
/**
|
|
* Check if file matches patterns.
|
|
*/
|
|
protected function matchesPatterns(string $filepath, array $patterns): bool
|
|
{
|
|
foreach ($patterns as $pattern) {
|
|
if (fnmatch($pattern, basename($filepath)) || fnmatch($pattern, $filepath)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if file matches exclude patterns.
|
|
*/
|
|
protected function matchesExcludePatterns(string $filepath, array $patterns): bool
|
|
{
|
|
foreach ($patterns as $pattern) {
|
|
if (fnmatch($pattern, basename($filepath)) || fnmatch($pattern, $filepath)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check if content is PHP.
|
|
*/
|
|
protected function isPhpContent(string $content): bool
|
|
{
|
|
return str_contains($content, '<?php') ||
|
|
preg_match('/\b(?:function|class|interface|trait|namespace|use|require|include)\b/', $content);
|
|
}
|
|
|
|
/**
|
|
* Check if content is JavaScript.
|
|
*/
|
|
protected function isJsContent(string $content): bool
|
|
{
|
|
return preg_match('/\b(?:function|const|let|var|import|export|class|extends)\b/', $content) ||
|
|
str_contains($content, '=>') ||
|
|
str_contains($content, 'React');
|
|
}
|
|
|
|
/**
|
|
* Check if content is HTML.
|
|
*/
|
|
protected function isHtmlContent(string $content): bool
|
|
{
|
|
return preg_match('/<[^>]+>/', $content) &&
|
|
(str_contains($content, '<html') || str_contains($content, '<div') || str_contains($content, '<span'));
|
|
}
|
|
|
|
/**
|
|
* Check if content is Twig.
|
|
*/
|
|
protected function isTwigContent(string $content): bool
|
|
{
|
|
return preg_match('/\{\{.*?\}\}|\{%.*?%\}/', $content);
|
|
}
|
|
|
|
/**
|
|
* Add custom parser.
|
|
*/
|
|
public function addParser(string $extension, callable $parser): void
|
|
{
|
|
$this->customParsers[$extension] = $parser;
|
|
}
|
|
|
|
/**
|
|
* Set custom patterns.
|
|
*/
|
|
public function setPatterns(array $patterns): void
|
|
{
|
|
$this->config['file_patterns'] = $patterns;
|
|
}
|
|
|
|
/**
|
|
* Set exclude patterns.
|
|
*/
|
|
public function setExcludePatterns(array $patterns): void
|
|
{
|
|
$this->config['exclude_patterns'] = $patterns;
|
|
}
|
|
|
|
/**
|
|
* Reset extractor state.
|
|
*/
|
|
public function reset(): void
|
|
{
|
|
$this->extractedKeys = [];
|
|
$this->fileScanned = [];
|
|
}
|
|
|
|
/**
|
|
* Get default configuration.
|
|
*/
|
|
protected function getDefaultConfig(): array
|
|
{
|
|
return [
|
|
'file_patterns' => [
|
|
'*.php',
|
|
'*.js',
|
|
'*.jsx',
|
|
'*.ts',
|
|
'*.tsx',
|
|
'*.html',
|
|
'*.htm',
|
|
'*.twig',
|
|
'*.vue'
|
|
],
|
|
'exclude_patterns' => [
|
|
'vendor/*',
|
|
'node_modules/*',
|
|
'.git/*',
|
|
'storage/*',
|
|
'bootstrap/*',
|
|
'config/*',
|
|
'routes/*',
|
|
'tests/*',
|
|
'*.min.js',
|
|
'*.min.css',
|
|
'*.cache'
|
|
],
|
|
'php_patterns' => [
|
|
'/trans\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/__\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/t\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/translate\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/Lang::get\s*\(\s*[\'"]([^\'"]+)[\'"]/'
|
|
],
|
|
'js_patterns' => [
|
|
'/trans\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/t\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/\$t\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/translate\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/i18n\.t\s*\(\s*[\'"]([^\'"]+)[\'"]/'
|
|
],
|
|
'html_patterns' => [
|
|
'/data-i18n\s*=\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/data-translate\s*=\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/data-trans\s*=\s*[\'"]([^\'"]+)[\'"]/'
|
|
],
|
|
'twig_patterns' => [
|
|
'/\{\{\s*trans\s*\([\'"]([^\'"]+)[\'"]/',
|
|
'/\{\{\s*t\s*\([\'"]([^\'"]+)[\'"]/',
|
|
'/\{\{\s*__\s*\([\'"]([^\'"]+)[\'"]/'
|
|
],
|
|
'vue_patterns' => [
|
|
'/\$t\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/t\s*\(\s*[\'"]([^\'"]+)[\'"]/',
|
|
'/v-t\s*=\s*[\'"]([^\'"]+)[\'"]/'
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get configuration.
|
|
*/
|
|
public function getConfig(): array
|
|
{
|
|
return $this->config;
|
|
}
|
|
|
|
/**
|
|
* Set configuration.
|
|
*/
|
|
public function setConfig(array $config): void
|
|
{
|
|
$this->config = array_merge($this->config, $config);
|
|
}
|
|
|
|
/**
|
|
* Create extractor instance.
|
|
*/
|
|
public static function create(array $config = []): self
|
|
{
|
|
return new self($config);
|
|
}
|
|
|
|
/**
|
|
* Create for PHP project.
|
|
*/
|
|
public static function forPhp(): self
|
|
{
|
|
return new self([
|
|
'file_patterns' => ['*.php', '*.twig'],
|
|
'exclude_patterns' => ['vendor/*', 'node_modules/*', 'storage/*']
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create for JavaScript project.
|
|
*/
|
|
public static function forJs(): self
|
|
{
|
|
return new self([
|
|
'file_patterns' => ['*.js', '*.jsx', '*.ts', '*.tsx', '*.vue', '*.html'],
|
|
'exclude_patterns' => ['node_modules/*', 'dist/*', 'build/*']
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Create for full-stack project.
|
|
*/
|
|
public static function forFullStack(): self
|
|
{
|
|
return new self([
|
|
'file_patterns' => ['*.php', '*.js', '*.jsx', '*.ts', '*.tsx', '*.vue', '*.html', '*.twig'],
|
|
'exclude_patterns' => ['vendor/*', 'node_modules/*', 'storage/*', 'dist/*', 'build/*']
|
|
]);
|
|
}
|
|
}
|