mostajs-orm-validator 0.2.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/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @mostajs/orm — ORMConceptValidator (VSCode Extension)
2
+
3
+ > **v0.1.0** — Plug-in VSCode pour le [`ORMConceptValidator`](https://www.npmjs.com/package/@mostajs/orm) de `@mostajs/orm`. Détecte 18 anomalies conceptuelles dans vos `EntitySchema` avec squiggles inline et quick-fix.
4
+
5
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
6
+
7
+ ## Installation
8
+
9
+ ### Depuis le Marketplace *(quand publié)*
10
+
11
+ ```
12
+ ext install apolocine.mostajs-orm-validator
13
+ ```
14
+
15
+ ### Depuis source *(développement)*
16
+
17
+ ```bash
18
+ git clone https://github.com/apolocine/mosta-orm-vscode
19
+ cd mosta-orm-vscode
20
+ npm install
21
+ npm run compile
22
+ # Ouvrir le dossier dans VSCode, F5 pour lancer un Extension Development Host
23
+ ```
24
+
25
+ ## Pré-requis
26
+
27
+ Le workspace doit avoir :
28
+ - `@mostajs/orm@^1.15.0` *(le validator CLI doit être accessible via `npx mostajs-orm-validator`)*
29
+ - Un dossier de schémas *(default `./schemas`, configurable)*
30
+
31
+ ## Configuration
32
+
33
+ Paramètres dans VSCode *(File > Preferences > Settings, chercher "mostajs orm")* :
34
+
35
+ | Paramètre | Default | Description |
36
+ |---|---|---|
37
+ | `mostajsOrmValidator.enabled` | `true` | Active/désactive l'extension pour le workspace |
38
+ | `mostajsOrmValidator.schemasDir` | `./schemas` | Dossier des fichiers `*.schema.ts` |
39
+ | `mostajsOrmValidator.sourceRoot` | `./lib` | Dossier source pour règles cross-file *(R005, R007, R008, R011, R012, R014, R015)* |
40
+ | `mostajsOrmValidator.ignore` | `[]` | Règles à ignorer *(ex: `["R015", "R017"]`)* |
41
+ | `mostajsOrmValidator.runOnSave` | `true` | Relancer à chaque save d'un `*.schema.ts` |
42
+
43
+ ## Commandes *(Ctrl+Shift+P)*
44
+
45
+ | Commande | Description |
46
+ |---|---|
47
+ | **ORMValidator: Validate current workspace** | Lance le validator sur tout le workspace |
48
+ | **ORMValidator: Apply all auto-fixes** | Applique `--fix` pour toutes les règles fixables *(V3-A V2)* |
49
+ | **ORMValidator: Rollback last --fix (restore .bak)** | Restaure les `.bak` du dernier `--fix` |
50
+
51
+ ## Fonctionnement
52
+
53
+ À l'ouverture d'un fichier TypeScript, l'extension :
54
+ 1. Lance `npx mostajs-orm-validator <schemasDir> --src <sourceRoot> --format json`
55
+ 2. Parse les findings JSON
56
+ 3. Affiche chaque finding comme un **Diagnostic VSCode** *(squiggle inline)* :
57
+ - 🔴 `error` → squiggle rouge
58
+ - 🟡 `warning` → squiggle jaune
59
+ - ℹ️ `info` → squiggle bleu
60
+ - 💡 `hint` → soulignement gris
61
+ 4. Le code de la règle *(ex: R001-EMPTY-RELATIONS)* apparaît dans le tooltip + dans la barre Problems
62
+ 5. Sur sauvegarde *(si `runOnSave: true`)*, relance la validation
63
+
64
+ ## Quick-fix V3-C V1 *(limité)*
65
+
66
+ V0.1.0 propose des suggestions textuelles dans les tooltips. Les quick-fix
67
+ automatiques *(via VSCode Code Actions)* arrivent en V3-C V2 — pour l'instant
68
+ utiliser le CLI :
69
+
70
+ ```bash
71
+ npx mostajs-orm-validator ./schemas --src ./lib --fix-dry-run # preview
72
+ npx mostajs-orm-validator ./schemas --src ./lib --fix # apply
73
+ npx mostajs-orm-validator ./schemas --rollback-fix # undo
74
+ ```
75
+
76
+ ## Limites V0.1.0
77
+
78
+ - Quick-fix VSCode Code Action : non implémenté *(V3-C V2)* — utiliser CLI pour appliquer
79
+ - LSP server dédié : pas encore *(V3-C V3, performance optimale)* — utilise le CLI via spawn
80
+ - Marketplace publish : pas encore *(décision utilisateur — nécessite PAT Azure DevOps)*
81
+
82
+ ## Roadmap
83
+
84
+ - **V3-C V2** : LSP server natif *(perf, sans spawn CLI)* + Code Actions VSCode pour les quick-fix
85
+ - **V3-C V3** : Publish sur VSCode Marketplace + JetBrains Marketplace *(IntelliJ/WebStorm)*
86
+ - **V4** : Intégration GitHub Actions / preview dans PR
87
+
88
+ ## Licence
89
+
90
+ AGPL-3.0-or-later
package/llms.txt ADDED
@@ -0,0 +1,66 @@
1
+ # mostajs-orm-validator (extension VSCode) — fiche LLM
2
+ > Linter algorithmique des schémas @mostajs/orm — 18 anomalies conceptuelles avec squiggles + quick-fix.
3
+
4
+ - Version: 0.2.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
+ - Chemin: mostajs/mosta-orm-vscode · Statut audit: partiel (src/ + manifest VSCode ; out/ compilé)
6
+
7
+ ## RÔLE
8
+ Extension Visual Studio Code (publisher `apolocine`). Analyse en continu les fichiers
9
+ `*.schema.ts` d'un projet @mostajs/orm et signale 18 anomalies conceptuelles (règles
10
+ R001-R018) directement dans l'éditeur, avec quick-fix pour les règles auto-corrigibles.
11
+ Importe @mostajs/orm/validator en-process (plus de spawn CLI), validation à la sauvegarde
12
+ (debounced) et au démarrage. Ce n'est pas un paquet npm importable mais une extension VSIX.
13
+
14
+ ## INSTALLATION
15
+ Installer le `.vsix` (commande `vsce package` puis VSCode → Install from VSIX),
16
+ ou publier via `vsce publish`. Requiert VSCode ^1.80.0.
17
+
18
+ ## EXPORTS
19
+ Aucun export de bibliothèque. L'extension expose des commandes et des réglages VSCode.
20
+ Point d'entrée : `./out/extension.js` — fonction `activate(context)`.
21
+ Activation : onLanguage:typescript, onLanguage:typescriptreact.
22
+
23
+ ## API — COMMANDES VSCODE
24
+ - `mostajsOrmValidator.validate` — « ORMValidator: Validate current workspace »
25
+ - `mostajsOrmValidator.fixAll` — « ORMValidator: Apply all auto-fixes »
26
+ - `mostajsOrmValidator.fixOne` — « ORMValidator: Apply this fix » (quick-fix ciblé)
27
+ - `mostajsOrmValidator.rollback` — « ORMValidator: Rollback last --fix (restore .bak) »
28
+
29
+ ## RÉGLAGES (settings.json)
30
+ - `mostajsOrmValidator.enabled` (boolean, def true) — activer le validator
31
+ - `mostajsOrmValidator.schemasDir` (string, def `./schemas`) — dossier des `*.schema.ts`
32
+ - `mostajsOrmValidator.sourceRoot` (string, def `./lib`) — racine des sources ;
33
+ active les règles cross-file R005, R007, R008, R011, R012, R014, R015
34
+ - `mostajsOrmValidator.ignore` (string[], def []) — règles à ignorer, ex `['R015','R017']`
35
+ - `mostajsOrmValidator.runOnSave` (boolean, def true) — revalider à chaque sauvegarde
36
+
37
+ ## TYPES CLÉS
38
+ Sans objet — extension VSCode, pas de surface de types publique.
39
+
40
+ ## PATTERN
41
+ ```jsonc
42
+ // .vscode/settings.json du projet @mostajs/orm
43
+ {
44
+ "mostajsOrmValidator.schemasDir": "./src/schemas",
45
+ "mostajsOrmValidator.sourceRoot": "./src",
46
+ "mostajsOrmValidator.ignore": ["R015"]
47
+ }
48
+ ```
49
+ Ouvrir un fichier `*.schema.ts` : les findings apparaissent en squiggles ;
50
+ Ctrl+. propose les quick-fix sur les règles fixables.
51
+
52
+ ## DÉPEND DE
53
+ - @mostajs/orm (dependency — utilise le sous-chemin `@mostajs/orm/validator` en-process)
54
+ - jiti (chargement des modules schéma TypeScript à la volée)
55
+
56
+ ## PIÈGES
57
+ - Les règles cross-file (R005, R007, R008, R011, R012, R014, R015) ne s'activent que si
58
+ `sourceRoot` est correctement renseigné — sinon elles sont silencieusement inactives.
59
+ - `rollback` restaure les `.bak` créés par le dernier `--fix` ; idempotent (no-op sans `.bak`).
60
+ - Validation déclenchée uniquement sur fichiers `.ts`/`.tsx` et si `runOnSave` est actif
61
+ (debounce 300 ms).
62
+ - Le linter partage le moteur de @mostajs/orm/validator — version des règles liée à la
63
+ version de @mostajs/orm installée.
64
+
65
+ ## RÉFÉRENCES
66
+ - README.md · (règles documentées dans mosta-orm/docs/ORMConceptValidator.md)
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "mostajs-orm-validator",
3
+ "displayName": "@mostajs/orm — ORMConceptValidator",
4
+ "description": "Algorithmic linter for @mostajs/orm schemas. Detects 18 conceptual anomalies with inline squiggles + quick-fix.",
5
+ "version": "0.2.0",
6
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
+ "license": "AGPL-3.0-or-later",
8
+ "publisher": "apolocine",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/apolocine/mosta-orm-vscode"
12
+ },
13
+ "engines": {
14
+ "vscode": "^1.80.0"
15
+ },
16
+ "categories": [
17
+ "Linters",
18
+ "Other"
19
+ ],
20
+ "keywords": [
21
+ "orm",
22
+ "validator",
23
+ "linter",
24
+ "mostajs",
25
+ "schema"
26
+ ],
27
+ "main": "./out/extension.js",
28
+ "activationEvents": [
29
+ "onLanguage:typescript",
30
+ "onLanguage:typescriptreact"
31
+ ],
32
+ "contributes": {
33
+ "configuration": {
34
+ "title": "@mostajs/orm — ORMConceptValidator",
35
+ "properties": {
36
+ "mostajsOrmValidator.enabled": {
37
+ "type": "boolean",
38
+ "default": true,
39
+ "description": "Activer/désactiver le validator pour ce workspace."
40
+ },
41
+ "mostajsOrmValidator.schemasDir": {
42
+ "type": "string",
43
+ "default": "./schemas",
44
+ "description": "Chemin relatif vers le dossier contenant les fichiers *.schema.ts."
45
+ },
46
+ "mostajsOrmValidator.sourceRoot": {
47
+ "type": "string",
48
+ "default": "./lib",
49
+ "description": "Chemin relatif vers les sources (active les règles cross-file R005, R007, R008, R011, R012, R014, R015)."
50
+ },
51
+ "mostajsOrmValidator.ignore": {
52
+ "type": "array",
53
+ "default": [],
54
+ "items": {
55
+ "type": "string"
56
+ },
57
+ "description": "Liste des règles à ignorer (ex: ['R015', 'R017'])."
58
+ },
59
+ "mostajsOrmValidator.runOnSave": {
60
+ "type": "boolean",
61
+ "default": true,
62
+ "description": "Relancer la validation à chaque sauvegarde d'un schema."
63
+ }
64
+ }
65
+ },
66
+ "commands": [
67
+ {
68
+ "command": "mostajsOrmValidator.validate",
69
+ "title": "ORMValidator: Validate current workspace",
70
+ "category": "@mostajs/orm"
71
+ },
72
+ {
73
+ "command": "mostajsOrmValidator.fixAll",
74
+ "title": "ORMValidator: Apply all auto-fixes",
75
+ "category": "@mostajs/orm"
76
+ },
77
+ {
78
+ "command": "mostajsOrmValidator.fixOne",
79
+ "title": "ORMValidator: Apply this fix",
80
+ "category": "@mostajs/orm"
81
+ },
82
+ {
83
+ "command": "mostajsOrmValidator.rollback",
84
+ "title": "ORMValidator: Rollback last --fix (restore .bak)",
85
+ "category": "@mostajs/orm"
86
+ }
87
+ ]
88
+ },
89
+ "dependencies": {
90
+ "@mostajs/orm": "^1.16.0",
91
+ "jiti": "^2.7.0"
92
+ },
93
+ "devDependencies": {
94
+ "@types/vscode": "^1.80.0",
95
+ "@types/node": "^22.0.0",
96
+ "@vscode/vsce": "^3.0.0",
97
+ "typescript": "^5.6.0"
98
+ },
99
+ "scripts": {
100
+ "compile": "tsc",
101
+ "watch": "tsc --watch",
102
+ "vscode:prepublish": "npm run compile",
103
+ "package": "vsce package"
104
+ }
105
+ }
@@ -0,0 +1,365 @@
1
+ // @mostajs/orm — VSCode extension v0.2.0 (V3-C V2)
2
+ // Author: Dr Hamid MADANI <drmdh@msn.com>
3
+ //
4
+ // V3-C V2 — Améliorations vs V0.1.0 :
5
+ // - Import direct @mostajs/orm/validator (plus de spawn CLI)
6
+ // - Code Actions (quick-fix) pour les findings fixables
7
+ // - Hot reload sur change (debounced)
8
+ // - Performance : in-process, partagé entre fichiers
9
+
10
+ import * as vscode from 'vscode'
11
+ import * as path from 'node:path'
12
+ import * as fs from 'node:fs'
13
+
14
+ let diagnosticCollection: vscode.DiagnosticCollection
15
+ let outputChannel: vscode.OutputChannel
16
+ let lastReport: any = null
17
+ let debounceHandle: NodeJS.Timeout | null = null
18
+
19
+ const DEBOUNCE_MS = 300
20
+
21
+ export async function activate(context: vscode.ExtensionContext) {
22
+ outputChannel = vscode.window.createOutputChannel('ORMConceptValidator')
23
+ diagnosticCollection = vscode.languages.createDiagnosticCollection('mostajsOrmValidator')
24
+ context.subscriptions.push(diagnosticCollection, outputChannel)
25
+
26
+ // Commands
27
+ context.subscriptions.push(
28
+ vscode.commands.registerCommand('mostajsOrmValidator.validate', runValidation),
29
+ vscode.commands.registerCommand('mostajsOrmValidator.fixAll', runFixAll),
30
+ vscode.commands.registerCommand('mostajsOrmValidator.fixOne', runFixOne),
31
+ vscode.commands.registerCommand('mostajsOrmValidator.rollback', runRollback),
32
+ )
33
+
34
+ // Trigger : on save (debounced)
35
+ context.subscriptions.push(
36
+ vscode.workspace.onDidSaveTextDocument((doc) => {
37
+ if (!isEnabled()) return
38
+ if (!doc.fileName.endsWith('.ts') && !doc.fileName.endsWith('.tsx')) return
39
+ const cfg = vscode.workspace.getConfiguration('mostajsOrmValidator')
40
+ if (!cfg.get<boolean>('runOnSave', true)) return
41
+ scheduleValidation()
42
+ }),
43
+ )
44
+
45
+ // Code Action provider — propose quick-fix sur les findings fixables
46
+ context.subscriptions.push(
47
+ vscode.languages.registerCodeActionsProvider(
48
+ { language: 'typescript' },
49
+ new ValidatorCodeActionProvider(),
50
+ { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] },
51
+ ),
52
+ vscode.languages.registerCodeActionsProvider(
53
+ { language: 'typescriptreact' },
54
+ new ValidatorCodeActionProvider(),
55
+ { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] },
56
+ ),
57
+ )
58
+
59
+ // Validation initiale au démarrage
60
+ if (isEnabled() && hasSchemasDir()) {
61
+ await runValidation()
62
+ }
63
+
64
+ outputChannel.appendLine('ORMConceptValidator V3-C V2 activated.')
65
+ }
66
+
67
+ export function deactivate() {
68
+ if (debounceHandle) clearTimeout(debounceHandle)
69
+ diagnosticCollection?.dispose()
70
+ outputChannel?.dispose()
71
+ }
72
+
73
+ function scheduleValidation() {
74
+ if (debounceHandle) clearTimeout(debounceHandle)
75
+ debounceHandle = setTimeout(() => { void runValidation() }, DEBOUNCE_MS)
76
+ }
77
+
78
+ // ─── Validation ─────────────────────────────────────────────────
79
+
80
+ async function runValidation() {
81
+ const workspace = vscode.workspace.workspaceFolders?.[0]
82
+ if (!workspace) return
83
+
84
+ const cfg = vscode.workspace.getConfiguration('mostajsOrmValidator')
85
+ const schemasDir = path.join(workspace.uri.fsPath, cfg.get<string>('schemasDir') ?? './schemas')
86
+ const sourceRoot = path.join(workspace.uri.fsPath, cfg.get<string>('sourceRoot') ?? './lib')
87
+ const ignore = cfg.get<string[]>('ignore') ?? []
88
+
89
+ if (!fs.existsSync(schemasDir)) {
90
+ outputChannel.appendLine(`Skip : schemasDir introuvable (${schemasDir})`)
91
+ return
92
+ }
93
+
94
+ try {
95
+ // Import direct (in-process). Lazy load car @mostajs/orm/validator + ts-morph
96
+ // est lourd (~2 MB) — on ne le charge qu'à la 1ère validation.
97
+ const validator = await import('@mostajs/orm/validator')
98
+ const { createJiti } = await import('jiti')
99
+
100
+ // Charge les schemas via jiti (TS direct)
101
+ const jiti = createJiti(require.main?.filename ?? __filename, { interopDefault: true, fsCache: false })
102
+ const byName = new Map<string, any>()
103
+ walkSchemas(schemasDir, async (filePath) => {
104
+ try {
105
+ const mod: any = await jiti.import(filePath)
106
+ for (const exp of Object.values(mod)) {
107
+ if (isEntitySchema(exp)) {
108
+ const s = exp as any
109
+ if (!byName.has(s.name)) byName.set(s.name, s)
110
+ }
111
+ }
112
+ } catch (e) {
113
+ outputChannel.appendLine(`Skip ${filePath}: ${(e as Error).message}`)
114
+ }
115
+ })
116
+ const schemas = [...byName.values()]
117
+ if (schemas.length === 0) {
118
+ outputChannel.appendLine(`Aucun schéma trouvé dans ${schemasDir}`)
119
+ diagnosticCollection.clear()
120
+ return
121
+ }
122
+
123
+ const report = await validator.validateSchemas(schemas, {
124
+ sourceRoot: fs.existsSync(sourceRoot) ? sourceRoot : undefined,
125
+ ignore,
126
+ })
127
+ lastReport = report
128
+
129
+ // Push diagnostics
130
+ diagnosticCollection.clear()
131
+ const findingsByFile = new Map<string, any[]>()
132
+ for (const f of report.findings) {
133
+ const file = locateFindingFile(f, schemasDir, workspace.uri.fsPath)
134
+ if (!file) continue
135
+ if (!findingsByFile.has(file)) findingsByFile.set(file, [])
136
+ findingsByFile.get(file)!.push(f)
137
+ }
138
+
139
+ for (const [file, findings] of findingsByFile) {
140
+ const diagnostics: vscode.Diagnostic[] = []
141
+ for (const f of findings) {
142
+ const range = computeRange(file, f)
143
+ const diag = new vscode.Diagnostic(range, `[${f.ruleId}] ${f.message}`, severityToVSCode(f.severity))
144
+ diag.source = '@mostajs/orm-validator'
145
+ diag.code = f.ruleId
146
+ // Stockage payload pour Code Action récupérer ensuite
147
+ ;(diag as any).fixableData = f
148
+ diagnostics.push(diag)
149
+ }
150
+ diagnosticCollection.set(vscode.Uri.file(file), diagnostics)
151
+ }
152
+
153
+ outputChannel.appendLine(`✓ ${report.findings.length} findings (${report.countBySeverity.warning} warnings, ${report.countBySeverity.info} infos, ${report.countBySeverity.hint} hints) sur ${schemas.length} schémas — ${report.durationMs}ms`)
154
+ } catch (e) {
155
+ outputChannel.appendLine(`Error: ${(e as Error).stack ?? (e as Error).message}`)
156
+ }
157
+ }
158
+
159
+ // ─── Code Actions provider (quick-fix) ──────────────────────────
160
+
161
+ class ValidatorCodeActionProvider implements vscode.CodeActionProvider {
162
+ provideCodeActions(
163
+ document: vscode.TextDocument,
164
+ range: vscode.Range,
165
+ context: vscode.CodeActionContext,
166
+ ): vscode.CodeAction[] {
167
+ const actions: vscode.CodeAction[] = []
168
+ for (const diag of context.diagnostics) {
169
+ if (diag.source !== '@mostajs/orm-validator') continue
170
+ const finding = (diag as any).fixableData
171
+ if (!finding?.fixable) continue
172
+ const action = new vscode.CodeAction(
173
+ `⚙ Auto-fix [${finding.ruleId}] ${finding.location.schema}${finding.location.field ? '.' + finding.location.field : ''}`,
174
+ vscode.CodeActionKind.QuickFix,
175
+ )
176
+ action.command = {
177
+ title: 'Auto-fix',
178
+ command: 'mostajsOrmValidator.fixOne',
179
+ arguments: [finding],
180
+ }
181
+ action.diagnostics = [diag]
182
+ action.isPreferred = true
183
+ actions.push(action)
184
+ }
185
+ return actions
186
+ }
187
+ }
188
+
189
+ async function runFixOne(finding: any) {
190
+ if (!lastReport) {
191
+ vscode.window.showWarningMessage('Aucun rapport en mémoire — lance "Validate" d\'abord.')
192
+ return
193
+ }
194
+ await applyFixesFiltered([finding])
195
+ }
196
+
197
+ async function runFixAll() {
198
+ if (!lastReport) {
199
+ vscode.window.showWarningMessage('Aucun rapport en mémoire — lance "Validate" d\'abord.')
200
+ return
201
+ }
202
+ const fixable = lastReport.findings.filter((f: any) => f.fixable)
203
+ if (fixable.length === 0) {
204
+ vscode.window.showInformationMessage('Aucun finding auto-fixable dans le rapport.')
205
+ return
206
+ }
207
+ const choice = await vscode.window.showWarningMessage(
208
+ `Appliquer ${fixable.length} auto-fix ? (backup .bak créé)`,
209
+ { modal: true },
210
+ 'Apply', 'Dry-run',
211
+ )
212
+ if (!choice) return
213
+ await applyFixesFiltered(fixable, choice === 'Dry-run')
214
+ }
215
+
216
+ async function applyFixesFiltered(findings: any[], dryRun = false) {
217
+ const workspace = vscode.workspace.workspaceFolders?.[0]
218
+ if (!workspace) return
219
+
220
+ try {
221
+ const validator = await import('@mostajs/orm/validator')
222
+ const cfg = vscode.workspace.getConfiguration('mostajsOrmValidator')
223
+ const sourceRoot = path.join(workspace.uri.fsPath, cfg.get<string>('schemasDir') ?? './schemas')
224
+
225
+ // Construit un mini-rapport ciblé
226
+ const targetedReport = { ...lastReport, findings }
227
+ const results = await validator.applyFixes(targetedReport as any, {
228
+ sourceRoot,
229
+ dryRun,
230
+ backup: true,
231
+ })
232
+
233
+ const applied = results.filter(r => r.applied).length
234
+ const skipped = results.filter(r => !r.applied).length
235
+ const msg = `${dryRun ? 'Dry-run' : 'Apply'} : ${applied} fix${applied > 1 ? 'es' : ''} ${dryRun ? 'préviewés' : 'appliqués'}, ${skipped} skippés.`
236
+ outputChannel.appendLine(msg)
237
+ for (const r of results) {
238
+ outputChannel.appendLine(` ${r.applied ? '✓' : '·'} ${r.ruleId} ${r.schema}${r.field ? '.' + r.field : ''} ${r.reason ?? ''}`)
239
+ if (r.diff && dryRun) outputChannel.appendLine(r.diff)
240
+ }
241
+ vscode.window.showInformationMessage(msg + (dryRun ? '' : ' (.bak créés)'))
242
+
243
+ if (!dryRun) {
244
+ // Relance validation après fix pour rafraîchir les diagnostics
245
+ setTimeout(() => { void runValidation() }, 200)
246
+ }
247
+ } catch (e) {
248
+ outputChannel.appendLine(`Fix error: ${(e as Error).message}`)
249
+ vscode.window.showErrorMessage(`Auto-fix échec: ${(e as Error).message}`)
250
+ }
251
+ }
252
+
253
+ async function runRollback() {
254
+ const workspace = vscode.workspace.workspaceFolders?.[0]
255
+ if (!workspace) return
256
+ try {
257
+ const validator = await import('@mostajs/orm/validator')
258
+ const cfg = vscode.workspace.getConfiguration('mostajsOrmValidator')
259
+ const schemasDir = path.join(workspace.uri.fsPath, cfg.get<string>('schemasDir') ?? './schemas')
260
+ const results = validator.rollbackFixes(schemasDir)
261
+ const ok = results.filter((r) => r.restored).length
262
+ const fail = results.filter((r) => !r.restored).length
263
+ outputChannel.appendLine(`Rollback: ${ok} restored, ${fail} failed`)
264
+ for (const r of results) {
265
+ outputChannel.appendLine(` ${r.restored ? '✓' : '✗'} ${r.file}${r.reason ? ' — ' + r.reason : ''}`)
266
+ }
267
+ vscode.window.showInformationMessage(`Rollback : ${ok} fichier${ok > 1 ? 's' : ''} restauré${ok > 1 ? 's' : ''}.`)
268
+ setTimeout(() => { void runValidation() }, 200)
269
+ } catch (e) {
270
+ vscode.window.showErrorMessage(`Rollback échec: ${(e as Error).message}`)
271
+ }
272
+ }
273
+
274
+ // ─── Helpers ────────────────────────────────────────────────────
275
+
276
+ function isEnabled(): boolean {
277
+ return vscode.workspace.getConfiguration('mostajsOrmValidator').get<boolean>('enabled', true)
278
+ }
279
+
280
+ function hasSchemasDir(): boolean {
281
+ const workspace = vscode.workspace.workspaceFolders?.[0]
282
+ if (!workspace) return false
283
+ const cfg = vscode.workspace.getConfiguration('mostajsOrmValidator')
284
+ return fs.existsSync(path.join(workspace.uri.fsPath, cfg.get<string>('schemasDir') ?? './schemas'))
285
+ }
286
+
287
+ function walkSchemas(dir: string, cb: (file: string) => Promise<void> | void) {
288
+ let entries: string[]
289
+ try { entries = fs.readdirSync(dir) } catch { return }
290
+ for (const e of entries) {
291
+ if (e === 'node_modules' || e === 'dist' || e === '.next' || e === '.git') continue
292
+ const full = path.join(dir, e)
293
+ let st
294
+ try { st = fs.statSync(full) } catch { continue }
295
+ if (st.isDirectory()) walkSchemas(full, cb)
296
+ else if ((full.endsWith('.ts') || full.endsWith('.tsx')) && !full.endsWith('.d.ts')) {
297
+ void cb(full)
298
+ }
299
+ }
300
+ }
301
+
302
+ function isEntitySchema(x: any): boolean {
303
+ return x && typeof x === 'object'
304
+ && typeof x.name === 'string'
305
+ && typeof x.collection === 'string'
306
+ && x.fields && typeof x.fields === 'object'
307
+ }
308
+
309
+ function locateFindingFile(f: any, schemasDir: string, workspaceRoot: string): string | null {
310
+ if (f.location?.file) {
311
+ const abs = path.isAbsolute(f.location.file)
312
+ ? f.location.file
313
+ : path.resolve(workspaceRoot, f.location.file)
314
+ return fs.existsSync(abs) ? abs : null
315
+ }
316
+ if (f.location?.schema) {
317
+ // Cherche le fichier contenant export const <SchemaName>Schema
318
+ const re = new RegExp(`export\\s+const\\s+${escapeRegex(f.location.schema)}Schema\\s*[:=]`)
319
+ let found: string | null = null
320
+ walkSchemas(schemasDir, (file) => {
321
+ if (found) return
322
+ try {
323
+ if (re.test(fs.readFileSync(file, 'utf-8'))) found = file
324
+ } catch { /* skip */ }
325
+ })
326
+ return found
327
+ }
328
+ return null
329
+ }
330
+
331
+ function computeRange(file: string, finding: any): vscode.Range {
332
+ if (finding.location?.line) {
333
+ const line = (finding.location.line - 1) | 0
334
+ return new vscode.Range(line, 0, line, 999)
335
+ }
336
+ // Cherche la déclaration du field dans le fichier pour placer le squiggle
337
+ if (finding.location?.schema && finding.location?.field) {
338
+ try {
339
+ const content = fs.readFileSync(file, 'utf-8')
340
+ const re = new RegExp(`\\b${escapeRegex(finding.location.field)}\\s*:`, 'm')
341
+ const m = content.match(re)
342
+ if (m && m.index !== undefined) {
343
+ const before = content.slice(0, m.index)
344
+ const line = (before.match(/\n/g) ?? []).length
345
+ const col = m.index - before.lastIndexOf('\n') - 1
346
+ return new vscode.Range(line, col, line, col + finding.location.field.length)
347
+ }
348
+ } catch { /* fall through */ }
349
+ }
350
+ return new vscode.Range(0, 0, 0, 0)
351
+ }
352
+
353
+ function escapeRegex(s: string): string {
354
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
355
+ }
356
+
357
+ function severityToVSCode(sev: string): vscode.DiagnosticSeverity {
358
+ switch (sev) {
359
+ case 'error': return vscode.DiagnosticSeverity.Error
360
+ case 'warning': return vscode.DiagnosticSeverity.Warning
361
+ case 'info': return vscode.DiagnosticSeverity.Information
362
+ case 'hint': return vscode.DiagnosticSeverity.Hint
363
+ default: return vscode.DiagnosticSeverity.Information
364
+ }
365
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "out",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "sourceMap": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "out", ".vscode-test"]
17
+ }