docstodev 1.0.0 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ai/analyzer.ts +6 -2
- package/src/analyzers/languageAnalyzer.ts +580 -0
- package/src/cache/cacheManager.ts +179 -0
- package/src/cli/index.ts +307 -23
- package/src/commands/generateSummary.ts +129 -39
- package/src/commands/run.ts +312 -75
- package/src/exporters/html.ts +604 -106
- package/.env +0 -1
- package/dist/ai/analyzer.d.ts +0 -3
- package/dist/ai/analyzer.d.ts.map +0 -1
- package/dist/ai/analyzer.js +0 -42
- package/dist/ai/analyzer.js.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -59
- package/dist/cli/index.js.map +0 -1
- package/dist/commands/classify.d.ts +0 -2
- package/dist/commands/classify.d.ts.map +0 -1
- package/dist/commands/classify.js +0 -37
- package/dist/commands/classify.js.map +0 -1
- package/dist/commands/generateSummary.d.ts +0 -5
- package/dist/commands/generateSummary.d.ts.map +0 -1
- package/dist/commands/generateSummary.js +0 -54
- package/dist/commands/generateSummary.js.map +0 -1
- package/dist/commands/run.d.ts +0 -2
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -142
- package/dist/commands/run.js.map +0 -1
- package/dist/exporters/html.d.ts +0 -2
- package/dist/exporters/html.d.ts.map +0 -1
- package/dist/exporters/html.js +0 -148
- package/dist/exporters/html.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +0 -1
- package/docs/docs-to-dev.md +0 -111
- package/docs/report.html +0 -90
- package/docs/report.pdf +0 -0
- package/docs/summary.md +0 -16
- package/src/commands/classify.ts +0 -40
- package/src/index.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "docstodev",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Solution d’automatisation de documentation technique intelligente avec IA",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli/index.js",
|
|
@@ -36,4 +36,4 @@
|
|
|
36
36
|
"globby": "^14.0.2",
|
|
37
37
|
"puppeteer": "^24.1.1"
|
|
38
38
|
}
|
|
39
|
-
}
|
|
39
|
+
}
|
package/src/ai/analyzer.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
import "dotenv/config"; // Pour lire GROQ_API_KEY dans ton .env
|
|
4
4
|
|
|
5
5
|
export async function askAI(technicalContext: string) {
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
// clé par défaut gratuite
|
|
8
|
+
const GROQ_API_KEY = process.env.GROQ_API_KEY || "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";
|
|
7
9
|
|
|
8
10
|
if (!GROQ_API_KEY) {
|
|
9
11
|
return "⚠️ Erreur : Clé API Groq manquante dans le fichier .env";
|
|
@@ -17,7 +19,9 @@ Instructions :
|
|
|
17
19
|
- Vous allez recevoir une liste de fichiers, leurs rôles et leurs exports.
|
|
18
20
|
- Pour chaque fichier, rédigez UNE SEULE phrase concise expliquant sa responsabilité métier.
|
|
19
21
|
- Soyez pro, mais gardez votre touche amicale et votre pointe d'humour du Congo-Brazzaville.
|
|
20
|
-
-
|
|
22
|
+
- mentionnez les imports, technique ( "il y a une fonction X"), expliquez le BUT du fichier.
|
|
23
|
+
- selon le nombre d'occurance précisez les couleurs qui reviennen souvent eu suggérez un design systèm , pallette de couleur.
|
|
24
|
+
- donnez une très bref description du but de l'ensemble du projet.
|
|
21
25
|
`;
|
|
22
26
|
|
|
23
27
|
try {
|
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
export interface LanguageAnalyzer {
|
|
2
|
+
extensions: string[];
|
|
3
|
+
analyzeFile(content: string): FileAnalysisResult;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface FileAnalysisResult {
|
|
7
|
+
functions: string[];
|
|
8
|
+
classes: string[];
|
|
9
|
+
types: string[];
|
|
10
|
+
imports: Array<{ name: string; type: string; usage?: string | undefined }>;
|
|
11
|
+
exports: string[];
|
|
12
|
+
structure?: Record<string, any>; // Pour les fichiers non-standards
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Analyseur TypeScript/JavaScript
|
|
16
|
+
class TSJSAnalyzer implements LanguageAnalyzer {
|
|
17
|
+
extensions = [".ts", ".js", ".tsx", ".jsx", ".mjs", ".cjs"];
|
|
18
|
+
|
|
19
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
20
|
+
const exports: string[] = [];
|
|
21
|
+
const exportRegex = /export\s+(?:default\s+)?(?:async\s+)?(?:function|const|let|class|type|interface|enum)\s+([a-zA-Z0-9_]+)/g;
|
|
22
|
+
let match: RegExpExecArray | null;
|
|
23
|
+
while ((match = exportRegex.exec(content)) !== null) {
|
|
24
|
+
if (match[1]) exports.push(match[1]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const functions: string[] = [];
|
|
28
|
+
const funcRegex = /(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z0-9_]+)/g;
|
|
29
|
+
funcRegex.lastIndex = 0;
|
|
30
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
31
|
+
if (match[1]) functions.push(match[1]);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const arrowRegex = /(?:export\s+)?const\s+([a-zA-Z0-9_]+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g;
|
|
35
|
+
while ((match = arrowRegex.exec(content)) !== null) {
|
|
36
|
+
if (match[1]) functions.push(match[1]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const classes: string[] = [];
|
|
40
|
+
const classRegex = /(?:export\s+)?(?:abstract\s+)?class\s+([a-zA-Z0-9_]+)/g;
|
|
41
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
42
|
+
if (match[1]) classes.push(match[1]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const types: string[] = [];
|
|
46
|
+
const typeRegex = /(?:export\s+)?(?:type|interface)\s+([a-zA-Z0-9_]+)/g;
|
|
47
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
48
|
+
if (match[1]) types.push(match[1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
const importLines = lines.filter(l => l.trim().startsWith("import "));
|
|
54
|
+
|
|
55
|
+
importLines.forEach(line => {
|
|
56
|
+
const fromMatch = line.match(/from ['"]([^'"]+)['"]/);
|
|
57
|
+
if (fromMatch?.[1]) {
|
|
58
|
+
const name = fromMatch[1];
|
|
59
|
+
const type = name.startsWith('.') ? 'Interne' : (name.startsWith('node:') ? 'Node.js' : 'NPM');
|
|
60
|
+
const importedMatch = line.match(/import\s+(?:\{([^}]+)\}|(\w+))/);
|
|
61
|
+
let usage: string | undefined = undefined;
|
|
62
|
+
if (importedMatch) {
|
|
63
|
+
const items = importedMatch[1] || importedMatch[2];
|
|
64
|
+
usage = items?.trim().split(',').map(s => s.trim()).join(', ');
|
|
65
|
+
}
|
|
66
|
+
imports.push({ name, type, usage });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return { functions, classes, types, imports, exports };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Analyseur Python
|
|
75
|
+
class PythonAnalyzer implements LanguageAnalyzer {
|
|
76
|
+
extensions = [".py"];
|
|
77
|
+
|
|
78
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
79
|
+
const functions: string[] = [];
|
|
80
|
+
const funcRegex = /def\s+([a-zA-Z0-9_]+)\s*\(/g;
|
|
81
|
+
let match: RegExpExecArray | null;
|
|
82
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
83
|
+
if (match[1] && !match[1].startsWith('__')) functions.push(match[1]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const classes: string[] = [];
|
|
87
|
+
const classRegex = /class\s+([a-zA-Z0-9_]+)(?:\(|:)/g;
|
|
88
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
89
|
+
if (match[1]) classes.push(match[1]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
93
|
+
const lines = content.split(/\r?\n/);
|
|
94
|
+
|
|
95
|
+
lines.forEach(line => {
|
|
96
|
+
const importMatch = line.match(/^import\s+([a-zA-Z0-9_., ]+)/);
|
|
97
|
+
const fromMatch = line.match(/^from\s+([a-zA-Z0-9_.]+)\s+import\s+(.+)/);
|
|
98
|
+
|
|
99
|
+
if (importMatch?.[1]) {
|
|
100
|
+
const modules = importMatch[1].split(',').map(m => m.trim());
|
|
101
|
+
modules.forEach(mod => {
|
|
102
|
+
const type = mod.startsWith('.') ? 'Interne' : 'PyPI';
|
|
103
|
+
imports.push({ name: mod, type });
|
|
104
|
+
});
|
|
105
|
+
} else if (fromMatch?.[1]) {
|
|
106
|
+
const moduleName = fromMatch[1];
|
|
107
|
+
const imported = fromMatch[2]?.trim();
|
|
108
|
+
const type = moduleName.startsWith('.') ? 'Interne' : 'PyPI';
|
|
109
|
+
imports.push({ name: moduleName, type, usage: imported });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return { functions, classes, types: [], imports, exports: [] };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Analyseur Java
|
|
118
|
+
class JavaAnalyzer implements LanguageAnalyzer {
|
|
119
|
+
extensions = [".java"];
|
|
120
|
+
|
|
121
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
122
|
+
const classes: string[] = [];
|
|
123
|
+
const classRegex = /(?:public\s+)?(?:abstract\s+)?class\s+([a-zA-Z0-9_]+)/g;
|
|
124
|
+
let match: RegExpExecArray | null;
|
|
125
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
126
|
+
if (match[1]) classes.push(match[1]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const interfaceRegex = /(?:public\s+)?interface\s+([a-zA-Z0-9_]+)/g;
|
|
130
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
131
|
+
if (match[1]) classes.push(match[1]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const functions: string[] = [];
|
|
135
|
+
const methodRegex = /(?:public|private|protected)\s+(?:static\s+)?(?:\w+(?:<[^>]+>)?)\s+([a-zA-Z0-9_]+)\s*\(/g;
|
|
136
|
+
while ((match = methodRegex.exec(content)) !== null) {
|
|
137
|
+
if (match[1]) functions.push(match[1]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
141
|
+
const lines = content.split(/\r?\n/);
|
|
142
|
+
|
|
143
|
+
lines.forEach(line => {
|
|
144
|
+
const importMatch = line.match(/^import\s+([a-zA-Z0-9_.]+);/);
|
|
145
|
+
if (importMatch?.[1]) {
|
|
146
|
+
const name = importMatch[1];
|
|
147
|
+
const type = name.startsWith('java.') || name.startsWith('javax.') ? 'JDK' : 'Maven';
|
|
148
|
+
imports.push({ name, type });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return { functions, classes, types: [], imports, exports: [] };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Analyseur C#
|
|
157
|
+
class CSharpAnalyzer implements LanguageAnalyzer {
|
|
158
|
+
extensions = [".cs"];
|
|
159
|
+
|
|
160
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
161
|
+
const classes: string[] = [];
|
|
162
|
+
const classRegex = /(?:public\s+)?(?:abstract\s+)?(?:partial\s+)?class\s+([a-zA-Z0-9_]+)/g;
|
|
163
|
+
let match: RegExpExecArray | null;
|
|
164
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
165
|
+
if (match[1]) classes.push(match[1]);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const interfaceRegex = /(?:public\s+)?interface\s+([a-zA-Z0-9_]+)/g;
|
|
169
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
170
|
+
if (match[1]) classes.push(match[1]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const functions: string[] = [];
|
|
174
|
+
const methodRegex = /(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:\w+(?:<[^>]+>)?)\s+([a-zA-Z0-9_]+)\s*\(/g;
|
|
175
|
+
while ((match = methodRegex.exec(content)) !== null) {
|
|
176
|
+
if (match[1]) functions.push(match[1]);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
180
|
+
const lines = content.split(/\r?\n/);
|
|
181
|
+
|
|
182
|
+
lines.forEach(line => {
|
|
183
|
+
const usingMatch = line.match(/^using\s+([a-zA-Z0-9_.]+);/);
|
|
184
|
+
if (usingMatch?.[1]) {
|
|
185
|
+
const name = usingMatch[1];
|
|
186
|
+
const type = name.startsWith('System.') ? '.NET' : 'NuGet';
|
|
187
|
+
imports.push({ name, type });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return { functions, classes, types: [], imports, exports: [] };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Analyseur Go
|
|
196
|
+
class GoAnalyzer implements LanguageAnalyzer {
|
|
197
|
+
extensions = [".go"];
|
|
198
|
+
|
|
199
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
200
|
+
const functions: string[] = [];
|
|
201
|
+
const funcRegex = /func\s+(?:\([^)]+\)\s+)?([a-zA-Z0-9_]+)\s*\(/g;
|
|
202
|
+
let match: RegExpExecArray | null;
|
|
203
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
204
|
+
if (match[1]) functions.push(match[1]);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const types: string[] = [];
|
|
208
|
+
const typeRegex = /type\s+([a-zA-Z0-9_]+)\s+(?:struct|interface)/g;
|
|
209
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
210
|
+
if (match[1]) types.push(match[1]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
214
|
+
const lines = content.split(/\r?\n/);
|
|
215
|
+
|
|
216
|
+
lines.forEach(line => {
|
|
217
|
+
const importMatch = line.match(/^\s*"([^"]+)"/);
|
|
218
|
+
if (importMatch?.[1]) {
|
|
219
|
+
const name = importMatch[1];
|
|
220
|
+
const type = name.includes('.') ? 'External' : 'Standard';
|
|
221
|
+
imports.push({ name, type });
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
return { functions, classes: [], types, imports, exports: [] };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Analyseur Rust
|
|
230
|
+
class RustAnalyzer implements LanguageAnalyzer {
|
|
231
|
+
extensions = [".rs"];
|
|
232
|
+
|
|
233
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
234
|
+
const functions: string[] = [];
|
|
235
|
+
const funcRegex = /(?:pub\s+)?fn\s+([a-zA-Z0-9_]+)/g;
|
|
236
|
+
let match: RegExpExecArray | null;
|
|
237
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
238
|
+
if (match[1]) functions.push(match[1]);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const types: string[] = [];
|
|
242
|
+
const structRegex = /(?:pub\s+)?struct\s+([a-zA-Z0-9_]+)/g;
|
|
243
|
+
while ((match = structRegex.exec(content)) !== null) {
|
|
244
|
+
if (match[1]) types.push(match[1]);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const enumRegex = /(?:pub\s+)?enum\s+([a-zA-Z0-9_]+)/g;
|
|
248
|
+
while ((match = enumRegex.exec(content)) !== null) {
|
|
249
|
+
if (match[1]) types.push(match[1]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
253
|
+
const lines = content.split(/\r?\n/);
|
|
254
|
+
|
|
255
|
+
lines.forEach(line => {
|
|
256
|
+
const useMatch = line.match(/^use\s+([a-zA-Z0-9_:]+)(?:::\{([^}]+)\})?/);
|
|
257
|
+
if (useMatch?.[1]) {
|
|
258
|
+
const name = useMatch[1];
|
|
259
|
+
const type = name.startsWith('std::') || name.startsWith('core::') ? 'Standard' : 'Crate';
|
|
260
|
+
const usage = useMatch[2];
|
|
261
|
+
imports.push({ name, type, usage });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return { functions, classes: [], types, imports, exports: [] };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Analyseur HTML
|
|
270
|
+
class HTMLAnalyzer implements LanguageAnalyzer {
|
|
271
|
+
extensions = [".html", ".htm"];
|
|
272
|
+
|
|
273
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
274
|
+
const structure: Record<string, any> = {
|
|
275
|
+
tags: new Set<string>(),
|
|
276
|
+
ids: [] as string[],
|
|
277
|
+
classes: [] as string[],
|
|
278
|
+
scripts: [] as string[],
|
|
279
|
+
styles: [] as string[]
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// Extraire les balises
|
|
283
|
+
const tagRegex = /<(\w+)/g;
|
|
284
|
+
let match: RegExpExecArray | null;
|
|
285
|
+
while ((match = tagRegex.exec(content)) !== null) {
|
|
286
|
+
if (match[1]) structure.tags.add(match[1].toLowerCase());
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Extraire les IDs
|
|
290
|
+
const idRegex = /id=["']([^"']+)["']/g;
|
|
291
|
+
while ((match = idRegex.exec(content)) !== null) {
|
|
292
|
+
if (match[1]) structure.ids.push(match[1]);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Extraire les classes
|
|
296
|
+
const classRegex = /class=["']([^"']+)["']/g;
|
|
297
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
298
|
+
if (match[1]) {
|
|
299
|
+
structure.classes.push(...match[1].split(/\s+/).filter(Boolean));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Extraire les scripts externes
|
|
304
|
+
const scriptRegex = /<script[^>]+src=["']([^"']+)["']/g;
|
|
305
|
+
while ((match = scriptRegex.exec(content)) !== null) {
|
|
306
|
+
if (match[1]) structure.scripts.push(match[1]);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Extraire les stylesheets externes
|
|
310
|
+
const linkRegex = /<link[^>]+href=["']([^"']+\.css)["']/g;
|
|
311
|
+
while ((match = linkRegex.exec(content)) !== null) {
|
|
312
|
+
if (match[1]) structure.styles.push(match[1]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
structure.tags = Array.from(structure.tags);
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
functions: [],
|
|
319
|
+
classes: structure.classes,
|
|
320
|
+
types: [],
|
|
321
|
+
imports: [
|
|
322
|
+
...structure.scripts.map((s : string) => ({ name: s, type: 'Script' })),
|
|
323
|
+
...structure.styles.map((s:string) => ({ name: s, type: 'Stylesheet' }))
|
|
324
|
+
],
|
|
325
|
+
exports: [],
|
|
326
|
+
structure
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Analyseur CSS/SCSS/SASS
|
|
332
|
+
class CSSAnalyzer implements LanguageAnalyzer {
|
|
333
|
+
extensions = [".css", ".scss", ".sass", ".less"];
|
|
334
|
+
|
|
335
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
336
|
+
const structure: Record<string, any> = {
|
|
337
|
+
selectors: [] as string[],
|
|
338
|
+
ids: [] as string[],
|
|
339
|
+
classes: [] as string[],
|
|
340
|
+
variables: [] as string[],
|
|
341
|
+
mixins: [] as string[],
|
|
342
|
+
imports: [] as string[]
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// Classes CSS
|
|
346
|
+
const classRegex = /\.([a-zA-Z0-9_-]+)/g;
|
|
347
|
+
let match: RegExpExecArray | null;
|
|
348
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
349
|
+
if (match[1] && !structure.classes.includes(match[1])) {
|
|
350
|
+
structure.classes.push(match[1]);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// IDs CSS
|
|
355
|
+
const idRegex = /#([a-zA-Z0-9_-]+)/g;
|
|
356
|
+
while ((match = idRegex.exec(content)) !== null) {
|
|
357
|
+
if (match[1] && !structure.ids.includes(match[1])) {
|
|
358
|
+
structure.ids.push(match[1]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Variables CSS/SCSS
|
|
363
|
+
const varRegex = /(?:--|\$)([a-zA-Z0-9_-]+)/g;
|
|
364
|
+
while ((match = varRegex.exec(content)) !== null) {
|
|
365
|
+
if (match[1] && !structure.variables.includes(match[1])) {
|
|
366
|
+
structure.variables.push(match[1]);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Mixins SCSS
|
|
371
|
+
const mixinRegex = /@mixin\s+([a-zA-Z0-9_-]+)/g;
|
|
372
|
+
while ((match = mixinRegex.exec(content)) !== null) {
|
|
373
|
+
if (match[1]) structure.mixins.push(match[1]);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Imports
|
|
377
|
+
const importRegex = /@import\s+['"]([^'"]+)['"]/g;
|
|
378
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
379
|
+
if (match[1]) structure.imports.push(match[1]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
functions: structure.mixins,
|
|
384
|
+
classes: structure.classes,
|
|
385
|
+
types: [],
|
|
386
|
+
imports: structure.imports.map((i:string) => ({ name: i, type: 'CSS' })),
|
|
387
|
+
exports: [],
|
|
388
|
+
structure
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Analyseur PHP
|
|
394
|
+
class PHPAnalyzer implements LanguageAnalyzer {
|
|
395
|
+
extensions = [".php"];
|
|
396
|
+
|
|
397
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
398
|
+
const functions: string[] = [];
|
|
399
|
+
const funcRegex = /function\s+([a-zA-Z0-9_]+)\s*\(/g;
|
|
400
|
+
let match: RegExpExecArray | null;
|
|
401
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
402
|
+
if (match[1]) functions.push(match[1]);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const classes: string[] = [];
|
|
406
|
+
const classRegex = /class\s+([a-zA-Z0-9_]+)/g;
|
|
407
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
408
|
+
if (match[1]) classes.push(match[1]);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
412
|
+
const useRegex = /use\s+([a-zA-Z0-9_\\]+)/g;
|
|
413
|
+
while ((match = useRegex.exec(content)) !== null) {
|
|
414
|
+
if (match[1]) {
|
|
415
|
+
imports.push({ name: match[1], type: 'Composer' });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const requireRegex = /(?:require|include)(?:_once)?\s*['"]([^'"]+)['"]/g;
|
|
420
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
421
|
+
if (match[1]) {
|
|
422
|
+
imports.push({ name: match[1], type: 'Local' });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return { functions, classes, types: [], imports, exports: [] };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Analyseur Ruby
|
|
431
|
+
class RubyAnalyzer implements LanguageAnalyzer {
|
|
432
|
+
extensions = [".rb"];
|
|
433
|
+
|
|
434
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
435
|
+
const functions: string[] = [];
|
|
436
|
+
const funcRegex = /def\s+([a-zA-Z0-9_?!]+)/g;
|
|
437
|
+
let match: RegExpExecArray | null;
|
|
438
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
439
|
+
if (match[1]) functions.push(match[1]);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const classes: string[] = [];
|
|
443
|
+
const classRegex = /class\s+([a-zA-Z0-9_]+)/g;
|
|
444
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
445
|
+
if (match[1]) classes.push(match[1]);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const imports: Array<{ name: string; type: string; usage?: string | undefined }> = [];
|
|
449
|
+
const requireRegex = /require\s+['"]([^'"]+)['"]/g;
|
|
450
|
+
while ((match = requireRegex.exec(content)) !== null) {
|
|
451
|
+
if (match[1]) {
|
|
452
|
+
imports.push({ name: match[1], type: 'Gem' });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return { functions, classes, types: [], imports, exports: [] };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Analyseur SQL
|
|
461
|
+
class SQLAnalyzer implements LanguageAnalyzer {
|
|
462
|
+
extensions = [".sql"];
|
|
463
|
+
|
|
464
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
465
|
+
const structure: Record<string, any> = {
|
|
466
|
+
tables: [] as string[],
|
|
467
|
+
procedures: [] as string[],
|
|
468
|
+
functions: [] as string[],
|
|
469
|
+
views: [] as string[]
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const tableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([a-zA-Z0-9_]+)/gi;
|
|
473
|
+
let match: RegExpExecArray | null;
|
|
474
|
+
while ((match = tableRegex.exec(content)) !== null) {
|
|
475
|
+
if (match[1]) structure.tables.push(match[1]);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const procRegex = /CREATE\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+([a-zA-Z0-9_]+)/gi;
|
|
479
|
+
while ((match = procRegex.exec(content)) !== null) {
|
|
480
|
+
if (match[1]) structure.procedures.push(match[1]);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const funcRegex = /CREATE\s+(?:OR\s+REPLACE\s+)?FUNCTION\s+([a-zA-Z0-9_]+)/gi;
|
|
484
|
+
while ((match = funcRegex.exec(content)) !== null) {
|
|
485
|
+
if (match[1]) structure.functions.push(match[1]);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const viewRegex = /CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+([a-zA-Z0-9_]+)/gi;
|
|
489
|
+
while ((match = viewRegex.exec(content)) !== null) {
|
|
490
|
+
if (match[1]) structure.views.push(match[1]);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
functions: structure.functions,
|
|
495
|
+
classes: structure.tables,
|
|
496
|
+
types: structure.views,
|
|
497
|
+
imports: [],
|
|
498
|
+
exports: [],
|
|
499
|
+
structure
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Analyseur par défaut pour tous les autres fichiers
|
|
505
|
+
class DefaultAnalyzer implements LanguageAnalyzer {
|
|
506
|
+
extensions = ["*"]; // Wildcard pour tous les fichiers
|
|
507
|
+
|
|
508
|
+
analyzeFile(content: string): FileAnalysisResult {
|
|
509
|
+
const lines = content.split(/\r?\n/);
|
|
510
|
+
const structure: Record<string, any> = {
|
|
511
|
+
lineCount: lines.length,
|
|
512
|
+
nonEmptyLines: lines.filter(l => l.trim().length > 0).length,
|
|
513
|
+
size: content.length,
|
|
514
|
+
hasCode: /[a-zA-Z0-9_]+\s*[=:({]/.test(content),
|
|
515
|
+
encoding: 'UTF-8'
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
// Détection basique de patterns communs
|
|
519
|
+
const patterns = {
|
|
520
|
+
functions: [] as string[],
|
|
521
|
+
variables: [] as string[],
|
|
522
|
+
comments: [] as string[]
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
// Détection générique de fonctions (pattern commun à beaucoup de langages)
|
|
526
|
+
const genericFuncRegex = /(?:function|func|def|fn|sub|procedure)\s+([a-zA-Z0-9_]+)/gi;
|
|
527
|
+
let match: RegExpExecArray | null;
|
|
528
|
+
while ((match = genericFuncRegex.exec(content)) !== null) {
|
|
529
|
+
if (match[1]) patterns.functions.push(match[1]);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
structure.patterns = patterns;
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
functions: patterns.functions,
|
|
536
|
+
classes: [],
|
|
537
|
+
types: [],
|
|
538
|
+
imports: [],
|
|
539
|
+
exports: [],
|
|
540
|
+
structure
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Registry des analyseurs
|
|
546
|
+
const analyzers: LanguageAnalyzer[] = [
|
|
547
|
+
new TSJSAnalyzer(),
|
|
548
|
+
new PythonAnalyzer(),
|
|
549
|
+
new JavaAnalyzer(),
|
|
550
|
+
new CSharpAnalyzer(),
|
|
551
|
+
new GoAnalyzer(),
|
|
552
|
+
new RustAnalyzer(),
|
|
553
|
+
new HTMLAnalyzer(),
|
|
554
|
+
new CSSAnalyzer(),
|
|
555
|
+
new PHPAnalyzer(),
|
|
556
|
+
new RubyAnalyzer(),
|
|
557
|
+
new SQLAnalyzer()
|
|
558
|
+
];
|
|
559
|
+
|
|
560
|
+
const defaultAnalyzer = new DefaultAnalyzer();
|
|
561
|
+
|
|
562
|
+
export function getAnalyzer(filePath: string): LanguageAnalyzer {
|
|
563
|
+
const lastDotIndex = filePath.lastIndexOf('.');
|
|
564
|
+
if (lastDotIndex === -1) return defaultAnalyzer;
|
|
565
|
+
|
|
566
|
+
const ext = filePath.substring(lastDotIndex).toLowerCase();
|
|
567
|
+
return analyzers.find(a => a.extensions.includes(ext)) || defaultAnalyzer;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function getSupportedExtensions(): string[] {
|
|
571
|
+
return analyzers.flatMap(a => a.extensions);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function isExplicitlySupported(filePath: string): boolean {
|
|
575
|
+
const lastDotIndex = filePath.lastIndexOf('.');
|
|
576
|
+
if (lastDotIndex === -1) return false;
|
|
577
|
+
|
|
578
|
+
const ext = filePath.substring(lastDotIndex).toLowerCase();
|
|
579
|
+
return analyzers.some(a => a.extensions.includes(ext));
|
|
580
|
+
}
|