graphql-lint-clint-platform
Version:
GraphQL unused fields linter for Clint platform - Custom patterns and actions.graphql support
214 lines • 8.8 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClintUsageAnalyzer = void 0;
const fs_1 = __importDefault(require("fs"));
const glob_1 = require("glob");
class ClintUsageAnalyzer {
constructor(actionPatterns) {
this.actionPatterns = {};
this.sourceFiles = [];
this.actionPatterns = actionPatterns;
}
async findSourceFiles(sourcePath) {
const tsFiles = await (0, glob_1.glob)('**/*.{ts,tsx,js,jsx}', {
cwd: sourcePath,
ignore: ['node_modules/**', 'dist/**', 'build/**', '.git/**'],
absolute: true
});
return tsFiles;
}
async analyzeUsageInPath(sourcePath, verbose = false) {
if (verbose) {
console.log('📋 Iniciando análise DETALHADA de uso dos actions da Clint...\n');
console.log(`🎯 Total de actions detectadas: ${Object.keys(this.actionPatterns).length}`);
}
// Encontrar arquivos fonte
this.sourceFiles = await this.findSourceFiles(sourcePath);
if (verbose) {
console.log(`📁 Total de arquivos para análise: ${this.sourceFiles.length}\n`);
}
const unusedActions = [];
const usedActions = {};
// Buscar por todos os patterns de todas as actions
for (const [actionName, patterns] of Object.entries(this.actionPatterns)) {
if (verbose) {
console.log(`🔍 Analisando action: ${actionName}`);
console.log(` 🎨 Padrões gerados (${patterns.length}): ${patterns.join(', ')}`);
}
let hasUsage = false;
const actionUsages = [];
// Testar cada padrão com logging detalhado
for (const pattern of patterns) {
if (verbose) {
console.log(` 🔎 Testando padrão: "${pattern}"`);
}
const usages = await this.findUsageInFiles(pattern);
if (usages.length > 0) {
hasUsage = true;
actionUsages.push(...usages);
if (verbose) {
console.log(` ✅ ${usages.length} uso(s) encontrado(s) para "${pattern}"`);
}
}
else {
if (verbose) {
console.log(` ❌ Nenhum uso para "${pattern}"`);
}
}
}
if (hasUsage) {
usedActions[actionName] = actionUsages;
if (verbose) {
console.log(` ✅ USADO - Total: ${actionUsages.length} ocorrência(s)\n`);
}
}
else {
unusedActions.push(actionName);
if (verbose) {
console.log(` ❌ NÃO USADO - Nenhuma ocorrência encontrada\n`);
}
}
}
if (verbose) {
console.log(`📊 RESUMO DA ANÁLISE:`);
console.log(` ✅ Actions usadas: ${Object.keys(usedActions).length}`);
console.log(` ❌ Actions não usadas: ${unusedActions.length}`);
console.log(` 📈 Total: ${Object.keys(this.actionPatterns).length}\n`);
}
return {
unusedActions,
usedActions,
totalActions: Object.keys(this.actionPatterns).length
};
}
async findUsageInFiles(pattern) {
const allUsages = [];
for (const filePath of this.sourceFiles) {
const usages = await this.findUsageInFile(filePath, pattern);
allUsages.push(...usages);
}
return allUsages;
}
async findUsageInFile(filePath, pattern) {
try {
const content = fs_1.default.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const usages = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineNumber = i + 1;
// MÚLTIPLAS ESTRATÉGIAS DE BUSCA:
// 1. Busca exata pelo padrão
if (line.includes(pattern)) {
usages.push({
file: filePath,
line: lineNumber,
code: line.trim()
});
continue;
}
// 2. Busca por regex - padrões com pontos
if (pattern.includes('.')) {
// Escapar pontos para regex
const regexPattern = pattern.replace(/\./g, '\\.');
const regex = new RegExp(`\\b${regexPattern}\\b`);
if (regex.test(line)) {
usages.push({
file: filePath,
line: lineNumber,
code: line.trim()
});
continue;
}
}
// 3. Busca por partes do padrão (mais flexível)
const patternParts = pattern.split('.');
if (patternParts.length >= 2) {
// Verificar se todas as partes estão na linha
const allPartsPresent = patternParts.every(part => line.toLowerCase().includes(part.toLowerCase()));
if (allPartsPresent) {
// Verificação adicional: deve ser código, não comentário
const trimmedLine = line.trim();
if (!trimmedLine.startsWith('//') && !trimmedLine.startsWith('/*') && !trimmedLine.startsWith('*')) {
usages.push({
file: filePath,
line: lineNumber,
code: line.trim()
});
}
}
}
// 4. Busca por snake_case original (fallback)
const snakeCasePattern = pattern.replace(/\./g, '_').replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
if (line.toLowerCase().includes(snakeCasePattern)) {
usages.push({
file: filePath,
line: lineNumber,
code: line.trim()
});
}
}
if (usages.length > 0) {
console.log(` ✅ Padrão "${pattern}" encontrado ${usages.length}x em ${filePath}`);
}
return usages;
}
catch (error) {
console.warn(`⚠️ Erro ao analisar arquivo ${filePath}:`, error);
return [];
}
}
/**
* Analisa uso de actions customizadas da Clint no código
* Detecta padrões como: clint.owner.getName, clint.user.createAccount
*/
analyzeClintUsage(sourceCode, filePath) {
const usages = [];
const lines = sourceCode.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineNumber = i + 1;
// Detectar padrões clint.*.*
const clintMatches = line.matchAll(/clint\.([a-zA-Z_]+)\.([a-zA-Z_]+)/g);
for (const match of clintMatches) {
const fullPattern = match[0]; // clint.owner.getName
const column = match.index || 0;
usages.push({
pattern: fullPattern,
line: lineNumber,
column: column
});
}
}
return usages;
}
/**
* Mapeia uso de padrões Clint para actions do Hasura
*/
mapClintUsageToActions(clintUsages) {
return clintUsages.map(usage => ({
actionName: this.clintPatternToAction(usage.pattern),
line: usage.line,
column: usage.column
}));
}
/**
* Converte padrão Clint para nome de action do Hasura
* Exemplo: clint.owner.getName -> owner_get_name
*/
clintPatternToAction(pattern) {
const match = pattern.match(/^clint\.([^.]+)\.([^.]+)$/);
if (!match) {
return pattern.replace('clint.', '');
}
const [, entity, method] = match;
// Converter camelCase para snake_case
const actionPart = method.replace(/([A-Z])/g, '_$1').toLowerCase();
return `${entity}_${actionPart}`;
}
}
exports.ClintUsageAnalyzer = ClintUsageAnalyzer;
//# sourceMappingURL=clintAnalyzer.js.map