graphql-lint-clint-platform
Version:
GraphQL unused fields linter for Clint platform - Custom patterns and actions.graphql support
164 lines (136 loc) • 4.59 kB
text/typescript
import { GraphQLQuery, GraphQLField } from "@graphql-lint/core";
import fs from "fs";
import path from "path";
import { glob } from "glob";
interface ClintAction {
name: string;
pattern: string; // clint.entity.method
file: string;
line: number;
}
interface ClintUsageResult {
name: string;
pattern: string;
file: string;
line: number;
used: boolean;
usageLocations?: Array<{
file: string;
line: number;
code: string;
}>;
}
export class ClintUsageAnalyzer {
async analyzeUsageInPath(
targetPath: string,
actions: ClintAction[],
results: ClintUsageResult[],
includePatterns: string[],
excludePatterns: string[]
): Promise<void> {
// Encontrar arquivos para analisar
const files = await this.findFilesToAnalyze(targetPath, includePatterns, excludePatterns);
// Para cada action, verificar se está sendo usada
for (const action of actions) {
const usageResult: ClintUsageResult = {
...action,
used: false,
usageLocations: []
};
// Procurar uso da action nos arquivos
for (const file of files) {
const usages = await this.findUsageInFile(file, action.pattern);
if (usages.length > 0) {
usageResult.used = true;
usageResult.usageLocations!.push(...usages);
}
}
results.push(usageResult);
}
}
private async findFilesToAnalyze(targetPath: string, includePatterns: string[], excludePatterns: string[]): Promise<string[]> {
const stats = fs.statSync(targetPath);
if (stats.isFile()) {
return [targetPath];
}
// Buscar arquivos TypeScript/JavaScript
const tsFiles = await glob("**/*.{ts,tsx,js,jsx}", {
cwd: targetPath,
ignore: excludePatterns,
absolute: true
});
return tsFiles;
}
private async findUsageInFile(filePath: string, pattern: string): Promise<Array<{file: string, line: number, code: string}>> {
try {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const usages: Array<{file: string, line: number, code: string}> = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineNumber = i + 1;
// Procurar pelo padrão clint.entity.method
if (line.includes(pattern)) {
usages.push({
file: filePath,
line: lineNumber,
code: line.trim()
});
}
}
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: string, filePath: string): Array<{pattern: string, line: number, column: number}> {
const usages: Array<{pattern: string, line: number, column: number}> = [];
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: Array<{pattern: string, line: number, column: number}>): Array<{actionName: string, line: number, column: number}> {
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
*/
private clintPatternToAction(pattern: string): string {
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}`;
}
}
export { ClintAction, ClintUsageResult };