plugin-eco 0.1.0 → 0.1.1
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 -21
- package/README.md +193 -193
- package/out/cli.js +20 -20
- 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.js +134 -134
- package/package.json +67 -67
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Vincent Guiberteau
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vincent Guiberteau
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,193 +1,193 @@
|
|
|
1
|
-
# plugin-eco
|
|
2
|
-
|
|
3
|
-
Extension VSCode qui note la sobriété énergétique du code **Java, JavaScript et
|
|
4
|
-
TypeScript**, avec une étiquette **A → E** empruntée au DPE des logements.
|
|
5
|
-
|
|
6
|
-
L'analyse est statique et locale : le fichier est parsé à la frappe, les
|
|
7
|
-
patterns énergivores connus sont signalés en annotations inline, et le score du
|
|
8
|
-
fichier s'affiche dans la barre de statut.
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
┌─ éditeur ────────────────────────┐ ┌─ panneau ───────────────┐
|
|
12
|
-
│ for (int i..) { │ │ │
|
|
13
|
-
│ for (int j..) { <- imbriquée │ │ ┌───┐ │
|
|
14
|
-
│ s += row[j]; <- String += │ │ │ E │ 28/100 │
|
|
15
|
-
│ } │ │ └───┘ │
|
|
16
|
-
│ } │ │ 4 hautes, 3 moyennes │
|
|
17
|
-
│ │ │ détail par ligne... │
|
|
18
|
-
└──────────────────────────────────┘ └─────────────────────────┘
|
|
19
|
-
barre de statut : Éco: E
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## Utilisation
|
|
23
|
-
|
|
24
|
-
Ouvrir un fichier `.java` : l'analyse démarre seule. Trois commandes dans la
|
|
25
|
-
palette (`Ctrl+Shift+P`) :
|
|
26
|
-
|
|
27
|
-
| Commande | Effet |
|
|
28
|
-
|---|---|
|
|
29
|
-
| `Greencoding: Analyser le fichier` | relance l'analyse du fichier actif |
|
|
30
|
-
| `Greencoding: Ouvrir le rapport détaillé` | ouvre le panneau, score + détail par ligne |
|
|
31
|
-
| `Greencoding: Analyser tout le workspace` | scanne tous les `.java`, classe les fichiers du pire au meilleur |
|
|
32
|
-
|
|
33
|
-
## Ce qui est détecté
|
|
34
|
-
|
|
35
|
-
**Partout**, quel que soit le langage et l'endroit où le code s'exécute :
|
|
36
|
-
|
|
37
|
-
| Pattern | Sévérité | Pénalité |
|
|
38
|
-
|---|---|---|
|
|
39
|
-
| Boucle imbriquée (complexité ≥ O(n²)) | haute | 15 |
|
|
40
|
-
| Requête SQL sans `LIMIT` | haute | 12 |
|
|
41
|
-
| `await` dans une boucle (appels enchaînés) | haute | 12 |
|
|
42
|
-
| Regex recompilée en boucle | haute | 12 |
|
|
43
|
-
|
|
44
|
-
**Côté serveur** — le coût est payé une fois, par le processus :
|
|
45
|
-
|
|
46
|
-
| Pattern | Sévérité | Pénalité |
|
|
47
|
-
|---|---|---|
|
|
48
|
-
| I/O synchrone dans une fonction (`readFileSync`, `execSync`…) | haute | 12 |
|
|
49
|
-
| I/O bloquant en boucle *(Java)* | haute | 12 |
|
|
50
|
-
|
|
51
|
-
**Côté navigateur** — le coût est payé par l'appareil de chaque visiteur :
|
|
52
|
-
|
|
53
|
-
| Pattern | Sévérité | Pénalité |
|
|
54
|
-
|---|---|---|
|
|
55
|
-
| `setInterval` de moins d'une seconde | haute | 12 |
|
|
56
|
-
| `setInterval` plus espacé | moyenne | 7 |
|
|
57
|
-
| Gestionnaire `scroll`/`resize`/`mousemove` sans limitation de débit | moyenne | 7 |
|
|
58
|
-
| Import global d'une bibliothèque lourde (`lodash`, `moment`…) | moyenne | 7 |
|
|
59
|
-
|
|
60
|
-
**Java seulement** : concaténation `+=` en boucle et `new` en boucle (moyenne, 7
|
|
61
|
-
chacune). Ces deux règles ne s'appliquent **pas** à JavaScript, et c'est
|
|
62
|
-
délibéré : V8 représente les concaténations par des *ropes*, et son
|
|
63
|
-
ramasse-miettes générationnel rend l'allocation à courte durée de vie bon
|
|
64
|
-
marché. Les signaler reviendrait à crier au loup sur du code sain.
|
|
65
|
-
|
|
66
|
-
Une règle restreinte à un côté ne se déclenche **jamais** sur un fichier dont le
|
|
67
|
-
contexte est indéterminé : sans certitude, le plugin se tait.
|
|
68
|
-
|
|
69
|
-
Le score part de 100, chaque détection retranche sa pénalité, et le reste donne
|
|
70
|
-
la lettre : **A** ≥ 90, **B** ≥ 75, **C** ≥ 55, **D** ≥ 35, **E** en dessous.
|
|
71
|
-
|
|
72
|
-
Sur un scan de projet, la note globale est la moyenne des **seuls fichiers qui
|
|
73
|
-
présentent au moins une alerte**. Les fichiers sains n'y entrent pas : sans
|
|
74
|
-
cela, une poignée de modules utilitaires vides suffisait à ramener un projet à
|
|
75
|
-
**A** en noyant le fichier qui pose réellement problème. Le nombre de fichiers
|
|
76
|
-
concernés est affiché à côté de la lettre, et le pire d'entre eux est mis en
|
|
77
|
-
avant — une lettre unique ne peut pas désigner un endroit.
|
|
78
|
-
|
|
79
|
-
## Développement
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
npm install
|
|
83
|
-
npm test
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Puis `F5` dans VSCode pour lancer une fenêtre de test, et ouvrir un des exemples
|
|
87
|
-
de `samples/` : `Example.java` déclenche les six règles Java, `example.ts` montre
|
|
88
|
-
ce qui s'applique hors contexte connu, `example-web.tsx` déclenche les règles
|
|
89
|
-
navigateur.
|
|
90
|
-
|
|
91
|
-
Les tests utilisent `node:test`, sans dépendance supplémentaire, et s'exécutent
|
|
92
|
-
sur le code compilé — donc sur ce qui part réellement dans l'extension. Ils
|
|
93
|
-
couvrent chaque règle (déclenchement et non-déclenchement), les seuils de
|
|
94
|
-
l'étiquette, et le résultat attendu sur `samples/Example.java`.
|
|
95
|
-
|
|
96
|
-
Pour produire l'extension installable :
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
npm run package
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## En intégration continue
|
|
103
|
-
|
|
104
|
-
Le même moteur s'utilise en ligne de commande, pour bloquer une pipeline sous un
|
|
105
|
-
seuil :
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
npx plugin-eco --min C src/
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
| | |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `--format <text\|json>` | sortie lisible ou exploitable par un script |
|
|
114
|
-
| `--min <A..E>` | note minimale acceptée |
|
|
115
|
-
| code de sortie | `0` conforme · `1` sous le seuil · `2` erreur d'utilisation |
|
|
116
|
-
|
|
117
|
-
Les positions sont rendues au format `fichier:ligne:colonne`, reconnu par la
|
|
118
|
-
plupart des annotateurs de CI et cliquable dans un terminal.
|
|
119
|
-
|
|
120
|
-
```yaml
|
|
121
|
-
- name: Green check
|
|
122
|
-
run: npx plugin-eco --min C src/
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
L'analyse est strictement la même que dans l'éditeur : le CLI et l'extension
|
|
126
|
-
partagent le parseur, les règles, l'inférence de contexte et le calcul de score.
|
|
127
|
-
Un verdict qui différerait entre l'IDE et la pipeline ruinerait la confiance dans
|
|
128
|
-
les deux.
|
|
129
|
-
|
|
130
|
-
Le moteur s'utilise aussi comme bibliothèque, pour bâtir un rapport sur mesure :
|
|
131
|
-
|
|
132
|
-
```js
|
|
133
|
-
const { initParser, parse, collectFindings, computeScore, specFor } = require('plugin-eco');
|
|
134
|
-
|
|
135
|
-
await initParser();
|
|
136
|
-
const findings = collectFindings(parse(source, 'java').rootNode, specFor('java'));
|
|
137
|
-
console.log(computeScore(findings).letter); // 'A' … 'E'
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Ce point d'entrée n'expose que le moteur : le code d'intégration VSCode en est
|
|
141
|
-
absent, et rien n'y importe `vscode`.
|
|
142
|
-
|
|
143
|
-
## Choix techniques
|
|
144
|
-
|
|
145
|
-
**Analyse statique d'abord, mesure à l'exécution ensuite.** Un profileur donne
|
|
146
|
-
des watts réels mais impose de compiler et d'exécuter le code ; l'analyse
|
|
147
|
-
statique tient sous la seconde et tourne pendant la frappe. C'est la condition
|
|
148
|
-
pour que l'information arrive au moment où le développeur peut encore agir.
|
|
149
|
-
|
|
150
|
-
**Une lettre plutôt que des watt-heures.** Une estimation en Wh sur du code non
|
|
151
|
-
exécuté serait une fausse précision. La lettre assume ce qu'elle est — un
|
|
152
|
-
classement relatif — et parle immédiatement. Les Wh et le CO₂ viendront avec la
|
|
153
|
-
mesure à l'exécution, où ils seront mesurés plutôt que devinés.
|
|
154
|
-
|
|
155
|
-
**tree-sitter plutôt qu'une analyse par expressions régulières.** Distinguer une
|
|
156
|
-
boucle imbriquée d'une boucle voisine, ou un `new` dans une boucle d'un `new`
|
|
157
|
-
juste après, demande un arbre syntaxique. tree-sitter le fournit pour de
|
|
158
|
-
nombreux langages avec un seul parseur — Python reste à ajouter.
|
|
159
|
-
|
|
160
|
-
**Un descripteur de langage, pas des conditions dispersées.** Tout ce qui varie
|
|
161
|
-
d'un langage à l'autre — grammaire, noms de nœuds tree-sitter, règles
|
|
162
|
-
applicables — est déclaré dans `src/languages.ts`. Les autres modules n'y font
|
|
163
|
-
aucune référence. Ajouter Python revient à ajouter une entrée.
|
|
164
|
-
|
|
165
|
-
**Le contexte d'exécution se déduit du code, pas des chemins.** Un `setInterval`
|
|
166
|
-
de *polling* coûte une fois sur un serveur et autant de fois qu'il y a de
|
|
167
|
-
visiteurs dans un navigateur : le plugin doit savoir où tourne le fichier. Il le
|
|
168
|
-
lit dans l'arbre déjà parsé — imports de modules Node d'un côté, globales du
|
|
169
|
-
navigateur et JSX de l'autre — plutôt que d'imposer une convention de dossiers,
|
|
170
|
-
qui diffère à chaque framework. Seuls les indices francs comptent, et des
|
|
171
|
-
indices contradictoires donnent « indéterminé » : en rendu côté serveur, un
|
|
172
|
-
fichier tourne réellement des deux côtés.
|
|
173
|
-
|
|
174
|
-
**Deux grammaires pour JS/TS, pas trois.** `tsx` est un sur-ensemble de
|
|
175
|
-
`javascript` et couvre `.js`, `.jsx` et `.tsx`. Mais elle ne peut pas remplacer
|
|
176
|
-
`typescript` pour les `.ts` : elle lit l'assertion `<Type>valeur` comme une
|
|
177
|
-
ouverture JSX et perd la suite du fichier. Mesuré plutôt que supposé — un `.ts`
|
|
178
|
-
contenant une telle assertion voyait ses trois boucles disparaître.
|
|
179
|
-
|
|
180
|
-
**Les grammaires WASM sont copiées dans `out/` au build.** Une extension
|
|
181
|
-
installée n'a pas les `node_modules` de développement sous la main : le script
|
|
182
|
-
`scripts/copy-wasm.js` place le runtime tree-sitter et la grammaire Java dans
|
|
183
|
-
`out/wasm/`, que l'extension résout depuis sa propre racine.
|
|
184
|
-
|
|
185
|
-
## État
|
|
186
|
-
|
|
187
|
-
Analyse statique de Java, JavaScript et TypeScript, avec un jeu de règles web
|
|
188
|
-
qui distingue le code serveur du code navigateur. Python, la mesure à
|
|
189
|
-
l'exécution (Wh et CO₂) et un portage IntelliJ sont les étapes suivantes.
|
|
190
|
-
|
|
191
|
-
## Licence
|
|
192
|
-
|
|
193
|
-
MIT — voir [LICENSE](LICENSE).
|
|
1
|
+
# plugin-eco
|
|
2
|
+
|
|
3
|
+
Extension VSCode qui note la sobriété énergétique du code **Java, JavaScript et
|
|
4
|
+
TypeScript**, avec une étiquette **A → E** empruntée au DPE des logements.
|
|
5
|
+
|
|
6
|
+
L'analyse est statique et locale : le fichier est parsé à la frappe, les
|
|
7
|
+
patterns énergivores connus sont signalés en annotations inline, et le score du
|
|
8
|
+
fichier s'affiche dans la barre de statut.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
┌─ éditeur ────────────────────────┐ ┌─ panneau ───────────────┐
|
|
12
|
+
│ for (int i..) { │ │ │
|
|
13
|
+
│ for (int j..) { <- imbriquée │ │ ┌───┐ │
|
|
14
|
+
│ s += row[j]; <- String += │ │ │ E │ 28/100 │
|
|
15
|
+
│ } │ │ └───┘ │
|
|
16
|
+
│ } │ │ 4 hautes, 3 moyennes │
|
|
17
|
+
│ │ │ détail par ligne... │
|
|
18
|
+
└──────────────────────────────────┘ └─────────────────────────┘
|
|
19
|
+
barre de statut : Éco: E
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Utilisation
|
|
23
|
+
|
|
24
|
+
Ouvrir un fichier `.java` : l'analyse démarre seule. Trois commandes dans la
|
|
25
|
+
palette (`Ctrl+Shift+P`) :
|
|
26
|
+
|
|
27
|
+
| Commande | Effet |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `Greencoding: Analyser le fichier` | relance l'analyse du fichier actif |
|
|
30
|
+
| `Greencoding: Ouvrir le rapport détaillé` | ouvre le panneau, score + détail par ligne |
|
|
31
|
+
| `Greencoding: Analyser tout le workspace` | scanne tous les `.java`, classe les fichiers du pire au meilleur |
|
|
32
|
+
|
|
33
|
+
## Ce qui est détecté
|
|
34
|
+
|
|
35
|
+
**Partout**, quel que soit le langage et l'endroit où le code s'exécute :
|
|
36
|
+
|
|
37
|
+
| Pattern | Sévérité | Pénalité |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| Boucle imbriquée (complexité ≥ O(n²)) | haute | 15 |
|
|
40
|
+
| Requête SQL sans `LIMIT` | haute | 12 |
|
|
41
|
+
| `await` dans une boucle (appels enchaînés) | haute | 12 |
|
|
42
|
+
| Regex recompilée en boucle | haute | 12 |
|
|
43
|
+
|
|
44
|
+
**Côté serveur** — le coût est payé une fois, par le processus :
|
|
45
|
+
|
|
46
|
+
| Pattern | Sévérité | Pénalité |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| I/O synchrone dans une fonction (`readFileSync`, `execSync`…) | haute | 12 |
|
|
49
|
+
| I/O bloquant en boucle *(Java)* | haute | 12 |
|
|
50
|
+
|
|
51
|
+
**Côté navigateur** — le coût est payé par l'appareil de chaque visiteur :
|
|
52
|
+
|
|
53
|
+
| Pattern | Sévérité | Pénalité |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `setInterval` de moins d'une seconde | haute | 12 |
|
|
56
|
+
| `setInterval` plus espacé | moyenne | 7 |
|
|
57
|
+
| Gestionnaire `scroll`/`resize`/`mousemove` sans limitation de débit | moyenne | 7 |
|
|
58
|
+
| Import global d'une bibliothèque lourde (`lodash`, `moment`…) | moyenne | 7 |
|
|
59
|
+
|
|
60
|
+
**Java seulement** : concaténation `+=` en boucle et `new` en boucle (moyenne, 7
|
|
61
|
+
chacune). Ces deux règles ne s'appliquent **pas** à JavaScript, et c'est
|
|
62
|
+
délibéré : V8 représente les concaténations par des *ropes*, et son
|
|
63
|
+
ramasse-miettes générationnel rend l'allocation à courte durée de vie bon
|
|
64
|
+
marché. Les signaler reviendrait à crier au loup sur du code sain.
|
|
65
|
+
|
|
66
|
+
Une règle restreinte à un côté ne se déclenche **jamais** sur un fichier dont le
|
|
67
|
+
contexte est indéterminé : sans certitude, le plugin se tait.
|
|
68
|
+
|
|
69
|
+
Le score part de 100, chaque détection retranche sa pénalité, et le reste donne
|
|
70
|
+
la lettre : **A** ≥ 90, **B** ≥ 75, **C** ≥ 55, **D** ≥ 35, **E** en dessous.
|
|
71
|
+
|
|
72
|
+
Sur un scan de projet, la note globale est la moyenne des **seuls fichiers qui
|
|
73
|
+
présentent au moins une alerte**. Les fichiers sains n'y entrent pas : sans
|
|
74
|
+
cela, une poignée de modules utilitaires vides suffisait à ramener un projet à
|
|
75
|
+
**A** en noyant le fichier qui pose réellement problème. Le nombre de fichiers
|
|
76
|
+
concernés est affiché à côté de la lettre, et le pire d'entre eux est mis en
|
|
77
|
+
avant — une lettre unique ne peut pas désigner un endroit.
|
|
78
|
+
|
|
79
|
+
## Développement
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm install
|
|
83
|
+
npm test
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Puis `F5` dans VSCode pour lancer une fenêtre de test, et ouvrir un des exemples
|
|
87
|
+
de `samples/` : `Example.java` déclenche les six règles Java, `example.ts` montre
|
|
88
|
+
ce qui s'applique hors contexte connu, `example-web.tsx` déclenche les règles
|
|
89
|
+
navigateur.
|
|
90
|
+
|
|
91
|
+
Les tests utilisent `node:test`, sans dépendance supplémentaire, et s'exécutent
|
|
92
|
+
sur le code compilé — donc sur ce qui part réellement dans l'extension. Ils
|
|
93
|
+
couvrent chaque règle (déclenchement et non-déclenchement), les seuils de
|
|
94
|
+
l'étiquette, et le résultat attendu sur `samples/Example.java`.
|
|
95
|
+
|
|
96
|
+
Pour produire l'extension installable :
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run package
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## En intégration continue
|
|
103
|
+
|
|
104
|
+
Le même moteur s'utilise en ligne de commande, pour bloquer une pipeline sous un
|
|
105
|
+
seuil :
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx plugin-eco --min C src/
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| | |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `--format <text\|json>` | sortie lisible ou exploitable par un script |
|
|
114
|
+
| `--min <A..E>` | note minimale acceptée |
|
|
115
|
+
| code de sortie | `0` conforme · `1` sous le seuil · `2` erreur d'utilisation |
|
|
116
|
+
|
|
117
|
+
Les positions sont rendues au format `fichier:ligne:colonne`, reconnu par la
|
|
118
|
+
plupart des annotateurs de CI et cliquable dans un terminal.
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
- name: Green check
|
|
122
|
+
run: npx plugin-eco --min C src/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
L'analyse est strictement la même que dans l'éditeur : le CLI et l'extension
|
|
126
|
+
partagent le parseur, les règles, l'inférence de contexte et le calcul de score.
|
|
127
|
+
Un verdict qui différerait entre l'IDE et la pipeline ruinerait la confiance dans
|
|
128
|
+
les deux.
|
|
129
|
+
|
|
130
|
+
Le moteur s'utilise aussi comme bibliothèque, pour bâtir un rapport sur mesure :
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
const { initParser, parse, collectFindings, computeScore, specFor } = require('plugin-eco');
|
|
134
|
+
|
|
135
|
+
await initParser();
|
|
136
|
+
const findings = collectFindings(parse(source, 'java').rootNode, specFor('java'));
|
|
137
|
+
console.log(computeScore(findings).letter); // 'A' … 'E'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Ce point d'entrée n'expose que le moteur : le code d'intégration VSCode en est
|
|
141
|
+
absent, et rien n'y importe `vscode`.
|
|
142
|
+
|
|
143
|
+
## Choix techniques
|
|
144
|
+
|
|
145
|
+
**Analyse statique d'abord, mesure à l'exécution ensuite.** Un profileur donne
|
|
146
|
+
des watts réels mais impose de compiler et d'exécuter le code ; l'analyse
|
|
147
|
+
statique tient sous la seconde et tourne pendant la frappe. C'est la condition
|
|
148
|
+
pour que l'information arrive au moment où le développeur peut encore agir.
|
|
149
|
+
|
|
150
|
+
**Une lettre plutôt que des watt-heures.** Une estimation en Wh sur du code non
|
|
151
|
+
exécuté serait une fausse précision. La lettre assume ce qu'elle est — un
|
|
152
|
+
classement relatif — et parle immédiatement. Les Wh et le CO₂ viendront avec la
|
|
153
|
+
mesure à l'exécution, où ils seront mesurés plutôt que devinés.
|
|
154
|
+
|
|
155
|
+
**tree-sitter plutôt qu'une analyse par expressions régulières.** Distinguer une
|
|
156
|
+
boucle imbriquée d'une boucle voisine, ou un `new` dans une boucle d'un `new`
|
|
157
|
+
juste après, demande un arbre syntaxique. tree-sitter le fournit pour de
|
|
158
|
+
nombreux langages avec un seul parseur — Python reste à ajouter.
|
|
159
|
+
|
|
160
|
+
**Un descripteur de langage, pas des conditions dispersées.** Tout ce qui varie
|
|
161
|
+
d'un langage à l'autre — grammaire, noms de nœuds tree-sitter, règles
|
|
162
|
+
applicables — est déclaré dans `src/languages.ts`. Les autres modules n'y font
|
|
163
|
+
aucune référence. Ajouter Python revient à ajouter une entrée.
|
|
164
|
+
|
|
165
|
+
**Le contexte d'exécution se déduit du code, pas des chemins.** Un `setInterval`
|
|
166
|
+
de *polling* coûte une fois sur un serveur et autant de fois qu'il y a de
|
|
167
|
+
visiteurs dans un navigateur : le plugin doit savoir où tourne le fichier. Il le
|
|
168
|
+
lit dans l'arbre déjà parsé — imports de modules Node d'un côté, globales du
|
|
169
|
+
navigateur et JSX de l'autre — plutôt que d'imposer une convention de dossiers,
|
|
170
|
+
qui diffère à chaque framework. Seuls les indices francs comptent, et des
|
|
171
|
+
indices contradictoires donnent « indéterminé » : en rendu côté serveur, un
|
|
172
|
+
fichier tourne réellement des deux côtés.
|
|
173
|
+
|
|
174
|
+
**Deux grammaires pour JS/TS, pas trois.** `tsx` est un sur-ensemble de
|
|
175
|
+
`javascript` et couvre `.js`, `.jsx` et `.tsx`. Mais elle ne peut pas remplacer
|
|
176
|
+
`typescript` pour les `.ts` : elle lit l'assertion `<Type>valeur` comme une
|
|
177
|
+
ouverture JSX et perd la suite du fichier. Mesuré plutôt que supposé — un `.ts`
|
|
178
|
+
contenant une telle assertion voyait ses trois boucles disparaître.
|
|
179
|
+
|
|
180
|
+
**Les grammaires WASM sont copiées dans `out/` au build.** Une extension
|
|
181
|
+
installée n'a pas les `node_modules` de développement sous la main : le script
|
|
182
|
+
`scripts/copy-wasm.js` place le runtime tree-sitter et la grammaire Java dans
|
|
183
|
+
`out/wasm/`, que l'extension résout depuis sa propre racine.
|
|
184
|
+
|
|
185
|
+
## État
|
|
186
|
+
|
|
187
|
+
Analyse statique de Java, JavaScript et TypeScript, avec un jeu de règles web
|
|
188
|
+
qui distingue le code serveur du code navigateur. Python, la mesure à
|
|
189
|
+
l'exécution (Wh et CO₂) et un portage IntelliJ sont les étapes suivantes.
|
|
190
|
+
|
|
191
|
+
## Licence
|
|
192
|
+
|
|
193
|
+
MIT — voir [LICENSE](LICENSE).
|
package/out/cli.js
CHANGED
|
@@ -59,26 +59,26 @@ const scoring_1 = require("./scoring");
|
|
|
59
59
|
const context_1 = require("./context");
|
|
60
60
|
const languages_1 = require("./languages");
|
|
61
61
|
const LETTERS = ['A', 'B', 'C', 'D', 'E'];
|
|
62
|
-
exports.USAGE = `Usage : plugin-eco [options] [chemins...]
|
|
63
|
-
|
|
64
|
-
Analyse la consommation énergétique estimée du code Java, JavaScript et
|
|
65
|
-
TypeScript, et rend une étiquette A–E inspirée du DPE.
|
|
66
|
-
|
|
67
|
-
Options
|
|
68
|
-
--format <text|json> Format de sortie (défaut : text)
|
|
69
|
-
--min <A|B|C|D|E> Note minimale acceptée ; en dessous, sortie en échec
|
|
70
|
-
-h, --help Affiche cette aide
|
|
71
|
-
|
|
72
|
-
Chemins
|
|
73
|
-
Fichiers ou dossiers à analyser. Défaut : le dossier courant.
|
|
74
|
-
Ignorés : ${languages_1.EXCLUDED_DIRS.join(', ')}
|
|
75
|
-
|
|
76
|
-
Codes de sortie
|
|
77
|
-
0 analyse effectuée, note au-dessus du seuil (ou aucun seuil demandé)
|
|
78
|
-
1 note en dessous du seuil passé à --min
|
|
79
|
-
2 erreur d'utilisation ou d'exécution
|
|
80
|
-
|
|
81
|
-
Exemple
|
|
62
|
+
exports.USAGE = `Usage : plugin-eco [options] [chemins...]
|
|
63
|
+
|
|
64
|
+
Analyse la consommation énergétique estimée du code Java, JavaScript et
|
|
65
|
+
TypeScript, et rend une étiquette A–E inspirée du DPE.
|
|
66
|
+
|
|
67
|
+
Options
|
|
68
|
+
--format <text|json> Format de sortie (défaut : text)
|
|
69
|
+
--min <A|B|C|D|E> Note minimale acceptée ; en dessous, sortie en échec
|
|
70
|
+
-h, --help Affiche cette aide
|
|
71
|
+
|
|
72
|
+
Chemins
|
|
73
|
+
Fichiers ou dossiers à analyser. Défaut : le dossier courant.
|
|
74
|
+
Ignorés : ${languages_1.EXCLUDED_DIRS.join(', ')}
|
|
75
|
+
|
|
76
|
+
Codes de sortie
|
|
77
|
+
0 analyse effectuée, note au-dessus du seuil (ou aucun seuil demandé)
|
|
78
|
+
1 note en dessous du seuil passé à --min
|
|
79
|
+
2 erreur d'utilisation ou d'exécution
|
|
80
|
+
|
|
81
|
+
Exemple
|
|
82
82
|
plugin-eco --min C --format json src/ > rapport.json`;
|
|
83
83
|
// ---------------------------------------------------------------------------
|
|
84
84
|
// Analyse des arguments
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/out/webview.js
CHANGED
|
@@ -23,71 +23,71 @@ function dpeStrip(activeLetter) {
|
|
|
23
23
|
.map(l => {
|
|
24
24
|
const c = DPE_COLORS[l];
|
|
25
25
|
const active = l === activeLetter;
|
|
26
|
-
return `<div class="dpe-box${active ? ' active' : ''}"
|
|
26
|
+
return `<div class="dpe-box${active ? ' active' : ''}"
|
|
27
27
|
style="background:${c.bg};color:${c.fg}">${l}</div>`;
|
|
28
28
|
})
|
|
29
29
|
.join('');
|
|
30
30
|
}
|
|
31
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; }
|
|
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
91
|
.clickable:hover td { background: var(--vscode-list-hoverBackground, rgba(255,255,255,0.06)); }`;
|
|
92
92
|
}
|
|
93
93
|
// ---------------------------------------------------------------------------
|
|
@@ -99,9 +99,9 @@ function buildWebviewHtml(findings, score, fileName, nonce, partial = false) {
|
|
|
99
99
|
const total = findings.length;
|
|
100
100
|
const { high, medium } = score.findingCount;
|
|
101
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.
|
|
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
105
|
</div>`
|
|
106
106
|
: '';
|
|
107
107
|
const sorted = [...findings].sort((a, b) => {
|
|
@@ -112,28 +112,28 @@ function buildWebviewHtml(findings, score, fileName, nonce, partial = false) {
|
|
|
112
112
|
? `<tr><td colspan="3" class="empty">Aucun pattern énergivore détecté 🎉</td></tr>`
|
|
113
113
|
: sorted.map(f => {
|
|
114
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>
|
|
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
119
|
</tr>`;
|
|
120
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>
|
|
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
127
|
<div class="summary">${total === 0
|
|
128
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>
|
|
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
137
|
`, nonce);
|
|
138
138
|
}
|
|
139
139
|
// ---------------------------------------------------------------------------
|
|
@@ -149,76 +149,76 @@ function buildWorkspaceHtml(report, nonce) {
|
|
|
149
149
|
// et une lettre unique ne peut pas le désigner.
|
|
150
150
|
const worst = files.reduce((a, b) => (b.score.value < a.score.value ? b : a), files[0]);
|
|
151
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>
|
|
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
157
|
</div>`
|
|
158
158
|
: '';
|
|
159
159
|
const rows = files.map(f => {
|
|
160
160
|
const c = DPE_COLORS[f.score.letter];
|
|
161
161
|
const firstLine = f.findings.length > 0 ? f.findings[0].startLine + 1 : 1;
|
|
162
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>
|
|
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
171
|
<td>${totalFindings > 0
|
|
172
172
|
? `${totalFindings} alerte${totalFindings > 1 ? 's' : ''}`
|
|
173
|
-
: '<span style="opacity:0.4">–</span>'}</td>
|
|
173
|
+
: '<span style="opacity:0.4">–</span>'}</td>
|
|
174
174
|
</tr>`;
|
|
175
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
|
-
});
|
|
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
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}
|
|
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
207
|
`, nonce, `script-src 'nonce-${nonce}';`);
|
|
208
208
|
}
|
|
209
209
|
// ---------------------------------------------------------------------------
|
|
210
210
|
// Helpers
|
|
211
211
|
// ---------------------------------------------------------------------------
|
|
212
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>
|
|
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
222
|
</html>`;
|
|
223
223
|
}
|
|
224
224
|
function esc(str) {
|
package/package.json
CHANGED
|
@@ -1,67 +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.
|
|
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
|
-
}
|
|
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.1",
|
|
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
|
+
}
|