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.
- package/LICENSE +21 -0
- package/README.md +193 -0
- package/out/cli.d.ts +28 -0
- package/out/cli.js +254 -0
- package/out/context.d.ts +31 -0
- package/out/context.js +173 -0
- package/out/extension.d.ts +3 -0
- package/out/extension.js +269 -0
- package/out/index.d.ts +28 -0
- package/out/index.js +44 -0
- package/out/languages.d.ts +76 -0
- package/out/languages.js +169 -0
- package/out/parser.d.ts +28 -0
- package/out/parser.js +106 -0
- package/out/rules.d.ts +18 -0
- package/out/rules.js +351 -0
- package/out/scoring.d.ts +26 -0
- package/out/scoring.js +71 -0
- package/out/types.d.ts +47 -0
- package/out/types.js +3 -0
- package/out/wasm/tree-sitter-java.wasm +0 -0
- package/out/wasm/tree-sitter-tsx.wasm +0 -0
- package/out/wasm/tree-sitter-typescript.wasm +0 -0
- package/out/wasm/tree-sitter.wasm +0 -0
- package/out/webview.d.ts +5 -0
- package/out/webview.js +231 -0
- package/out/workspace.d.ts +10 -0
- package/out/workspace.js +122 -0
- package/package.json +67 -0
package/out/scoring.d.ts
ADDED
|
@@ -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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/out/webview.d.ts
ADDED
|
@@ -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, '&')
|
|
227
|
+
.replace(/</g, '<')
|
|
228
|
+
.replace(/>/g, '>')
|
|
229
|
+
.replace(/"/g, '"');
|
|
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>;
|
package/out/workspace.js
ADDED
|
@@ -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
|
+
}
|