@jxsuite/schema 5.4.0 → 5.5.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/package.json +1 -1
- package/src/guards.ts +114 -1
- package/src/project-file.ts +1 -1
package/package.json
CHANGED
package/src/guards.ts
CHANGED
|
@@ -242,7 +242,120 @@ export function isTemplateString(value?: unknown): value is string {
|
|
|
242
242
|
* because a newline after `return` is an ASI bare return.
|
|
243
243
|
*/
|
|
244
244
|
export function bodyReturnsValue(body: string): boolean {
|
|
245
|
-
return /\breturn\b[^\S\n]*(?![;}\s]|$)/.test(body);
|
|
245
|
+
return /\breturn\b[^\S\n]*(?![;}\s]|$)/.test(maskCode(body));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A function body with its comments and the contents of its strings blanked, so a pattern reads
|
|
250
|
+
* only code (`spec.md` §23.1). The result is the same length, every newline kept, so an offset in
|
|
251
|
+
* it is an offset in the body. A string keeps its quotes, so `return "x"` still returns a value,
|
|
252
|
+
* and a template literal's `${…}` keeps its contents, which are code.
|
|
253
|
+
*
|
|
254
|
+
* A scanner and not a parser, because the interpreter must answer the same way and cannot carry
|
|
255
|
+
* one. Its known limit: a regular expression literal is read as code, so a quote inside one can
|
|
256
|
+
* blank what follows it.
|
|
257
|
+
*
|
|
258
|
+
* @param body The body, already JavaScript.
|
|
259
|
+
*/
|
|
260
|
+
export function maskCode(body: string): string {
|
|
261
|
+
// Nothing can open a comment or a string: the body is all code, and most handlers are.
|
|
262
|
+
if (!/[/'"`]/u.test(body)) {
|
|
263
|
+
return body;
|
|
264
|
+
}
|
|
265
|
+
const known = masked.get(body);
|
|
266
|
+
if (known !== undefined) {
|
|
267
|
+
return known;
|
|
268
|
+
}
|
|
269
|
+
const result = scanCode(body);
|
|
270
|
+
if (masked.size >= MASKED_LIMIT) {
|
|
271
|
+
masked.clear();
|
|
272
|
+
}
|
|
273
|
+
masked.set(body, result);
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Bodies already masked. Every pattern a compile runs over a body reads it masked, and a dev server
|
|
279
|
+
* compiles the same bodies again on every change, so the scan is done once per text. Bounded,
|
|
280
|
+
* because a long session sees many texts.
|
|
281
|
+
*/
|
|
282
|
+
const masked = new Map<string, string>();
|
|
283
|
+
const MASKED_LIMIT = 2048;
|
|
284
|
+
|
|
285
|
+
/** The scan {@link maskCode} caches. */
|
|
286
|
+
function scanCode(body: string): string {
|
|
287
|
+
/* UTF-16 units, as `at` counts them. Code points (`[...body]`) made an astral character in a
|
|
288
|
+
string one element for two offsets, so the result came back short and every offset after it,
|
|
289
|
+
which is what a caller splices the body at, was off by one. */
|
|
290
|
+
const out = body.split("");
|
|
291
|
+
const blank = (at: number): void => {
|
|
292
|
+
if (out[at] !== undefined && out[at] !== "\n") {
|
|
293
|
+
out[at] = " ";
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
/** The brace depth of each template substitution still open, innermost last. */
|
|
297
|
+
const substitutions: number[] = [];
|
|
298
|
+
let inTemplate = false;
|
|
299
|
+
let at = 0;
|
|
300
|
+
while (at < body.length) {
|
|
301
|
+
const char = body.charAt(at);
|
|
302
|
+
if (inTemplate) {
|
|
303
|
+
if (char === "\\") {
|
|
304
|
+
blank(at);
|
|
305
|
+
blank(at + 1);
|
|
306
|
+
at += 2;
|
|
307
|
+
} else if (char === "`") {
|
|
308
|
+
inTemplate = false;
|
|
309
|
+
at += 1;
|
|
310
|
+
} else if (char === "$" && body.charAt(at + 1) === "{") {
|
|
311
|
+
substitutions.push(0);
|
|
312
|
+
inTemplate = false;
|
|
313
|
+
at += 2;
|
|
314
|
+
} else {
|
|
315
|
+
blank(at);
|
|
316
|
+
at += 1;
|
|
317
|
+
}
|
|
318
|
+
} else if (char === "/" && body.charAt(at + 1) === "/") {
|
|
319
|
+
while (at < body.length && body.charAt(at) !== "\n") {
|
|
320
|
+
blank(at);
|
|
321
|
+
at += 1;
|
|
322
|
+
}
|
|
323
|
+
} else if (char === "/" && body.charAt(at + 1) === "*") {
|
|
324
|
+
const end = body.indexOf("*/", at + 2);
|
|
325
|
+
const stop = end === -1 ? body.length : end + 2;
|
|
326
|
+
for (; at < stop; at += 1) {
|
|
327
|
+
blank(at);
|
|
328
|
+
}
|
|
329
|
+
} else if (char === "'" || char === '"') {
|
|
330
|
+
at += 1;
|
|
331
|
+
while (at < body.length && body.charAt(at) !== char && body.charAt(at) !== "\n") {
|
|
332
|
+
if (body.charAt(at) === "\\") {
|
|
333
|
+
blank(at);
|
|
334
|
+
at += 1;
|
|
335
|
+
}
|
|
336
|
+
blank(at);
|
|
337
|
+
at += 1;
|
|
338
|
+
}
|
|
339
|
+
at += 1;
|
|
340
|
+
} else if (char === "`") {
|
|
341
|
+
inTemplate = true;
|
|
342
|
+
at += 1;
|
|
343
|
+
} else {
|
|
344
|
+
const open = substitutions.length - 1;
|
|
345
|
+
if (open >= 0 && char === "{") {
|
|
346
|
+
substitutions[open] = (substitutions[open] ?? 0) + 1;
|
|
347
|
+
} else if (open >= 0 && char === "}") {
|
|
348
|
+
if (substitutions[open] === 0) {
|
|
349
|
+
substitutions.pop();
|
|
350
|
+
inTemplate = true;
|
|
351
|
+
} else {
|
|
352
|
+
substitutions[open] = (substitutions[open] ?? 1) - 1;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
at += 1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return out.join("");
|
|
246
359
|
}
|
|
247
360
|
|
|
248
361
|
// ─── Style guards & accessors ───────────────────────────────────────────────────
|
package/src/project-file.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type ProjectFileName = (typeof PROJECT_FILE_NAMES)[number];
|
|
|
37
37
|
* What a new project's file is called (`site-architecture.md` §2.4). Never what an existing
|
|
38
38
|
* project's is: that is whichever of {@link PROJECT_FILE_NAMES} its folder holds.
|
|
39
39
|
*/
|
|
40
|
-
export const NEW_PROJECT_FILE_NAME: ProjectFileName = "project.
|
|
40
|
+
export const NEW_PROJECT_FILE_NAME: ProjectFileName = "project.jxn";
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Whether a file name is one a project file may have.
|