@emnudge/wat-lsp 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wat-check-wasm: A standalone CLI tool for scanning WAT files for issues
|
|
5
|
+
*
|
|
6
|
+
* This tool uses the WASM build of wat-lsp to provide file validation.
|
|
7
|
+
* It mirrors the functionality of the native wat-check binary.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { resolve, dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { Parser } from 'web-tree-sitter';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Parse command line arguments
|
|
19
|
+
function parseArgs(args) {
|
|
20
|
+
const result = {
|
|
21
|
+
files: [],
|
|
22
|
+
format: 'text',
|
|
23
|
+
errorsOnly: false,
|
|
24
|
+
quiet: false,
|
|
25
|
+
help: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let i = 0;
|
|
29
|
+
while (i < args.length) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
|
|
32
|
+
if (arg === '-h' || arg === '--help') {
|
|
33
|
+
result.help = true;
|
|
34
|
+
} else if (arg === '-f' || arg === '--format') {
|
|
35
|
+
i++;
|
|
36
|
+
result.format = args[i] || 'text';
|
|
37
|
+
} else if (arg === '-e' || arg === '--errors-only') {
|
|
38
|
+
result.errorsOnly = true;
|
|
39
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
40
|
+
result.quiet = true;
|
|
41
|
+
} else if (arg === '-') {
|
|
42
|
+
// Special case: '-' means stdin
|
|
43
|
+
result.files.push(arg);
|
|
44
|
+
} else if (!arg.startsWith('-')) {
|
|
45
|
+
result.files.push(arg);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`Unknown option: ${arg}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printHelp() {
|
|
57
|
+
console.log(`wat-check-wasm - WAT file checker using WASM build
|
|
58
|
+
|
|
59
|
+
Usage: wat-check-wasm [OPTIONS] <FILES>...
|
|
60
|
+
|
|
61
|
+
Arguments:
|
|
62
|
+
<FILES> Files to check. Use '-' to read from stdin.
|
|
63
|
+
|
|
64
|
+
Options:
|
|
65
|
+
-f, --format <FORMAT> Output format: text, json, compact [default: text]
|
|
66
|
+
-e, --errors-only Only show errors (hide warnings and hints)
|
|
67
|
+
-q, --quiet Suppress all output except errors (for scripting)
|
|
68
|
+
-h, --help Print help
|
|
69
|
+
`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function severityToString(severity) {
|
|
73
|
+
switch (severity) {
|
|
74
|
+
case 1:
|
|
75
|
+
return 'error';
|
|
76
|
+
case 2:
|
|
77
|
+
return 'warning';
|
|
78
|
+
case 3:
|
|
79
|
+
return 'info';
|
|
80
|
+
case 4:
|
|
81
|
+
return 'hint';
|
|
82
|
+
default:
|
|
83
|
+
return 'unknown';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function severityToCompact(severity) {
|
|
88
|
+
switch (severity) {
|
|
89
|
+
case 1:
|
|
90
|
+
return 'E';
|
|
91
|
+
case 2:
|
|
92
|
+
return 'W';
|
|
93
|
+
case 3:
|
|
94
|
+
return 'I';
|
|
95
|
+
case 4:
|
|
96
|
+
return 'H';
|
|
97
|
+
default:
|
|
98
|
+
return '?';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function printDiagnosticsText(filename, diagnostics) {
|
|
103
|
+
for (const d of diagnostics) {
|
|
104
|
+
const line = d.range.start.line + 1;
|
|
105
|
+
const col = d.range.start.character + 1;
|
|
106
|
+
const severity = severityToString(d.severity);
|
|
107
|
+
console.log(`${filename}:${line}:${col}: ${severity}: ${d.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function printDiagnosticsCompact(filename, diagnostics) {
|
|
112
|
+
for (const d of diagnostics) {
|
|
113
|
+
const line = d.range.start.line + 1;
|
|
114
|
+
const col = d.range.start.character + 1;
|
|
115
|
+
const severity = severityToCompact(d.severity);
|
|
116
|
+
console.log(`${filename}:${line}:${col}:${severity}:${d.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function readStdin() {
|
|
121
|
+
const chunks = [];
|
|
122
|
+
for await (const chunk of process.stdin) {
|
|
123
|
+
chunks.push(chunk);
|
|
124
|
+
}
|
|
125
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Initialize WatLSP for Node.js environment
|
|
130
|
+
*/
|
|
131
|
+
async function initWatLSP() {
|
|
132
|
+
// Paths to WASM files
|
|
133
|
+
const distWasmDir = join(__dirname, '..', 'dist', 'wasm');
|
|
134
|
+
const treeSitterWasmPath = join(distWasmDir, 'tree-sitter.wasm');
|
|
135
|
+
const watLspWasmPath = join(distWasmDir, 'wat_lsp_rust_bg.wasm');
|
|
136
|
+
|
|
137
|
+
// Initialize web-tree-sitter with file:// URL for Node.js
|
|
138
|
+
await Parser.init({
|
|
139
|
+
locateFile: (file) => {
|
|
140
|
+
if (file === 'tree-sitter.wasm') {
|
|
141
|
+
return `file://${treeSitterWasmPath}`;
|
|
142
|
+
}
|
|
143
|
+
return file;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Load the wat-lsp WASM module
|
|
148
|
+
const { default: initWasm, WatLSP } = await import('../dist/wasm/wat_lsp_rust.js');
|
|
149
|
+
|
|
150
|
+
// In Node.js, we need to pass the WASM file as a buffer or URL
|
|
151
|
+
const wasmBuffer = readFileSync(watLspWasmPath);
|
|
152
|
+
await initWasm(wasmBuffer);
|
|
153
|
+
|
|
154
|
+
// Create and initialize the LSP instance
|
|
155
|
+
const lsp = new WatLSP();
|
|
156
|
+
const success = await lsp.initialize();
|
|
157
|
+
|
|
158
|
+
if (!success) {
|
|
159
|
+
throw new Error('Failed to initialize WAT LSP');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return lsp;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function main() {
|
|
166
|
+
const args = parseArgs(process.argv.slice(2));
|
|
167
|
+
|
|
168
|
+
if (args.help) {
|
|
169
|
+
printHelp();
|
|
170
|
+
process.exit(0);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (args.files.length === 0) {
|
|
174
|
+
console.error('Error: No files specified');
|
|
175
|
+
printHelp();
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Initialize the WASM LSP
|
|
180
|
+
let lsp;
|
|
181
|
+
try {
|
|
182
|
+
lsp = await initWatLSP();
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error(`Failed to initialize WASM LSP: ${e.message}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const allResults = [];
|
|
189
|
+
let totalErrors = 0;
|
|
190
|
+
let totalWarnings = 0;
|
|
191
|
+
let hadReadError = false;
|
|
192
|
+
|
|
193
|
+
for (const path of args.files) {
|
|
194
|
+
let filename;
|
|
195
|
+
let source;
|
|
196
|
+
|
|
197
|
+
if (path === '-') {
|
|
198
|
+
// Read from stdin
|
|
199
|
+
try {
|
|
200
|
+
source = await readStdin();
|
|
201
|
+
filename = '<stdin>';
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(`stdin: Failed to read: ${e.message}`);
|
|
204
|
+
hadReadError = true;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
// Read from file
|
|
209
|
+
const resolvedPath = resolve(path);
|
|
210
|
+
if (!existsSync(resolvedPath)) {
|
|
211
|
+
console.error(`${path}: File not found`);
|
|
212
|
+
hadReadError = true;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
source = readFileSync(resolvedPath, 'utf8');
|
|
217
|
+
filename = path;
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.error(`${path}: Failed to read: ${e.message}`);
|
|
220
|
+
hadReadError = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Parse and get diagnostics
|
|
226
|
+
lsp.parse(source);
|
|
227
|
+
let diagnostics = lsp.provideDiagnostics();
|
|
228
|
+
|
|
229
|
+
// Convert to array if needed (JsValue might be array-like)
|
|
230
|
+
diagnostics = Array.from(diagnostics);
|
|
231
|
+
|
|
232
|
+
// Filter errors only if requested
|
|
233
|
+
if (args.errorsOnly) {
|
|
234
|
+
diagnostics = diagnostics.filter((d) => d.severity === 1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const errorCount = diagnostics.filter((d) => d.severity === 1).length;
|
|
238
|
+
const warningCount = diagnostics.filter((d) => d.severity === 2).length;
|
|
239
|
+
|
|
240
|
+
totalErrors += errorCount;
|
|
241
|
+
totalWarnings += warningCount;
|
|
242
|
+
|
|
243
|
+
switch (args.format) {
|
|
244
|
+
case 'text':
|
|
245
|
+
if (diagnostics.length > 0) {
|
|
246
|
+
printDiagnosticsText(filename, diagnostics);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case 'compact':
|
|
250
|
+
printDiagnosticsCompact(filename, diagnostics);
|
|
251
|
+
break;
|
|
252
|
+
case 'json':
|
|
253
|
+
allResults.push({
|
|
254
|
+
file: filename,
|
|
255
|
+
diagnostics: diagnostics.map((d) => ({
|
|
256
|
+
line: d.range.start.line + 1,
|
|
257
|
+
column: d.range.start.character + 1,
|
|
258
|
+
end_line: d.range.end.line + 1,
|
|
259
|
+
end_column: d.range.end.character + 1,
|
|
260
|
+
severity: severityToString(d.severity),
|
|
261
|
+
message: d.message,
|
|
262
|
+
})),
|
|
263
|
+
error_count: errorCount,
|
|
264
|
+
warning_count: warningCount,
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// JSON output at the end
|
|
271
|
+
if (args.format === 'json') {
|
|
272
|
+
const output = {
|
|
273
|
+
files: allResults,
|
|
274
|
+
summary: {
|
|
275
|
+
total_errors: totalErrors,
|
|
276
|
+
total_warnings: totalWarnings,
|
|
277
|
+
files_checked: args.files.length,
|
|
278
|
+
},
|
|
279
|
+
};
|
|
280
|
+
console.log(JSON.stringify(output, null, 2));
|
|
281
|
+
} else if (!args.quiet) {
|
|
282
|
+
// Summary for text output
|
|
283
|
+
if (args.files.length > 1 || totalErrors > 0 || totalWarnings > 0) {
|
|
284
|
+
const fileWord = args.files.length === 1 ? 'file' : 'files';
|
|
285
|
+
console.error(
|
|
286
|
+
`\nChecked ${args.files.length} ${fileWord}: ${totalErrors} error(s), ${totalWarnings} warning(s)`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Exit code: 1 if errors found, 2 if read errors, 0 if clean
|
|
292
|
+
if (totalErrors > 0) {
|
|
293
|
+
process.exit(1);
|
|
294
|
+
} else if (hadReadError) {
|
|
295
|
+
process.exit(2);
|
|
296
|
+
} else {
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
main().catch((e) => {
|
|
302
|
+
console.error(`Fatal error: ${e.message}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
@@ -30,7 +30,7 @@ export class WatLSP {
|
|
|
30
30
|
*/
|
|
31
31
|
provideReferences(line: number, col: number, include_declaration: boolean): any;
|
|
32
32
|
/**
|
|
33
|
-
* Get diagnostics (syntax errors) for the current document
|
|
33
|
+
* Get diagnostics (syntax and semantic errors) for the current document
|
|
34
34
|
*/
|
|
35
35
|
provideDiagnostics(): any;
|
|
36
36
|
/**
|
|
@@ -320,7 +320,7 @@ export class WatLSP {
|
|
|
320
320
|
return ret;
|
|
321
321
|
}
|
|
322
322
|
/**
|
|
323
|
-
* Get diagnostics (syntax errors) for the current document
|
|
323
|
+
* Get diagnostics (syntax and semantic errors) for the current document
|
|
324
324
|
* @returns {any}
|
|
325
325
|
*/
|
|
326
326
|
provideDiagnostics() {
|
|
@@ -564,6 +564,10 @@ function __wbg_get_imports() {
|
|
|
564
564
|
const ret = result;
|
|
565
565
|
return ret;
|
|
566
566
|
};
|
|
567
|
+
imports.wbg.__wbg_isMissing_bca0753051acc9d6 = function(arg0) {
|
|
568
|
+
const ret = arg0.isMissing;
|
|
569
|
+
return ret;
|
|
570
|
+
};
|
|
567
571
|
imports.wbg.__wbg_length_22ac23eaec9d8053 = function(arg0) {
|
|
568
572
|
const ret = arg0.length;
|
|
569
573
|
return ret;
|
|
@@ -731,8 +735,8 @@ function __wbg_get_imports() {
|
|
|
731
735
|
const ret = arg0;
|
|
732
736
|
return ret;
|
|
733
737
|
};
|
|
734
|
-
imports.wbg.
|
|
735
|
-
// Cast intrinsic for `Closure(Closure { dtor_idx:
|
|
738
|
+
imports.wbg.__wbindgen_cast_dd223879213a40a2 = function(arg0, arg1) {
|
|
739
|
+
// Cast intrinsic for `Closure(Closure { dtor_idx: 732, function: Function { arguments: [Externref], shim_idx: 733, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
736
740
|
const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hdb18b2b625d87f9d, wasm_bindgen__convert__closures_____invoke__hfee0e13a54da0cc9);
|
|
737
741
|
return ret;
|
|
738
742
|
};
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emnudge/wat-lsp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "WebAssembly Text Format (WAT) Language Server - WASM build for browser and Node.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"wat-check-wasm": "./bin/wat-check-wasm.js"
|
|
10
|
+
},
|
|
8
11
|
"exports": {
|
|
9
12
|
".": {
|
|
10
13
|
"types": "./dist/index.d.ts",
|
|
@@ -16,7 +19,8 @@
|
|
|
16
19
|
}
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"bin"
|
|
20
24
|
],
|
|
21
25
|
"scripts": {
|
|
22
26
|
"build:wasm": "cd ../.. && wasm-pack build --target web --no-opt --no-pack -- --features wasm --no-default-features && mkdir -p packages/wat-lsp/dist/wasm && cp pkg/wat_lsp_rust.js pkg/wat_lsp_rust.d.ts pkg/wat_lsp_rust_bg.wasm packages/wat-lsp/dist/wasm/",
|