plugin-eco 0.1.0

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.
@@ -0,0 +1,26 @@
1
+ import { Finding, Score } from './types';
2
+ /** Convertit une valeur numérique (0–100) en lettre A–E. */
3
+ export declare function letterFor(value: number): Score['letter'];
4
+ /**
5
+ * Calcule le score énergétique (0–100) et la lettre A–E à partir des findings d'un fichier.
6
+ */
7
+ export declare function computeScore(findings: Finding[]): Score;
8
+ /**
9
+ * Score global d'un ensemble de fichiers.
10
+ *
11
+ * **Les fichiers sans aucune alerte sont exclus du calcul.** Une moyenne sur
12
+ * tous les fichiers diluait tout : baby-tracker sortait en A 99/100 alors que
13
+ * son unique fichier volumineux était en C 72, noyé par 22 modules utilitaires
14
+ * sans le moindre finding. Le score annonçait surtout combien le projet compte
15
+ * de petits fichiers anodins.
16
+ *
17
+ * Restreindre la moyenne aux fichiers réellement concernés répond à la question
18
+ * utile — « à quel point ce qui pose problème pose problème » — et empêche
19
+ * d'améliorer sa note en ajoutant du code sain.
20
+ *
21
+ * Le compte d'alertes, lui, reste calculé sur l'ensemble : c'est la mesure de
22
+ * l'étendue, que le panneau affiche à côté de la lettre.
23
+ */
24
+ export declare function aggregateScore(scores: Score[]): Score;
25
+ /** Étiquette textuelle du score pour les tooltips et le rapport. */
26
+ export declare function scoreSummary(score: Score): string;
package/out/scoring.js ADDED
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.letterFor = letterFor;
4
+ exports.computeScore = computeScore;
5
+ exports.aggregateScore = aggregateScore;
6
+ exports.scoreSummary = scoreSummary;
7
+ // Seuils de lettres (ordre décroissant — premier match gagne)
8
+ const THRESHOLDS = [
9
+ { min: 90, letter: 'A' },
10
+ { min: 75, letter: 'B' },
11
+ { min: 55, letter: 'C' },
12
+ { min: 35, letter: 'D' },
13
+ { min: 0, letter: 'E' },
14
+ ];
15
+ /** Convertit une valeur numérique (0–100) en lettre A–E. */
16
+ function letterFor(value) {
17
+ return THRESHOLDS.find(t => value >= t.min).letter;
18
+ }
19
+ /**
20
+ * Calcule le score énergétique (0–100) et la lettre A–E à partir des findings d'un fichier.
21
+ */
22
+ function computeScore(findings) {
23
+ let penalty = 0;
24
+ const count = { high: 0, medium: 0, low: 0 };
25
+ for (const f of findings) {
26
+ penalty += f.weight;
27
+ count[f.severity]++;
28
+ }
29
+ const value = Math.max(0, 100 - penalty);
30
+ return { letter: letterFor(value), value, findingCount: count };
31
+ }
32
+ /**
33
+ * Score global d'un ensemble de fichiers.
34
+ *
35
+ * **Les fichiers sans aucune alerte sont exclus du calcul.** Une moyenne sur
36
+ * tous les fichiers diluait tout : baby-tracker sortait en A 99/100 alors que
37
+ * son unique fichier volumineux était en C 72, noyé par 22 modules utilitaires
38
+ * sans le moindre finding. Le score annonçait surtout combien le projet compte
39
+ * de petits fichiers anodins.
40
+ *
41
+ * Restreindre la moyenne aux fichiers réellement concernés répond à la question
42
+ * utile — « à quel point ce qui pose problème pose problème » — et empêche
43
+ * d'améliorer sa note en ajoutant du code sain.
44
+ *
45
+ * Le compte d'alertes, lui, reste calculé sur l'ensemble : c'est la mesure de
46
+ * l'étendue, que le panneau affiche à côté de la lettre.
47
+ */
48
+ function aggregateScore(scores) {
49
+ const total = scores.reduce((acc, s) => ({
50
+ high: acc.high + s.findingCount.high,
51
+ medium: acc.medium + s.findingCount.medium,
52
+ low: acc.low + s.findingCount.low,
53
+ }), { high: 0, medium: 0, low: 0 });
54
+ const concerned = scores.filter(s => s.findingCount.high + s.findingCount.medium + s.findingCount.low > 0);
55
+ const value = concerned.length === 0
56
+ ? 100
57
+ : Math.round(concerned.reduce((sum, s) => sum + s.value, 0) / concerned.length);
58
+ return { letter: letterFor(value), value, findingCount: total };
59
+ }
60
+ /** Étiquette textuelle du score pour les tooltips et le rapport. */
61
+ function scoreSummary(score) {
62
+ const labels = {
63
+ A: 'Excellent — code très sobre',
64
+ B: 'Bon — quelques optimisations possibles',
65
+ C: 'Moyen — des patterns énergivores détectés',
66
+ D: 'Faible — restructuration recommandée',
67
+ E: 'Critique — consommation élevée',
68
+ };
69
+ return labels[score.letter];
70
+ }
71
+ //# sourceMappingURL=scoring.js.map
package/out/types.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ /** Sévérité d'un pattern énergivore détecté. */
2
+ export type Severity = 'high' | 'medium' | 'low';
3
+ /**
4
+ * Où s'exécute le code analysé.
5
+ *
6
+ * L'enjeu énergétique n'est pas le même : un `setInterval` de polling coûte une
7
+ * fois sur un serveur, et autant de fois qu'il y a d'utilisateurs sur le client.
8
+ * `unknown` est un état légitime, pas un échec — en rendu côté serveur, un même
9
+ * fichier tourne réellement des deux côtés.
10
+ */
11
+ export type ExecutionContext = 'client' | 'server' | 'unknown';
12
+ /** Un pattern énergivore détecté dans le code, avec sa position et son poids de pénalité. */
13
+ export interface Finding {
14
+ startLine: number;
15
+ startChar: number;
16
+ endLine: number;
17
+ endChar: number;
18
+ message: string;
19
+ severity: Severity;
20
+ weight: number;
21
+ }
22
+ /** Score énergétique agrégé pour un fichier. */
23
+ export interface Score {
24
+ letter: 'A' | 'B' | 'C' | 'D' | 'E';
25
+ value: number;
26
+ findingCount: {
27
+ high: number;
28
+ medium: number;
29
+ low: number;
30
+ };
31
+ }
32
+ /** Résultat d'analyse d'un fichier Java unique (utilisé dans le rapport workspace). */
33
+ export interface FileResult {
34
+ uri: string;
35
+ fileName: string;
36
+ score: Score;
37
+ findings: Finding[];
38
+ }
39
+ /** Rapport global d'un scan workspace. */
40
+ export interface WorkspaceReport {
41
+ files: FileResult[];
42
+ /** Moyenne des seuls fichiers présentant au moins une alerte. */
43
+ global: Score;
44
+ /** Étendue : combien de fichiers sont concernés, sur combien d'analysés. */
45
+ filesWithFindings: number;
46
+ scannedAt: string;
47
+ }
package/out/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ import { Finding, Score, WorkspaceReport } from './types';
2
+ /** Génère le HTML du panneau mono-fichier (pas de scripts nécessaires). */
3
+ export declare function buildWebviewHtml(findings: Finding[], score: Score, fileName: string, nonce: string, partial?: boolean): string;
4
+ /** Génère le HTML du panneau workspace avec lignes cliquables (requiert enableScripts). */
5
+ export declare function buildWorkspaceHtml(report: WorkspaceReport, nonce: string): string;
package/out/webview.js ADDED
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildWebviewHtml = buildWebviewHtml;
4
+ exports.buildWorkspaceHtml = buildWorkspaceHtml;
5
+ const scoring_1 = require("./scoring");
6
+ const DPE_COLORS = {
7
+ A: { bg: '#00A550', fg: '#ffffff' },
8
+ B: { bg: '#52B747', fg: '#ffffff' },
9
+ C: { bg: '#F0E729', fg: '#1a1a1a' },
10
+ D: { bg: '#F7A329', fg: '#ffffff' },
11
+ E: { bg: '#EC1C24', fg: '#ffffff' },
12
+ };
13
+ const SEVERITY_LABELS = {
14
+ high: { label: 'Haute', color: '#EC1C24' },
15
+ medium: { label: 'Moyenne', color: '#F7A329' },
16
+ low: { label: 'Faible', color: '#52B747' },
17
+ };
18
+ // ---------------------------------------------------------------------------
19
+ // Composants réutilisables
20
+ // ---------------------------------------------------------------------------
21
+ function dpeStrip(activeLetter) {
22
+ return ['A', 'B', 'C', 'D', 'E']
23
+ .map(l => {
24
+ const c = DPE_COLORS[l];
25
+ const active = l === activeLetter;
26
+ return `<div class="dpe-box${active ? ' active' : ''}"
27
+ style="background:${c.bg};color:${c.fg}">${l}</div>`;
28
+ })
29
+ .join('');
30
+ }
31
+ function sharedStyles() {
32
+ return `
33
+ body {
34
+ font-family: var(--vscode-font-family, sans-serif);
35
+ font-size: var(--vscode-font-size, 13px);
36
+ color: var(--vscode-editor-foreground);
37
+ background: var(--vscode-editor-background);
38
+ padding: 20px 24px;
39
+ margin: 0;
40
+ }
41
+ h1 { font-size: 1.1em; margin: 0 0 4px; opacity: 0.7; font-weight: 400; }
42
+ .subtitle { font-size: 0.85em; opacity: 0.5; margin-bottom: 20px; word-break: break-all; }
43
+ .dpe-strip { display: flex; gap: 6px; align-items: flex-end; margin-bottom: 8px; }
44
+ .dpe-box {
45
+ width: 44px; height: 36px;
46
+ display: flex; align-items: center; justify-content: center;
47
+ font-weight: 700; font-size: 1em;
48
+ border-radius: 3px; opacity: 0.28;
49
+ }
50
+ .dpe-box.active {
51
+ opacity: 1; height: 52px; font-size: 1.4em;
52
+ box-shadow: 0 2px 8px rgba(0,0,0,0.35);
53
+ }
54
+ .score-line { font-size: 0.9em; margin-bottom: 6px; font-weight: 600; }
55
+ .summary { font-size: 0.85em; opacity: 0.6; margin-bottom: 16px; }
56
+ .worst {
57
+ font-size: 0.9em; margin-bottom: 24px; padding: 8px 12px;
58
+ border-left: 3px solid var(--vscode-textLink-foreground);
59
+ background: var(--vscode-textBlockQuote-background);
60
+ }
61
+ h2 {
62
+ font-size: 0.95em; margin: 0 0 10px;
63
+ border-bottom: 1px solid var(--vscode-panel-border, #444);
64
+ padding-bottom: 6px;
65
+ }
66
+ table { width: 100%; border-collapse: collapse; }
67
+ th {
68
+ text-align: left; font-size: 0.8em; opacity: 0.55; font-weight: 600;
69
+ padding: 4px 8px; border-bottom: 1px solid var(--vscode-panel-border, #444);
70
+ }
71
+ td {
72
+ padding: 6px 8px; vertical-align: top;
73
+ border-bottom: 1px solid var(--vscode-panel-border, #333);
74
+ font-size: 0.88em;
75
+ }
76
+ tr:last-child td { border-bottom: none; }
77
+ .col-line { white-space: nowrap; opacity: 0.55; font-family: monospace; }
78
+ .col-msg { line-height: 1.45; }
79
+ .col-file { font-family: monospace; font-size: 0.85em; }
80
+ .col-score { text-align: center; }
81
+ td.empty { text-align: center; padding: 20px; opacity: 0.6; }
82
+ .badge {
83
+ display: inline-block; padding: 1px 7px; border-radius: 10px;
84
+ font-size: 0.8em; font-weight: 700; color: #fff; white-space: nowrap;
85
+ }
86
+ .letter-badge {
87
+ display: inline-block; width: 24px; height: 24px; line-height: 24px;
88
+ text-align: center; border-radius: 3px; font-weight: 700; font-size: 0.9em;
89
+ }
90
+ .clickable { cursor: pointer; }
91
+ .clickable:hover td { background: var(--vscode-list-hoverBackground, rgba(255,255,255,0.06)); }`;
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Rapport fichier unique
95
+ // ---------------------------------------------------------------------------
96
+ /** Génère le HTML du panneau mono-fichier (pas de scripts nécessaires). */
97
+ function buildWebviewHtml(findings, score, fileName, nonce, partial = false) {
98
+ const color = DPE_COLORS[score.letter];
99
+ const total = findings.length;
100
+ const { high, medium } = score.findingCount;
101
+ const partialBlock = partial
102
+ ? `<div class="worst" style="border-left-color:#F7A329">
103
+ ⚠ Analyse partielle : le fichier n'a pas pu être parsé entièrement.
104
+ Le score est à prendre avec réserve.
105
+ </div>`
106
+ : '';
107
+ const sorted = [...findings].sort((a, b) => {
108
+ const order = { high: 0, medium: 1, low: 2 };
109
+ return order[a.severity] - order[b.severity];
110
+ });
111
+ const rows = sorted.length === 0
112
+ ? `<tr><td colspan="3" class="empty">Aucun pattern énergivore détecté 🎉</td></tr>`
113
+ : sorted.map(f => {
114
+ const sv = SEVERITY_LABELS[f.severity];
115
+ return `<tr>
116
+ <td class="col-line">L.${f.startLine + 1}</td>
117
+ <td><span class="badge" style="background:${sv.color}">${sv.label}</span></td>
118
+ <td class="col-msg">${esc(f.message)}</td>
119
+ </tr>`;
120
+ }).join('');
121
+ return html(`
122
+ <h1>⚡ Rapport éco — fichier</h1>
123
+ <div class="subtitle">${esc(fileName)}</div>
124
+
125
+ <div class="dpe-strip">${dpeStrip(score.letter)}</div>
126
+ <div class="score-line" style="color:${color.bg}">${score.value}/100 — ${(0, scoring_1.scoreSummary)(score)}</div>
127
+ <div class="summary">${total === 0
128
+ ? 'Aucune alerte.'
129
+ : `${total} alerte${total > 1 ? 's' : ''} : ${high} haute${high > 1 ? 's' : ''}, ${medium} moyenne${medium > 1 ? 's' : ''}`}</div>
130
+ ${partialBlock}
131
+
132
+ <h2>Détail des alertes</h2>
133
+ <table>
134
+ <thead><tr><th>Ligne</th><th>Sévérité</th><th>Message</th></tr></thead>
135
+ <tbody>${rows}</tbody>
136
+ </table>
137
+ `, nonce);
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Rapport workspace
141
+ // ---------------------------------------------------------------------------
142
+ /** Génère le HTML du panneau workspace avec lignes cliquables (requiert enableScripts). */
143
+ function buildWorkspaceHtml(report, nonce) {
144
+ const { global, files, scannedAt, filesWithFindings } = report;
145
+ const color = DPE_COLORS[global.letter];
146
+ const totalFiles = files.length;
147
+ const { high, medium } = global.findingCount;
148
+ // Le pire fichier est mis en avant : c'est lui qu'il faut regarder d'abord,
149
+ // et une lettre unique ne peut pas le désigner.
150
+ const worst = files.reduce((a, b) => (b.score.value < a.score.value ? b : a), files[0]);
151
+ const worstBlock = worst && worst.findings.length > 0
152
+ ? `<div class="worst">
153
+ À regarder en premier :
154
+ <span class="letter-badge" style="background:${DPE_COLORS[worst.score.letter].bg};color:${DPE_COLORS[worst.score.letter].fg}">${worst.score.letter}</span>
155
+ <strong>${esc(worst.fileName)}</strong>
156
+ <span style="opacity:0.6">— ${worst.findings.length} alerte${worst.findings.length > 1 ? 's' : ''}</span>
157
+ </div>`
158
+ : '';
159
+ const rows = files.map(f => {
160
+ const c = DPE_COLORS[f.score.letter];
161
+ const firstLine = f.findings.length > 0 ? f.findings[0].startLine + 1 : 1;
162
+ const totalFindings = f.findings.length;
163
+ return `<tr class="clickable"
164
+ data-uri="${esc(f.uri)}"
165
+ data-line="${firstLine}">
166
+ <td class="col-file">${esc(f.fileName)}</td>
167
+ <td class="col-score">
168
+ <span class="letter-badge" style="background:${c.bg};color:${c.fg}">${f.score.letter}</span>
169
+ <span style="opacity:0.6;font-size:0.85em;margin-left:4px">${f.score.value}</span>
170
+ </td>
171
+ <td>${totalFindings > 0
172
+ ? `${totalFindings} alerte${totalFindings > 1 ? 's' : ''}`
173
+ : '<span style="opacity:0.4">–</span>'}</td>
174
+ </tr>`;
175
+ }).join('');
176
+ const script = `
177
+ <script nonce="${nonce}">
178
+ const vscode = acquireVsCodeApi();
179
+ document.querySelectorAll('.clickable').forEach(row => {
180
+ row.addEventListener('click', () => {
181
+ vscode.postMessage({
182
+ command: 'open',
183
+ file: row.dataset.uri,
184
+ line: parseInt(row.dataset.line, 10)
185
+ });
186
+ });
187
+ });
188
+ </script>`;
189
+ return html(`
190
+ <h1>⚡ Rapport éco — workspace</h1>
191
+ <div class="subtitle">Analysé le ${esc(scannedAt)} · ${totalFiles} fichier${totalFiles > 1 ? 's' : ''}</div>
192
+
193
+ <div class="dpe-strip">${dpeStrip(global.letter)}</div>
194
+ <div class="score-line" style="color:${color.bg}">${global.value}/100 — ${(0, scoring_1.scoreSummary)(global)}</div>
195
+ <div class="summary">
196
+ ${high + medium} alerte${(high + medium) > 1 ? 's' : ''} au total : ${high} haute${high > 1 ? 's' : ''}, ${medium} moyenne${medium > 1 ? 's' : ''}<br>
197
+ <span style="opacity:0.75">Moyenne des ${filesWithFindings} fichier${filesWithFindings > 1 ? 's' : ''} concerné${filesWithFindings > 1 ? 's' : ''} sur ${totalFiles} analysé${totalFiles > 1 ? 's' : ''} — les fichiers sans alerte ne comptent pas dans la note.</span>
198
+ </div>
199
+ ${worstBlock}
200
+
201
+ <h2>Fichiers — pires en premier</h2>
202
+ <table>
203
+ <thead><tr><th>Fichier</th><th>Score</th><th>Alertes</th></tr></thead>
204
+ <tbody>${rows}</tbody>
205
+ </table>
206
+ ${script}
207
+ `, nonce, `script-src 'nonce-${nonce}';`);
208
+ }
209
+ // ---------------------------------------------------------------------------
210
+ // Helpers
211
+ // ---------------------------------------------------------------------------
212
+ function html(body, nonce, extraCsp = '') {
213
+ return `<!DOCTYPE html>
214
+ <html lang="fr">
215
+ <head>
216
+ <meta charset="UTF-8">
217
+ <meta http-equiv="Content-Security-Policy"
218
+ content="default-src 'none'; style-src 'unsafe-inline'; ${extraCsp}">
219
+ <style>${sharedStyles()}</style>
220
+ </head>
221
+ <body>${body}</body>
222
+ </html>`;
223
+ }
224
+ function esc(str) {
225
+ return str
226
+ .replace(/&/g, '&amp;')
227
+ .replace(/</g, '&lt;')
228
+ .replace(/>/g, '&gt;')
229
+ .replace(/"/g, '&quot;');
230
+ }
231
+ //# sourceMappingURL=webview.js.map
@@ -0,0 +1,10 @@
1
+ import * as vscode from 'vscode';
2
+ import { WorkspaceReport } from './types';
3
+ /**
4
+ * Scanne tous les fichiers analysables du workspace, calcule un score par
5
+ * fichier, et retourne un rapport global (score moyen + liste triée par score
6
+ * croissant).
7
+ *
8
+ * Les diagnostics de chaque fichier sont posés dans la collection partagée.
9
+ */
10
+ export declare function analyzeWorkspace(diagnosticCollection: vscode.DiagnosticCollection): Promise<WorkspaceReport | null>;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyzeWorkspace = analyzeWorkspace;
37
+ const vscode = __importStar(require("vscode"));
38
+ const path = __importStar(require("path"));
39
+ const parser_1 = require("./parser");
40
+ const rules_1 = require("./rules");
41
+ const scoring_1 = require("./scoring");
42
+ const languages_1 = require("./languages");
43
+ const context_1 = require("./context");
44
+ /**
45
+ * Scanne tous les fichiers analysables du workspace, calcule un score par
46
+ * fichier, et retourne un rapport global (score moyen + liste triée par score
47
+ * croissant).
48
+ *
49
+ * Les diagnostics de chaque fichier sont posés dans la collection partagée.
50
+ */
51
+ async function analyzeWorkspace(diagnosticCollection) {
52
+ // Un passage par langage : chaque fichier garde le descripteur qui le décrit,
53
+ // faute de quoi on ne saurait plus quelle grammaire lui appliquer.
54
+ const targets = [];
55
+ for (const spec of languages_1.LANGUAGES) {
56
+ const found = await vscode.workspace.findFiles((0, languages_1.globFor)(spec), (0, languages_1.excludeGlob)());
57
+ for (const uri of found)
58
+ targets.push({ uri, spec });
59
+ }
60
+ if (targets.length === 0) {
61
+ vscode.window.showInformationMessage('Plugin Eco : aucun fichier analysable trouvé dans le workspace ' +
62
+ `(${languages_1.LANGUAGES.map(l => l.label).join(', ')}).`);
63
+ return null;
64
+ }
65
+ const results = [];
66
+ await vscode.window.withProgress({
67
+ location: vscode.ProgressLocation.Notification,
68
+ title: '⚡ Plugin Eco — Analyse du workspace',
69
+ cancellable: true,
70
+ }, async (progress, token) => {
71
+ const total = targets.length;
72
+ for (let i = 0; i < total; i++) {
73
+ if (token.isCancellationRequested)
74
+ break;
75
+ const { uri, spec } = targets[i];
76
+ const baseName = path.basename(uri.fsPath);
77
+ progress.report({
78
+ message: `${baseName} (${i + 1}/${total})`,
79
+ increment: (1 / total) * 100,
80
+ });
81
+ try {
82
+ const bytes = await vscode.workspace.fs.readFile(uri);
83
+ const code = Buffer.from(bytes).toString('utf-8');
84
+ const tree = (0, parser_1.parseWith)(code, spec);
85
+ const { context } = (0, context_1.inferContext)(tree.rootNode, spec);
86
+ const findings = (0, rules_1.collectFindings)(tree.rootNode, spec, context);
87
+ const score = (0, scoring_1.computeScore)(findings);
88
+ // Poser les diagnostics inline pour ce fichier
89
+ diagnosticCollection.set(uri, findings.map(f => {
90
+ const range = new vscode.Range(f.startLine, f.startChar, f.endLine, f.endChar);
91
+ const severity = f.severity === 'high'
92
+ ? vscode.DiagnosticSeverity.Warning
93
+ : vscode.DiagnosticSeverity.Information;
94
+ const diag = new vscode.Diagnostic(range, `⚡ ${f.message}`, severity);
95
+ diag.source = 'Plugin Eco';
96
+ return diag;
97
+ }));
98
+ // Nom d'affichage : chemin relatif au workspace si possible
99
+ const wsFolder = vscode.workspace.getWorkspaceFolder(uri);
100
+ const displayName = wsFolder
101
+ ? path.relative(wsFolder.uri.fsPath, uri.fsPath).replace(/\\/g, '/')
102
+ : baseName;
103
+ results.push({ uri: uri.toString(), fileName: displayName, score, findings });
104
+ }
105
+ catch {
106
+ // Fichier illisible ou non parsable → ignoré silencieusement
107
+ }
108
+ }
109
+ });
110
+ if (results.length === 0)
111
+ return null;
112
+ const global = (0, scoring_1.aggregateScore)(results.map(r => r.score));
113
+ // Trier : pires fichiers en premier
114
+ results.sort((a, b) => a.score.value - b.score.value);
115
+ return {
116
+ files: results,
117
+ global,
118
+ filesWithFindings: results.filter(r => r.findings.length > 0).length,
119
+ scannedAt: new Date().toLocaleString('fr-CA'),
120
+ };
121
+ }
122
+ //# sourceMappingURL=workspace.js.map
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "plugin-eco",
3
+ "displayName": "Plugin Eco — Green Coding",
4
+ "description": "Analyse statique de la consommation énergétique du code Java, JavaScript et TypeScript. Score A–E inspiré du DPE.",
5
+ "version": "0.1.0",
6
+ "publisher": "greencoding",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/Tekateyy/plugin-eco.git"
11
+ },
12
+ "engines": {
13
+ "vscode": "^1.80.0"
14
+ },
15
+ "categories": ["Linters", "Other"],
16
+ "keywords": ["green coding", "java", "javascript", "typescript", "energy", "performance", "éco"],
17
+ "activationEvents": [
18
+ "onLanguage:java",
19
+ "onLanguage:javascript",
20
+ "onLanguage:javascriptreact",
21
+ "onLanguage:typescript",
22
+ "onLanguage:typescriptreact"
23
+ ],
24
+ "main": "./out/extension",
25
+ "exports": {
26
+ ".": "./out/index.js",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "types": "./out/index.d.ts",
30
+ "bin": {
31
+ "plugin-eco": "./out/cli.js"
32
+ },
33
+ "contributes": {
34
+ "commands": [
35
+ {
36
+ "command": "greencoding.analyze",
37
+ "title": "Greencoding: Analyser le fichier"
38
+ },
39
+ {
40
+ "command": "greencoding.showPanel",
41
+ "title": "Greencoding: Ouvrir le rapport détaillé"
42
+ },
43
+ {
44
+ "command": "greencoding.analyzeWorkspace",
45
+ "title": "Greencoding: Analyser tout le workspace"
46
+ }
47
+ ]
48
+ },
49
+ "scripts": {
50
+ "copy-wasm": "node scripts/copy-wasm.js",
51
+ "compile": "tsc -p ./ && npm run copy-wasm",
52
+ "watch": "npm run copy-wasm && tsc -watch -p ./",
53
+ "test": "npm run compile && node --test",
54
+ "package": "npx @vscode/vsce package",
55
+ "prepublishOnly": "npm run compile && npm test",
56
+ "vscode:prepublish": "npm run compile"
57
+ },
58
+ "dependencies": {
59
+ "web-tree-sitter": "^0.22.6"
60
+ },
61
+ "devDependencies": {
62
+ "typescript": "^5.4.5",
63
+ "@types/vscode": "^1.80.0",
64
+ "@types/node": "^20.0.0",
65
+ "tree-sitter-wasms": "^0.1.12"
66
+ }
67
+ }