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/out/context.js ADDED
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inferContext = inferContext;
4
+ // --- Indices --------------------------------------------------------------
5
+ /** Modules du cœur de Node. Tout `node:*` est de toute façon serveur. */
6
+ const NODE_BUILTINS = new Set([
7
+ 'fs', 'path', 'http', 'https', 'net', 'tls', 'dns', 'os', 'crypto',
8
+ 'child_process', 'cluster', 'worker_threads', 'stream', 'zlib', 'readline',
9
+ 'buffer', 'querystring', 'url', 'util', 'v8', 'vm', 'perf_hooks',
10
+ ]);
11
+ /** Frameworks serveur, clients de bases de données et outillage backend. */
12
+ const SERVER_MODULES = new Set([
13
+ 'express', 'fastify', 'koa', 'hapi', 'connect', 'body-parser',
14
+ '@nestjs/core', '@nestjs/common', 'next/server',
15
+ 'pg', 'mysql', 'mysql2', 'mongodb', 'mongoose', 'redis', 'ioredis',
16
+ 'prisma', '@prisma/client', 'sequelize', 'typeorm', 'knex',
17
+ 'sqlite3', 'better-sqlite3', 'nodemailer', 'dotenv',
18
+ ]);
19
+ /**
20
+ * Bibliothèques dont le coût de rendu est payé par le navigateur.
21
+ * `react` en fait partie même en rendu côté serveur : ce qui est produit ici
22
+ * sera hydraté et animé sur l'appareil du visiteur.
23
+ */
24
+ const CLIENT_MODULES = new Set([
25
+ 'react', 'react-dom', 'preact', 'vue', 'svelte', 'solid-js', 'lit',
26
+ '@angular/core', 'jquery', 'react-router-dom', 'framer-motion',
27
+ ]);
28
+ /** Globales que seul un navigateur fournit. */
29
+ const CLIENT_GLOBALS = new Set([
30
+ 'document', 'window', 'navigator', 'localStorage', 'sessionStorage',
31
+ 'history', 'XMLHttpRequest',
32
+ ]);
33
+ /** Globales que seul Node fournit (CommonJS). */
34
+ const SERVER_GLOBALS = new Set(['__dirname', '__filename']);
35
+ // --- Inférence ------------------------------------------------------------
36
+ /**
37
+ * @param root racine de l'arbre syntaxique
38
+ * @param spec descripteur du langage ; un `fixedContext` court-circuite l'analyse
39
+ */
40
+ function inferContext(root, spec) {
41
+ if (spec.fixedContext) {
42
+ return { context: spec.fixedContext, clientSignals: [], serverSignals: [] };
43
+ }
44
+ // Une directive 'use client' / 'use server' est une déclaration explicite
45
+ // du développeur : elle prime sur tout ce qu'on pourrait déduire.
46
+ const directive = leadingDirective(root);
47
+ if (directive === 'use client') {
48
+ return { context: 'client', clientSignals: ["directive 'use client'"], serverSignals: [] };
49
+ }
50
+ if (directive === 'use server') {
51
+ return { context: 'server', clientSignals: [], serverSignals: ["directive 'use server'"] };
52
+ }
53
+ const found = {
54
+ client: new Set(),
55
+ server: new Set(),
56
+ globals: new Map(),
57
+ declared: new Set(),
58
+ };
59
+ collect(root, found);
60
+ // Une globale qui porte le même nom qu'une variable locale n'en est pas une.
61
+ // `document` est un nom de paramètre courant — le premier essai classait
62
+ // `src/extension.ts` de ce projet comme du code navigateur.
63
+ for (const [name, side] of found.globals) {
64
+ if (found.declared.has(name))
65
+ continue;
66
+ (side === 'client' ? found.client : found.server).add(name);
67
+ }
68
+ const clientSignals = [...found.client].sort();
69
+ const serverSignals = [...found.server].sort();
70
+ let context = 'unknown';
71
+ if (serverSignals.length > 0 && clientSignals.length === 0)
72
+ context = 'server';
73
+ else if (clientSignals.length > 0 && serverSignals.length === 0)
74
+ context = 'client';
75
+ return { context, clientSignals, serverSignals };
76
+ }
77
+ /** Directive en tête de fichier, si présente. */
78
+ function leadingDirective(root) {
79
+ for (const stmt of root.namedChildren) {
80
+ if (stmt.type !== 'expression_statement')
81
+ return null;
82
+ const expr = stmt.namedChildren[0];
83
+ if (!expr || expr.type !== 'string')
84
+ return null;
85
+ const value = unquote(expr.text);
86
+ if (value === 'use client' || value === 'use server')
87
+ return value;
88
+ // Une autre directive ('use strict') n'interrompt pas la recherche.
89
+ }
90
+ return null;
91
+ }
92
+ /** Nœuds dont les identifiants descendants sont des liaisons, pas des usages. */
93
+ const BINDING_PARENTS = new Set(['formal_parameters', 'import_clause']);
94
+ function collect(node, found) {
95
+ switch (node.type) {
96
+ case 'import_statement':
97
+ case 'export_statement': {
98
+ const source = node.childForFieldName('source');
99
+ if (source)
100
+ classifyModule(unquote(source.text), found);
101
+ break;
102
+ }
103
+ case 'call_expression': {
104
+ const callee = node.childForFieldName('function');
105
+ // require('x') et import('x') portent tous deux leur cible en argument.
106
+ if (callee && (callee.text === 'require' || callee.type === 'import')) {
107
+ const arg = node.childForFieldName('arguments')?.namedChildren[0];
108
+ if (arg?.type === 'string')
109
+ classifyModule(unquote(arg.text), found);
110
+ }
111
+ break;
112
+ }
113
+ case 'variable_declarator':
114
+ case 'function_declaration':
115
+ case 'class_declaration': {
116
+ const name = node.childForFieldName('name');
117
+ if (name?.type === 'identifier')
118
+ found.declared.add(name.text);
119
+ break;
120
+ }
121
+ case 'identifier': {
122
+ if (CLIENT_GLOBALS.has(node.text))
123
+ found.globals.set(node.text, 'client');
124
+ else if (SERVER_GLOBALS.has(node.text))
125
+ found.globals.set(node.text, 'server');
126
+ break;
127
+ }
128
+ default:
129
+ // Du JSX signifie un rendu, donc un coût payé par le navigateur.
130
+ if (node.type.startsWith('jsx_element') || node.type.startsWith('jsx_self_closing')) {
131
+ found.client.add('JSX');
132
+ }
133
+ }
134
+ if (BINDING_PARENTS.has(node.type))
135
+ declareAll(node, found.declared);
136
+ for (const child of node.namedChildren)
137
+ collect(child, found);
138
+ }
139
+ /** Enregistre tous les identifiants d'un sous-arbre comme noms liés. */
140
+ function declareAll(node, declared) {
141
+ if (node.type === 'identifier' || node.type === 'shorthand_property_identifier_pattern') {
142
+ declared.add(node.text);
143
+ }
144
+ for (const child of node.namedChildren)
145
+ declareAll(child, declared);
146
+ }
147
+ function classifyModule(source, found) {
148
+ const client = found.client;
149
+ const server = found.server;
150
+ if (source.startsWith('.') || source.startsWith('/'))
151
+ return; // module local
152
+ if (source.startsWith('node:')) {
153
+ server.add(source);
154
+ return;
155
+ }
156
+ const root = moduleRoot(source);
157
+ if (NODE_BUILTINS.has(root) || SERVER_MODULES.has(root) || SERVER_MODULES.has(source)) {
158
+ server.add(root);
159
+ }
160
+ else if (CLIENT_MODULES.has(root) || CLIENT_MODULES.has(source)) {
161
+ client.add(root);
162
+ }
163
+ // Un module inconnu ne prouve rien : on ne l'utilise pas comme indice.
164
+ }
165
+ /** 'express/lib/router' → 'express' ; '@nestjs/core/x' → '@nestjs/core'. */
166
+ function moduleRoot(source) {
167
+ const parts = source.split('/');
168
+ return source.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
169
+ }
170
+ function unquote(text) {
171
+ return text.replace(/^['"`]|['"`]$/g, '');
172
+ }
173
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,3 @@
1
+ import * as vscode from 'vscode';
2
+ export declare function activate(context: vscode.ExtensionContext): Promise<void>;
3
+ export declare function deactivate(): void;
@@ -0,0 +1,269 @@
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.activate = activate;
37
+ exports.deactivate = deactivate;
38
+ const vscode = __importStar(require("vscode"));
39
+ const parser_1 = require("./parser");
40
+ const rules_1 = require("./rules");
41
+ const scoring_1 = require("./scoring");
42
+ const webview_1 = require("./webview");
43
+ const workspace_1 = require("./workspace");
44
+ const languages_1 = require("./languages");
45
+ const context_1 = require("./context");
46
+ let diagnosticCollection;
47
+ let statusBarItem;
48
+ let panel;
49
+ /** 'file' = rapport du fichier actif / 'workspace' = rapport global. */
50
+ let panelMode = 'file';
51
+ /** Timers de debounce par URI de document. */
52
+ const debounceTimers = new Map();
53
+ /** Dernier résultat d'analyse mono-fichier. */
54
+ let lastFindings = [];
55
+ let lastScore = { letter: 'A', value: 100, findingCount: { high: 0, medium: 0, low: 0 } };
56
+ let lastFileName = '';
57
+ let lastPartial = false;
58
+ // ---------------------------------------------------------------------------
59
+ // Point d'entrée
60
+ // ---------------------------------------------------------------------------
61
+ async function activate(context) {
62
+ try {
63
+ await (0, parser_1.initParser)(context.extensionUri.fsPath);
64
+ }
65
+ catch (err) {
66
+ vscode.window.showErrorMessage(`Plugin Eco : erreur d'initialisation — ${err}`);
67
+ return;
68
+ }
69
+ diagnosticCollection = vscode.languages.createDiagnosticCollection('greencoding');
70
+ context.subscriptions.push(diagnosticCollection);
71
+ statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
72
+ statusBarItem.command = 'greencoding.showPanel';
73
+ context.subscriptions.push(statusBarItem);
74
+ // Commande : re-analyser le fichier actif
75
+ context.subscriptions.push(vscode.commands.registerCommand('greencoding.analyze', () => {
76
+ const doc = vscode.window.activeTextEditor?.document;
77
+ if (doc && (0, languages_1.isSupported)(doc.languageId))
78
+ analyzeDocument(doc);
79
+ }));
80
+ // Commande : ouvrir / révéler le panneau (mode fichier)
81
+ context.subscriptions.push(vscode.commands.registerCommand('greencoding.showPanel', () => {
82
+ panelMode = 'file';
83
+ openOrRevealPanel(context);
84
+ }));
85
+ // Commande : analyser tout le workspace
86
+ context.subscriptions.push(vscode.commands.registerCommand('greencoding.analyzeWorkspace', async () => {
87
+ const report = await (0, workspace_1.analyzeWorkspace)(diagnosticCollection);
88
+ if (!report)
89
+ return;
90
+ panelMode = 'workspace';
91
+ openOrRevealPanel(context);
92
+ if (panel) {
93
+ panel.title = '⚡ Rapport Éco — Workspace';
94
+ panel.webview.html = (0, webview_1.buildWorkspaceHtml)(report, getNonce());
95
+ }
96
+ }));
97
+ // Événements document
98
+ context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(doc => {
99
+ if ((0, languages_1.isSupported)(doc.languageId))
100
+ analyzeDocument(doc);
101
+ }), vscode.workspace.onDidSaveTextDocument(doc => {
102
+ if ((0, languages_1.isSupported)(doc.languageId))
103
+ analyzeDocument(doc);
104
+ }), vscode.workspace.onDidChangeTextDocument(event => {
105
+ if (!(0, languages_1.isSupported)(event.document.languageId))
106
+ return;
107
+ scheduleAnalysis(event.document);
108
+ }), vscode.window.onDidChangeActiveTextEditor(editor => {
109
+ if (editor && (0, languages_1.isSupported)(editor.document.languageId)) {
110
+ analyzeDocument(editor.document);
111
+ }
112
+ else {
113
+ statusBarItem.hide();
114
+ }
115
+ }), vscode.workspace.onDidCloseTextDocument(doc => {
116
+ diagnosticCollection.delete(doc.uri);
117
+ const key = doc.uri.toString();
118
+ const timer = debounceTimers.get(key);
119
+ if (timer) {
120
+ clearTimeout(timer);
121
+ debounceTimers.delete(key);
122
+ }
123
+ }));
124
+ // Analyser le fichier actif au démarrage
125
+ const activeDoc = vscode.window.activeTextEditor?.document;
126
+ if (activeDoc && (0, languages_1.isSupported)(activeDoc.languageId))
127
+ analyzeDocument(activeDoc);
128
+ }
129
+ function deactivate() {
130
+ diagnosticCollection?.clear();
131
+ diagnosticCollection?.dispose();
132
+ statusBarItem?.dispose();
133
+ panel?.dispose();
134
+ debounceTimers.forEach(t => clearTimeout(t));
135
+ debounceTimers.clear();
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Analyse mono-fichier
139
+ // ---------------------------------------------------------------------------
140
+ function scheduleAnalysis(document) {
141
+ const key = document.uri.toString();
142
+ const existing = debounceTimers.get(key);
143
+ if (existing)
144
+ clearTimeout(existing);
145
+ debounceTimers.set(key, setTimeout(() => {
146
+ analyzeDocument(document);
147
+ debounceTimers.delete(key);
148
+ }, 400));
149
+ }
150
+ function analyzeDocument(document) {
151
+ const spec = (0, languages_1.specFor)(document.languageId);
152
+ if (!spec)
153
+ return;
154
+ try {
155
+ const tree = (0, parser_1.parse)(document.getText(), document.languageId);
156
+ const { context, clientSignals, serverSignals } = (0, context_1.inferContext)(tree.rootNode, spec);
157
+ const findings = (0, rules_1.collectFindings)(tree.rootNode, spec, context);
158
+ const score = (0, scoring_1.computeScore)(findings);
159
+ // Un arbre incomplet donne une analyse incomplète : sans ce signal, un
160
+ // fichier illisible sortirait en A 100/100, ce qui est pire qu'aucun
161
+ // résultat. Fréquent pendant la frappe, d'où un simple avertissement
162
+ // plutôt qu'un blocage.
163
+ const partial = tree.rootNode.hasError;
164
+ lastFindings = findings;
165
+ lastScore = score;
166
+ lastFileName = document.fileName;
167
+ lastPartial = partial;
168
+ // Diagnostics inline
169
+ diagnosticCollection.set(document.uri, findings.map(f => {
170
+ const range = new vscode.Range(f.startLine, f.startChar, f.endLine, f.endChar);
171
+ const severity = f.severity === 'high'
172
+ ? vscode.DiagnosticSeverity.Warning
173
+ : vscode.DiagnosticSeverity.Information;
174
+ const diag = new vscode.Diagnostic(range, `⚡ ${f.message}`, severity);
175
+ diag.source = 'Plugin Eco';
176
+ return diag;
177
+ }));
178
+ // Barre de statut
179
+ statusBarItem.text = `⚡ Éco: ${score.letter}${partial ? ' ⚠' : ''}`;
180
+ statusBarItem.tooltip = [
181
+ `${spec.label} — score : ${score.value}/100 — ${(0, scoring_1.scoreSummary)(score)}`,
182
+ `Alertes : ${score.findingCount.high} haute(s), ${score.findingCount.medium} moyenne(s)`,
183
+ `Exécution : ${contextLabel(context)}${contextEvidence(clientSignals, serverSignals)}`,
184
+ ...(partial
185
+ ? ['', '⚠ Analyse partielle : le fichier n\'a pas pu être parsé entièrement.',
186
+ 'Le score est à prendre avec réserve.']
187
+ : []),
188
+ '',
189
+ 'Cliquer pour ouvrir le rapport détaillé',
190
+ ].join('\n');
191
+ statusBarItem.backgroundColor =
192
+ score.letter === 'D' || score.letter === 'E'
193
+ ? new vscode.ThemeColor('statusBarItem.warningBackground')
194
+ : undefined;
195
+ statusBarItem.show();
196
+ // Mettre à jour le panneau uniquement si on est en mode fichier
197
+ if (panel && panelMode === 'file') {
198
+ panel.title = '⚡ Rapport Éco';
199
+ panel.webview.html = (0, webview_1.buildWebviewHtml)(findings, score, document.fileName, getNonce(), partial);
200
+ }
201
+ }
202
+ catch {
203
+ // Silencieux (fichier incomplet en cours de frappe)
204
+ }
205
+ }
206
+ // ---------------------------------------------------------------------------
207
+ // Panneau WebView
208
+ // ---------------------------------------------------------------------------
209
+ function openOrRevealPanel(context) {
210
+ if (panel) {
211
+ panel.reveal(vscode.ViewColumn.Beside);
212
+ // Rafraîchir si on repasse en mode fichier
213
+ if (panelMode === 'file') {
214
+ panel.title = '⚡ Rapport Éco';
215
+ panel.webview.html = (0, webview_1.buildWebviewHtml)(lastFindings, lastScore, lastFileName, getNonce(), lastPartial);
216
+ }
217
+ return;
218
+ }
219
+ panel = vscode.window.createWebviewPanel('greencoding.report', '⚡ Rapport Éco', vscode.ViewColumn.Beside, {
220
+ enableScripts: true,
221
+ retainContextWhenHidden: true,
222
+ });
223
+ panel.webview.html = (0, webview_1.buildWebviewHtml)(lastFindings, lastScore, lastFileName, getNonce(), lastPartial);
224
+ // Handler : clic sur un fichier dans le rapport workspace → ouvrir dans l'éditeur
225
+ panel.webview.onDidReceiveMessage(async (message) => {
226
+ if (message.command === 'open' && typeof message.file === 'string') {
227
+ try {
228
+ const uri = vscode.Uri.parse(message.file);
229
+ const doc = await vscode.workspace.openTextDocument(uri);
230
+ const line = Math.max(0, (message.line || 1) - 1);
231
+ await vscode.window.showTextDocument(doc, {
232
+ selection: new vscode.Range(line, 0, line, 0),
233
+ viewColumn: vscode.ViewColumn.One,
234
+ });
235
+ }
236
+ catch {
237
+ // URI invalide ou fichier supprimé
238
+ }
239
+ }
240
+ }, null, context.subscriptions);
241
+ panel.onDidDispose(() => {
242
+ panel = undefined;
243
+ panelMode = 'file';
244
+ }, null, context.subscriptions);
245
+ }
246
+ // ---------------------------------------------------------------------------
247
+ // Utilitaire
248
+ // ---------------------------------------------------------------------------
249
+ const CONTEXT_LABELS = {
250
+ client: 'navigateur — le coût est payé par chaque visiteur',
251
+ server: 'serveur',
252
+ unknown: 'indéterminée',
253
+ };
254
+ function contextLabel(context) {
255
+ return CONTEXT_LABELS[context];
256
+ }
257
+ /** Indices ayant conduit au verdict, tronqués pour rester lisibles. */
258
+ function contextEvidence(clientSignals, serverSignals) {
259
+ const all = [...serverSignals, ...clientSignals];
260
+ if (all.length === 0)
261
+ return '';
262
+ const shown = all.slice(0, 4).join(', ');
263
+ return ` (${shown}${all.length > 4 ? '…' : ''})`;
264
+ }
265
+ function getNonce() {
266
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
267
+ return Array.from({ length: 32 }, () => chars.charAt(Math.floor(Math.random() * chars.length))).join('');
268
+ }
269
+ //# sourceMappingURL=extension.js.map
package/out/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Point d'entrée bibliothèque du paquet npm.
3
+ *
4
+ * Ce fichier expose le moteur d'analyse — parseur, règles, contexte, score —
5
+ * et **rien de l'intégration VSCode** : ni `extension.ts`, ni `webview.ts`, ni
6
+ * `workspace.ts`, qui importent `vscode` et ne se chargent pas hors éditeur.
7
+ * C'est la même frontière que celle tenue par `cli.ts`.
8
+ *
9
+ * Le manifeste garde `main` vers `out/extension.js` pour VSCode, qui charge ce
10
+ * chemin en absolu et ignore `exports` ; `require('plugin-eco')` passe ici.
11
+ *
12
+ * ```js
13
+ * const { initParser, parse, collectFindings, computeScore } = require('plugin-eco');
14
+ *
15
+ * await initParser();
16
+ * const tree = parse(source, 'java');
17
+ * const findings = collectFindings(tree.rootNode, specFor('java'));
18
+ * console.log(computeScore(findings).letter); // 'A' … 'E'
19
+ * ```
20
+ */
21
+ export { initParser, parse, parseWith } from './parser';
22
+ export { collectFindings } from './rules';
23
+ export { letterFor, computeScore, aggregateScore, scoreSummary } from './scoring';
24
+ export { inferContext } from './context';
25
+ export type { ContextResult } from './context';
26
+ export { LANGUAGES, RULE_CONTEXTS, ruleAppliesIn, specFor, specForPath, isSupported, } from './languages';
27
+ export type { LanguageSpec, RuleId } from './languages';
28
+ export type { ExecutionContext, Finding, FileResult, Score, Severity, WorkspaceReport, } from './types';
package/out/index.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * Point d'entrée bibliothèque du paquet npm.
4
+ *
5
+ * Ce fichier expose le moteur d'analyse — parseur, règles, contexte, score —
6
+ * et **rien de l'intégration VSCode** : ni `extension.ts`, ni `webview.ts`, ni
7
+ * `workspace.ts`, qui importent `vscode` et ne se chargent pas hors éditeur.
8
+ * C'est la même frontière que celle tenue par `cli.ts`.
9
+ *
10
+ * Le manifeste garde `main` vers `out/extension.js` pour VSCode, qui charge ce
11
+ * chemin en absolu et ignore `exports` ; `require('plugin-eco')` passe ici.
12
+ *
13
+ * ```js
14
+ * const { initParser, parse, collectFindings, computeScore } = require('plugin-eco');
15
+ *
16
+ * await initParser();
17
+ * const tree = parse(source, 'java');
18
+ * const findings = collectFindings(tree.rootNode, specFor('java'));
19
+ * console.log(computeScore(findings).letter); // 'A' … 'E'
20
+ * ```
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.isSupported = exports.specForPath = exports.specFor = exports.ruleAppliesIn = exports.RULE_CONTEXTS = exports.LANGUAGES = exports.inferContext = exports.scoreSummary = exports.aggregateScore = exports.computeScore = exports.letterFor = exports.collectFindings = exports.parseWith = exports.parse = exports.initParser = void 0;
24
+ var parser_1 = require("./parser");
25
+ Object.defineProperty(exports, "initParser", { enumerable: true, get: function () { return parser_1.initParser; } });
26
+ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return parser_1.parse; } });
27
+ Object.defineProperty(exports, "parseWith", { enumerable: true, get: function () { return parser_1.parseWith; } });
28
+ var rules_1 = require("./rules");
29
+ Object.defineProperty(exports, "collectFindings", { enumerable: true, get: function () { return rules_1.collectFindings; } });
30
+ var scoring_1 = require("./scoring");
31
+ Object.defineProperty(exports, "letterFor", { enumerable: true, get: function () { return scoring_1.letterFor; } });
32
+ Object.defineProperty(exports, "computeScore", { enumerable: true, get: function () { return scoring_1.computeScore; } });
33
+ Object.defineProperty(exports, "aggregateScore", { enumerable: true, get: function () { return scoring_1.aggregateScore; } });
34
+ Object.defineProperty(exports, "scoreSummary", { enumerable: true, get: function () { return scoring_1.scoreSummary; } });
35
+ var context_1 = require("./context");
36
+ Object.defineProperty(exports, "inferContext", { enumerable: true, get: function () { return context_1.inferContext; } });
37
+ var languages_1 = require("./languages");
38
+ Object.defineProperty(exports, "LANGUAGES", { enumerable: true, get: function () { return languages_1.LANGUAGES; } });
39
+ Object.defineProperty(exports, "RULE_CONTEXTS", { enumerable: true, get: function () { return languages_1.RULE_CONTEXTS; } });
40
+ Object.defineProperty(exports, "ruleAppliesIn", { enumerable: true, get: function () { return languages_1.ruleAppliesIn; } });
41
+ Object.defineProperty(exports, "specFor", { enumerable: true, get: function () { return languages_1.specFor; } });
42
+ Object.defineProperty(exports, "specForPath", { enumerable: true, get: function () { return languages_1.specForPath; } });
43
+ Object.defineProperty(exports, "isSupported", { enumerable: true, get: function () { return languages_1.isSupported; } });
44
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,76 @@
1
+ import { ExecutionContext } from './types';
2
+ /**
3
+ * Descripteurs de langage.
4
+ *
5
+ * Tout ce qui varie d'un langage à l'autre est décrit ici : la grammaire à
6
+ * charger, les noms de nœuds tree-sitter, et les règles applicables. Les autres
7
+ * modules ne connaissent que ce descripteur — ils n'ont plus de `if (java)`.
8
+ */
9
+ /** Identifiant d'une règle de détection. */
10
+ export type RuleId = 'nested-loops' | 'string-concat-in-loop' | 'object-creation-in-loop' | 'regex-compile-in-loop' | 'blocking-io-in-loop' | 'sql-without-limit' | 'await-in-loop' | 'sync-io-in-function' | 'polling-interval' | 'unthrottled-event-listener' | 'whole-library-import';
11
+ /**
12
+ * Contexte d'exécution dans lequel chaque règle a un sens.
13
+ *
14
+ * `'any'` signifie « partout, y compris quand le contexte est indéterminé ».
15
+ * Une règle restreinte ne se déclenche **pas** sur un fichier `unknown` : sans
16
+ * certitude, mieux vaut se taire que produire un faux positif.
17
+ */
18
+ export declare const RULE_CONTEXTS: Record<RuleId, 'any' | ExecutionContext[]>;
19
+ /** La règle s'applique-t-elle à un fichier dont le contexte est celui-ci ? */
20
+ export declare function ruleAppliesIn(rule: RuleId, context: ExecutionContext): boolean;
21
+ /**
22
+ * Noms des nœuds tree-sitter, qui diffèrent d'une grammaire à l'autre.
23
+ * Exemple : une instanciation est `object_creation_expression` en Java,
24
+ * `new_expression` en JS.
25
+ */
26
+ export interface NodeNames {
27
+ loops: string[];
28
+ objectCreation: string[];
29
+ call: string[];
30
+ stringLiteral: string[];
31
+ /** Nœud portant `+=` — un opérateur composé a son propre type de nœud en JS. */
32
+ compoundAssignment: string[];
33
+ /** Frontières de fonction, qui bornent la portée de certaines règles. */
34
+ functions: string[];
35
+ }
36
+ export interface LanguageSpec {
37
+ /** Nom lisible, pour les messages. */
38
+ label: string;
39
+ /** Fichier WASM dans out/wasm/. */
40
+ grammarFile: string;
41
+ /** `languageId` VSCode couverts par ce descripteur. */
42
+ vscodeLanguageIds: string[];
43
+ /** Extensions de fichier couvertes, point compris. Source unique : le glob
44
+ * du scan VSCode et la reconnaissance par chemin du CLI en découlent. */
45
+ extensions: string[];
46
+ nodes: NodeNames;
47
+ /** Règles applicables. Une règle absente n'est jamais évaluée. */
48
+ rules: RuleId[];
49
+ /**
50
+ * Contexte d'exécution connu d'avance, qui dispense de l'inférence.
51
+ * Java n'a pas de moitié navigateur : c'est du serveur par construction.
52
+ * Absent pour JS/TS, où le contexte se déduit du code.
53
+ */
54
+ fixedContext?: ExecutionContext;
55
+ }
56
+ export declare const LANGUAGES: LanguageSpec[];
57
+ /** Descripteur correspondant à un `languageId` VSCode, ou undefined. */
58
+ export declare function specFor(languageId: string): LanguageSpec | undefined;
59
+ /** Le langage est-il analysable ? */
60
+ export declare function isSupported(languageId: string): boolean;
61
+ /** Fichiers WASM à charger, sans doublon (tsx sert deux descripteurs). */
62
+ export declare function grammarFiles(): string[];
63
+ /**
64
+ * Dossiers exclus de toute analyse de projet.
65
+ *
66
+ * Partagé par le scan VSCode et le CLI : deux listes divergentes donneraient
67
+ * deux verdicts différents sur le même dépôt, ce qui ruinerait la confiance
68
+ * dans l'outil bien plus sûrement qu'une règle imparfaite.
69
+ */
70
+ export declare const EXCLUDED_DIRS: string[];
71
+ /** Motif VSCode excluant ces dossiers. */
72
+ export declare function excludeGlob(): string;
73
+ /** Motif de recherche VSCode pour un langage, dérivé de ses extensions. */
74
+ export declare function globFor(spec: LanguageSpec): string;
75
+ /** Descripteur correspondant à un chemin de fichier, ou undefined. */
76
+ export declare function specForPath(filePath: string): LanguageSpec | undefined;