-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathplugin.js
More file actions
270 lines (229 loc) · 8.27 KB
/
plugin.js
File metadata and controls
270 lines (229 loc) · 8.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import { plugin } from "bun";
import {theme} from './utils.js';
import * as compiler from 'imba/compiler'
import dir from 'path'
import fs from 'fs'
import { Glob } from "bun";
import { unlink } from "node:fs/promises";
export const cache = dir.join(process.cwd(), '.cache')
if (!fs.existsSync(cache)){ fs.mkdirSync(cache);}
// this should be reset from outside to get results of entrypoint building
export let stats = {
failed: 0,
compiled: 0,
cached: 0,
bundled: 0,
errors: 0,
reported: 0,
};
const _activeCompileErrors = new Map();
function normalizeCompilePath(filepath) {
let value = String(filepath || '').split(/[?#]/)[0];
if (dir.isAbsolute(value)) {
const rel = dir.relative(process.cwd(), value);
if (!rel.startsWith('..')) value = rel;
}
return value.replaceAll('\\', '/');
}
function physicalCompileKey(filepath) {
try {
const stat = fs.statSync(filepath);
if (!stat.isFile()) return null;
const real = fs.realpathSync(filepath).replaceAll('\\', '/');
return `fs:${stat.dev}:${stat.ino}:${real}`;
} catch(_) {
return null;
}
}
function compileErrorKey(filepath) {
return physicalCompileKey(filepath) || `path:${normalizeCompilePath(filepath)}`;
}
function compileFileStamp(filepath) {
try {
const stat = fs.statSync(filepath);
if (!stat.isFile()) return null;
return `${stat.mtimeMs ?? stat.mtime?.getTime?.() ?? 0}_${stat.size ?? 0}`;
} catch(_) {
return null;
}
}
function compileErrorMessage(error) {
return error?.message || String(error);
}
function compileErrorLine(error) {
return error?.range?.start?.line ?? error?.line ?? '';
}
function compileErrorSnippet(error) {
try {
return error?.toSnippet?.() || error?.snippet || error?.stack || compileErrorMessage(error);
} catch(_) {
return error?.snippet || error?.stack || compileErrorMessage(error);
}
}
function compileErrorSignature(errors) {
return errors
.map(error => [compileErrorMessage(error), compileErrorLine(error)].join('\n'))
.join('\n---\n');
}
function shouldPrintCompileError(filepath, errors) {
const key = compileErrorKey(filepath);
const signature = compileErrorSignature(errors);
const previous = _activeCompileErrors.get(key);
_activeCompileErrors.set(key, { file: normalizeCompilePath(filepath), signature, time: Date.now() });
return previous?.signature !== signature;
}
function clearCompileError(filepath) {
_activeCompileErrors.delete(compileErrorKey(filepath));
}
function printCompileError(error) {
try {
printerr(error);
} catch(_) {
console.log('');
console.log(' ' + theme.error(' ' + compileErrorMessage(error) + ' '));
console.log('');
}
}
// Target platform for the Imba compiler: 'browser' or 'node'
// Set via setTarget() from the CLI before building
export let target = 'browser';
export function setTarget(t) { target = t; }
export const imbaPlugin = {
name: "imba",
async setup(build) {
// when an .imba file is imported...
build.onLoad({ filter: /\.imba$/ }, async ({ path }) => {
const f = dir.parse(path)
while (true) {
const stamp = compileFileStamp(path);
if (!stamp) {
clearCompileError(path);
return { contents: '', loader: "js" };
}
let contents = '';
// return the cached version if exists (include target in hash to avoid cross-platform cache hits)
const cached = dir.join(cache, Bun.hash(path + ':' + target) + '_' + stamp + '.js');
if (fs.existsSync(cached)) {
const cachedContents = await Bun.file(cached).text();
if (compileFileStamp(path) !== stamp) continue;
clearCompileError(path);
stats.bundled++;
stats.cached++;
return {
contents: cachedContents,
loader: "js",
};
}
// clear previous cached version
const glob = new Glob(Bun.hash(path + ':' + target) + '_' + "*.js");
for await (const file of glob.scan(cache)) if (fs.existsSync(dir.join(cache, file))) unlink(dir.join(cache, file));
// if no cached version read and compile it with the imba compiler
const file = await Bun.file(path).text();
if (compileFileStamp(path) !== stamp) continue;
const platform = target === 'node' || target === 'bun' ? 'node' : 'browser';
let out
try {
out = compiler.compile(file, {
sourcePath: path,
platform: platform,
comments: false
})
} catch (error) {
out = { js: '', errors: [error] }
}
// Never report or cache a result from an older source snapshot.
if (compileFileStamp(path) !== stamp) continue;
// the file has been successfully compiled
if (!out.errors?.length) {
clearCompileError(path);
console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.success("compiled"));
stats.bundled++;
stats.compiled++;
contents = out.js;
await Bun.write(cached, contents);
}
// there were errors during compilation
else {
const shouldPrint = shouldPrintCompileError(path, out.errors);
if (shouldPrint) console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.failure(" fail "));
stats.failed++;
if (shouldPrint) {
stats.reported++;
for (let i = 0; i < out.errors.length; i++) {
if(out.errors[i]) printCompileError(out.errors[i]);
}
}
stats.errors++;
}
// and return the compiled source code as "js"
return {
contents,
loader: "js",
};
}
});
}
};
plugin(imbaPlugin);
// -------------------------------------------------------------------------------
// print pretty messages produced by the imba compiler
// -------------------------------------------------------------------------------
// print an error generated by the imba compiler
export function printerr(err) {
// halper function to produce empty strings
const fill = (len = 0) => {return new Array(len + 1).join(' ')}
// gather the needed information from the compiler error
const snippet = err.toSnippet().split("\n");
const errs = snippet[2] ? snippet[2].indexOf('^') : -1;
// no source context available — print compact fallback
if (!snippet[1] || errs === -1) {
console.log('');
console.log(fill(10) + theme.error(" " + err.message + " "));
console.log('');
return;
}
const display = {
error: " " + err.message + " ",
outdent: fill(10),
source: snippet[1] + " ",
margin: " line " + (err.range.start.line + 1) + " ",
errs: errs,
erre: snippet[2].lastIndexOf('^') + 1,
};
// calculate parameters for priniting a message
const center = display.margin.length + display.errs + Math.floor((display.erre - display.errs) / 2);
const half = Math.ceil((display.error.length - 1) / 2);
const start = Math.max(0, center - half);
const end = start + display.error.length;
const total = Math.max(display.margin.length + display.source.length, end);
// print emtpy line
console.log('');
// print line with the error message
console.log(
display.outdent +
theme.margin(fill(Math.min(start, display.margin.length))) +
theme.code(fill(Math.max(0, start - display.margin.length))) +
theme.error(display.error) +
theme.margin(fill(Math.max(0, display.margin.length - end))) +
theme.code(fill(Math.min(total - display.margin.length, total - end)))
);
// print line with the source code
console.log(
display.outdent +
theme.margin(display.margin) +
theme.code(display.source.slice(0,display.errs)) +
theme.error(display.source.slice(display.errs,display.erre)) +
theme.code(display.source.slice(display.erre)) +
theme.code(fill(total - display.source.length - display.margin.length))
);
// print empty line to balance the view
// later we can put something usefull here
// for example a link to online docs about the error
console.log(
display.outdent +
theme.margin(fill(display.margin.length)) +
theme.code(fill(total - display.margin.length))
);
// print emtpy line
console.log('');
}