Files
agenciapsilmno/scripts/clean-auto-imports.js
2026-03-25 09:11:05 -03:00

207 lines
7.7 KiB
JavaScript

/**
* clean-auto-imports.js
*
* Remove imports manuais redundantes de arquivos .vue e .js,
* considerando que o projeto usa:
* - unplugin-auto-import → auto-importa APIs do Vue
* - unplugin-vue-components + PrimeVueResolver → auto-importa componentes PrimeVue nos templates
*
* Uso: node scripts/clean-auto-imports.js
* Dry-run (sem escrever): node scripts/clean-auto-imports.js --dry-run
*/
import { readdir, readFile, writeFile } from 'node:fs/promises';
import { join, extname, relative, basename } from 'node:path';
import { fileURLToPath } from 'node:url';
const isDryRun = process.argv.includes('--dry-run');
// ─── Lista exata do auto-imports.d.ts gerado pelo unplugin-auto-import ────────
const VUE_AUTO_IMPORTS = new Set([
'EffectScope', 'computed', 'createApp', 'customRef', 'defineAsyncComponent',
'defineComponent', 'effectScope', 'getCurrentInstance', 'getCurrentScope',
'getCurrentWatcher', 'h', 'inject', 'isProxy', 'isReactive', 'isReadonly',
'isRef', 'isShallow', 'markRaw', 'nextTick', 'onActivated', 'onBeforeMount',
'onBeforeUnmount', 'onBeforeUpdate', 'onDeactivated', 'onErrorCaptured',
'onMounted', 'onRenderTracked', 'onRenderTriggered', 'onScopeDispose',
'onServerPrefetch', 'onUnmounted', 'onUpdated', 'onWatcherCleanup', 'provide',
'reactive', 'readonly', 'ref', 'resolveComponent', 'shallowReactive',
'shallowReadonly', 'shallowRef', 'toRaw', 'toRef', 'toRefs', 'toValue',
'triggerRef', 'unref', 'useAttrs', 'useCssModule', 'useCssVars', 'useId',
'useModel', 'useSlots', 'useTemplateRef', 'watch', 'watchEffect',
'watchPostEffect', 'watchSyncEffect',
// Tipos re-exportados (import type { ... } from 'vue')
'Component', 'Slot', 'Slots', 'ComponentPublicInstance', 'ComputedRef',
'DirectiveBinding', 'ExtractDefaultPropTypes', 'ExtractPropTypes',
'ExtractPublicPropTypes', 'InjectionKey', 'PropType', 'Ref', 'ShallowRef',
'MaybeRef', 'MaybeRefOrGetter', 'VNode', 'WritableComputedRef',
]);
// ─── Paths do PrimeVue que NÃO são componentes (serviços, plugins, diretivas) ─
const PRIMEVUE_KEEP_PATHS = new Set([
'primevue/config',
'primevue/confirmationservice',
'primevue/toastservice',
'primevue/dialogservice',
'primevue/useconfirm',
'primevue/usetoast',
'primevue/usedynamicdialog',
'primevue/ripple',
'primevue/tooltip',
'primevue/focustrap',
'primevue/styleclass',
'primevue/badgedirective',
'primevue/animateonscroll',
'primevue/passthrough',
'primevue/useStyle',
]);
// ─── Arquivos a ignorar ────────────────────────────────────────────────────────
const SKIP_FILES = new Set(['main.js', 'main.ts']);
// ─────────────────────────────────────────────────────────────────────────────
async function getAllFiles(dir) {
const results = [];
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
if (!['node_modules', '.git', 'dist', '.nuxt', 'public'].includes(entry.name)) {
results.push(...await getAllFiles(fullPath));
}
} else {
const ext = extname(entry.name);
if (['.vue', '.js', '.mjs'].includes(ext) && !SKIP_FILES.has(entry.name)) {
results.push(fullPath);
}
}
}
return results;
}
/**
* Remove do `import { ... } from 'vue'` (single e multi-line)
* os símbolos que já são auto-importados.
*/
function cleanVueImports(content) {
// Captura tanto single-line quanto multi-line: import [type] { ... } from 'vue'
const importRegex = /^import\s+(type\s+)?\{([^}]+)\}\s+from\s+['"]vue['"]\s*;?/gm;
let changed = false;
const modified = content.replace(importRegex, (match, typeKeyword, namedImports) => {
const symbols = namedImports
.split(',')
.map(s => s.trim())
.filter(Boolean);
// Para cada símbolo, verifica se está na lista de auto-imports
// Suporta aliases: "ref as vRef" → extrai "ref"
const keep = symbols.filter(sym => {
const name = sym.split(/\s+as\s+/)[0].trim();
return !VUE_AUTO_IMPORTS.has(name);
});
if (keep.length === symbols.length) return match; // nada a remover
changed = true;
if (keep.length === 0) return ''; // remove a linha toda
const isType = typeKeyword ? 'type ' : '';
return `import ${isType}{ ${keep.join(', ')} } from 'vue'`;
});
return { content: modified, changed };
}
/**
* Remove `import ComponentName from 'primevue/...'` em arquivos .vue.
* Mantém serviços/plugins listados em PRIMEVUE_KEEP_PATHS.
* Mantém named imports ({ useToast }) pois esses não são resolvidos pelo resolver de componentes.
*/
function cleanPrimeVueImports(content) {
// Apenas default imports de componentes (PascalCase)
const importRegex = /^import\s+([A-Z]\w*)\s+from\s+['"]primevue\/([^'"]+)['"]\s*;?\n?/gm;
let changed = false;
const modified = content.replace(importRegex, (match, componentName, path) => {
const fullPath = `primevue/${path}`;
if (PRIMEVUE_KEEP_PATHS.has(fullPath)) return match;
changed = true;
return '';
});
return { content: modified, changed };
}
/** Remove linhas em branco consecutivas excessivas (máx 2 \n seguidos) */
function collapseBlankLines(content) {
return content.replace(/\n{3,}/g, '\n\n');
}
async function processFile(filePath) {
const ext = extname(filePath);
const original = await readFile(filePath, 'utf-8');
let modified = original;
let changed = false;
// 1. Vue auto-imports → .vue e .js
const vueResult = cleanVueImports(modified);
if (vueResult.changed) {
modified = vueResult.content;
changed = true;
}
// 2. PrimeVue component imports → apenas .vue
if (ext === '.vue') {
const pvResult = cleanPrimeVueImports(modified);
if (pvResult.changed) {
modified = pvResult.content;
changed = true;
}
}
if (changed) {
modified = collapseBlankLines(modified);
if (!isDryRun) {
await writeFile(filePath, modified, 'utf-8');
}
}
return changed;
}
// ─── Main ─────────────────────────────────────────────────────────────────────
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const srcDir = join(__dirname, '..', 'src');
console.log(isDryRun ? '🔍 Dry-run ativo — nenhum arquivo será alterado\n' : '');
console.log(`Varrendo: ${srcDir}\n`);
const files = await getAllFiles(srcDir);
console.log(`${files.length} arquivos encontrados...\n`);
let modifiedCount = 0;
let skippedCount = 0;
for (const file of files) {
const wasModified = await processFile(file);
if (wasModified) {
console.log('✓', relative(srcDir, file));
modifiedCount++;
} else {
skippedCount++;
}
}
console.log(`\n─────────────────────────────────────────`);
console.log(`Modificados : ${modifiedCount} arquivos`);
console.log(`Sem mudança : ${skippedCount} arquivos`);
if (isDryRun) {
console.log('\n⚠ Dry-run: rode sem --dry-run para aplicar as mudanças.');
}