@dogsbay/literal 0.2.0-beta.132 → 0.2.0-beta.134
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 +17 -4
- package/dist/index.d.ts +15 -2
- package/dist/index.js +196 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,11 +11,24 @@ const { value, unresolved } = evaluateModule(source); // the module's default ex
|
|
|
11
11
|
|
|
12
12
|
It understands object, array, string, number, boolean and null literals; template strings
|
|
13
13
|
with no `${}`; spreads; module-level `const` references; zero-argument helpers that return a
|
|
14
|
-
literal; wrapper calls such as `defineConfig({…})`; member access
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
literal; wrapper calls such as `defineConfig({…})`; member access and literal indexes
|
|
15
|
+
(`releases[0]["x"]`); `"a" + "b"` (strings only); `module.exports =`; and TypeScript type
|
|
16
|
+
noise. Anything else becomes `UNRESOLVED` and is listed in `unresolved`: never a plausible
|
|
17
|
+
wrong value, never a silent omission.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
Other modules (`require("./x")`, `import x from "./x"`) are read only through a resolver you
|
|
20
|
+
pass, which decides what a path means and what may be read:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
evaluateModule(source, "sidebars", { require: (spec) => readModule(spec) }); // value or undefined
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Someone else's code can be hostile, so there are limits: a value that expands past 100,000
|
|
27
|
+
entries (shared `const`s doubling, required modules included), a string past 1 MB, or nesting
|
|
28
|
+
past 500 levels is `UNRESOLVED`; a `__proto__` key is data, never a prototype.
|
|
29
|
+
|
|
30
|
+
Used by the Dogsbay VitePress importer (`.vitepress/config.*`) and Docusaurus importer
|
|
31
|
+
(`sidebars*.js`, `variables.js`).
|
|
19
32
|
|
|
20
33
|
## License
|
|
21
34
|
|
package/dist/index.d.ts
CHANGED
|
@@ -18,8 +18,13 @@
|
|
|
18
18
|
* - zero-arg calls to module-level `function f() { return <literal> }` and
|
|
19
19
|
* `const f = () => <literal>` (hono's `sidebar: { '/': sidebars() }`)
|
|
20
20
|
* - wrapper calls with a single literal argument (`defineConfig({…})`)
|
|
21
|
-
* - member access into an already-evaluated object (`shared.themeConfig`)
|
|
21
|
+
* - member access into an already-evaluated object (`shared.themeConfig`), and a LITERAL
|
|
22
|
+
* index (`releases[0]`, `x["key"]`); a computed index (`nav[i]`) is unresolved
|
|
22
23
|
* - TS noise: type annotations, `as const`, `as T`, `satisfies T`, generics
|
|
24
|
+
* - CommonJS: `module.exports = <expr>` is the default export
|
|
25
|
+
* - `require("./x")` and `import x from "./x"`, through a resolver the CALLER supplies
|
|
26
|
+
* (`EvaluateOptions.require`): this module reads no files and decides no paths
|
|
27
|
+
* - `"a" + "b"`: concatenation of two strings, nothing else (camunda's `root_dir + "x"`)
|
|
23
28
|
*
|
|
24
29
|
* Anything else evaluates to the `UNRESOLVED` sentinel — never to a plausible
|
|
25
30
|
* wrong value and never to a silent omission. Callers report what they could
|
|
@@ -40,6 +45,14 @@ export interface EvalResult {
|
|
|
40
45
|
/** Human-readable descriptions of expressions that could not be read. */
|
|
41
46
|
unresolved: string[];
|
|
42
47
|
}
|
|
48
|
+
export interface EvaluateOptions {
|
|
49
|
+
/**
|
|
50
|
+
* The default export of the module `spec` names (`require("./x")`, `import x from "./x"`),
|
|
51
|
+
* or undefined when there is none. The caller resolves the path, decides what may be read,
|
|
52
|
+
* and bounds how deep modules nest; reading a module is the caller's call, never this one's.
|
|
53
|
+
*/
|
|
54
|
+
require?: (spec: string) => LiteralValue | undefined;
|
|
55
|
+
}
|
|
43
56
|
/**
|
|
44
57
|
* Evaluate a module's default export as far as the literal subset allows.
|
|
45
58
|
*
|
|
@@ -47,7 +60,7 @@ export interface EvalResult {
|
|
|
47
60
|
* there is no `export default`, which covers
|
|
48
61
|
* `const config = { … }; export { config }`.
|
|
49
62
|
*/
|
|
50
|
-
export declare function evaluateModule(source: string, fallbackExport?: string): EvalResult;
|
|
63
|
+
export declare function evaluateModule(source: string, fallbackExport?: string, opts?: EvaluateOptions): EvalResult;
|
|
51
64
|
/** Narrowing helper: a plain object with no UNRESOLVED identity. */
|
|
52
65
|
export declare function asObject(v: LiteralValue): Record<string, LiteralValue> | undefined;
|
|
53
66
|
export declare function asArray(v: LiteralValue): LiteralValue[] | undefined;
|
package/dist/index.js
CHANGED
|
@@ -18,8 +18,13 @@
|
|
|
18
18
|
* - zero-arg calls to module-level `function f() { return <literal> }` and
|
|
19
19
|
* `const f = () => <literal>` (hono's `sidebar: { '/': sidebars() }`)
|
|
20
20
|
* - wrapper calls with a single literal argument (`defineConfig({…})`)
|
|
21
|
-
* - member access into an already-evaluated object (`shared.themeConfig`)
|
|
21
|
+
* - member access into an already-evaluated object (`shared.themeConfig`), and a LITERAL
|
|
22
|
+
* index (`releases[0]`, `x["key"]`); a computed index (`nav[i]`) is unresolved
|
|
22
23
|
* - TS noise: type annotations, `as const`, `as T`, `satisfies T`, generics
|
|
24
|
+
* - CommonJS: `module.exports = <expr>` is the default export
|
|
25
|
+
* - `require("./x")` and `import x from "./x"`, through a resolver the CALLER supplies
|
|
26
|
+
* (`EvaluateOptions.require`): this module reads no files and decides no paths
|
|
27
|
+
* - `"a" + "b"`: concatenation of two strings, nothing else (camunda's `root_dir + "x"`)
|
|
23
28
|
*
|
|
24
29
|
* Anything else evaluates to the `UNRESOLVED` sentinel — never to a plausible
|
|
25
30
|
* wrong value and never to a silent omission. Callers report what they could
|
|
@@ -244,15 +249,106 @@ const CLOSERS = new Set(["}", "]", ")"]);
|
|
|
244
249
|
* 1 KB config), and spreads (`[...a, ...a]`) copy for real.
|
|
245
250
|
*/
|
|
246
251
|
const MAX_VALUES = 100_000;
|
|
252
|
+
/** The longest string `+` may build: `const b = a + a` doubles, thirty times is a gigabyte. */
|
|
253
|
+
const MAX_STRING = 1024 * 1024;
|
|
254
|
+
/** How deeply values may nest: past this a parser or a consumer walking it runs out of stack. */
|
|
255
|
+
const MAX_DEPTH = 500;
|
|
256
|
+
/**
|
|
257
|
+
* Assign a key the way a literal does: `{ "__proto__": x }` is an own property. Plain
|
|
258
|
+
* assignment would set the object's prototype instead, so a sidebar item could inherit a
|
|
259
|
+
* `type` or `items` that no key in the source gave it.
|
|
260
|
+
*/
|
|
261
|
+
function setKey(out, key, value) {
|
|
262
|
+
if (key === "__proto__")
|
|
263
|
+
Object.defineProperty(out, key, { value, enumerable: true, writable: true, configurable: true });
|
|
264
|
+
else
|
|
265
|
+
out[key] = value;
|
|
266
|
+
}
|
|
247
267
|
class Evaluator {
|
|
268
|
+
opts;
|
|
248
269
|
toks;
|
|
249
270
|
bindings = new Map();
|
|
250
271
|
/** Each array or object built here → the values it expands to, itself included. */
|
|
251
272
|
sizes = new WeakMap();
|
|
273
|
+
depth = 0;
|
|
252
274
|
unresolved = [];
|
|
275
|
+
constructor(src, opts = {}) {
|
|
276
|
+
this.opts = opts;
|
|
277
|
+
this.toks = tokenize(src);
|
|
278
|
+
this.collectBindings();
|
|
279
|
+
this.reportMutations();
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* A top-level statement that changes a binding after it is defined — `module.exports.y = 2`,
|
|
283
|
+
* `exports.y = 2`, `s.items = […]`, `s.items.push(…)` — is not read: the value is read as
|
|
284
|
+
* declared, so the change is REPORTED, never silently missing (review 2026-10-21).
|
|
285
|
+
*/
|
|
286
|
+
reportMutations() {
|
|
287
|
+
let depth = 0;
|
|
288
|
+
for (let i = 0; i < this.toks.length; i++) {
|
|
289
|
+
const t = this.toks[i];
|
|
290
|
+
if (t.kind === "punct" && OPENERS[t.text])
|
|
291
|
+
depth++;
|
|
292
|
+
else if (t.kind === "punct" && CLOSERS.has(t.text))
|
|
293
|
+
depth--;
|
|
294
|
+
if (depth !== 0 || t.kind !== "ident" || !(this.is(i + 1, ".") || this.is(i + 1, "[")))
|
|
295
|
+
continue;
|
|
296
|
+
const prev = this.toks[i - 1];
|
|
297
|
+
// The start of a statement: the file's start, after `;` or `}`, or on a new line
|
|
298
|
+
if (prev && !(prev.kind === "punct" && (prev.text === ";" || prev.text === "}")) && prev.line === t.line)
|
|
299
|
+
continue;
|
|
300
|
+
if (prev && prev.kind === "punct" && prev.text === ".")
|
|
301
|
+
continue;
|
|
302
|
+
const name = t.text;
|
|
303
|
+
const known = name === "exports" || name === "module" || this.bindings.has(name);
|
|
304
|
+
if (!known)
|
|
305
|
+
continue;
|
|
306
|
+
if (name === "module" && this.is(i + 2, "exports") && this.is(i + 3, "="))
|
|
307
|
+
continue; // the export itself
|
|
308
|
+
this.report(i, `a statement that changes \`${name}\` after it is defined (read as declared, without the change)`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/** What `v` costs against MAX_VALUES. A long string costs more than one value. */
|
|
253
312
|
sizeOf(v) {
|
|
313
|
+
if (typeof v === "string")
|
|
314
|
+
return 1 + (v.length >> 10);
|
|
254
315
|
return v !== null && typeof v === "object" ? (this.sizes.get(v) ?? 1) : 1;
|
|
255
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* The size of a value this evaluator did not build (another module's), measured once per
|
|
319
|
+
* object: a module required twice is shared, not copied, and counts as what it expands to.
|
|
320
|
+
* MAX_VALUES + 1 for anything over the budget or nested past MAX_DEPTH.
|
|
321
|
+
*/
|
|
322
|
+
measure(v, depth = 0) {
|
|
323
|
+
if (v === null || typeof v !== "object")
|
|
324
|
+
return this.sizeOf(v);
|
|
325
|
+
const known = this.sizes.get(v);
|
|
326
|
+
if (known !== undefined)
|
|
327
|
+
return known;
|
|
328
|
+
if (depth > MAX_DEPTH)
|
|
329
|
+
return MAX_VALUES + 1;
|
|
330
|
+
let size = 1;
|
|
331
|
+
for (const c of Array.isArray(v) ? v : Object.values(v)) {
|
|
332
|
+
size += this.measure(c, depth + 1);
|
|
333
|
+
if (size > MAX_VALUES)
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
this.sizes.set(v, size);
|
|
337
|
+
return size;
|
|
338
|
+
}
|
|
339
|
+
/** `require(spec)` / `import … from spec`, through the caller's resolver, within budget. */
|
|
340
|
+
required(spec, i) {
|
|
341
|
+
const value = this.opts.require?.(spec);
|
|
342
|
+
if (value === undefined || value === UNRESOLVED) {
|
|
343
|
+
this.report(i, `module "${spec}" ${this.opts.require ? "could not be read" : "(no module resolver)"}`);
|
|
344
|
+
return UNRESOLVED;
|
|
345
|
+
}
|
|
346
|
+
if (this.measure(value) > MAX_VALUES) {
|
|
347
|
+
this.report(i, `module "${spec}": a value that expands to more than ${MAX_VALUES} entries or nests past ${MAX_DEPTH} levels`);
|
|
348
|
+
return UNRESOLVED;
|
|
349
|
+
}
|
|
350
|
+
return value;
|
|
351
|
+
}
|
|
256
352
|
/** `out` when it expands to at most MAX_VALUES values, else UNRESOLVED and a report. */
|
|
257
353
|
bounded(out, size, i) {
|
|
258
354
|
if (size > MAX_VALUES) {
|
|
@@ -262,10 +358,6 @@ class Evaluator {
|
|
|
262
358
|
this.sizes.set(out, size);
|
|
263
359
|
return out;
|
|
264
360
|
}
|
|
265
|
-
constructor(src) {
|
|
266
|
-
this.toks = tokenize(src);
|
|
267
|
-
this.collectBindings();
|
|
268
|
-
}
|
|
269
361
|
tok(i) {
|
|
270
362
|
return this.toks[i];
|
|
271
363
|
}
|
|
@@ -406,6 +498,23 @@ class Evaluator {
|
|
|
406
498
|
this.bindings.set("default", { at: i + 2 });
|
|
407
499
|
continue;
|
|
408
500
|
}
|
|
501
|
+
// CommonJS `module.exports = <expr>` is the default export (Docusaurus sidebars).
|
|
502
|
+
if (t.text === "module" && this.is(i + 1, ".") && this.is(i + 2, "exports") && this.is(i + 3, "=")) {
|
|
503
|
+
this.bindings.set("default", { at: i + 4 });
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
// `import x from "spec"` (and the default of `import x, { … } from "spec"`). Named and
|
|
507
|
+
// namespace imports are not read: an identifier from one is reported where it is used.
|
|
508
|
+
if (t.text === "import" && this.toks[i + 1]?.kind === "ident" && !this.is(i + 1, "type")) {
|
|
509
|
+
const name = this.toks[i + 1].text;
|
|
510
|
+
let k = i + 2;
|
|
511
|
+
if (this.is(k, ","))
|
|
512
|
+
k = this.skipBalanced(k + 1);
|
|
513
|
+
const spec = this.toks[k + 1];
|
|
514
|
+
if (this.is(k, "from") && spec?.kind === "str")
|
|
515
|
+
this.bindings.set(name, { at: k + 1, from: spec.text });
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
409
518
|
// Skip the `export` modifier so `export const x = …` is indexed too.
|
|
410
519
|
const j = t.text === "export" ? i + 1 : i;
|
|
411
520
|
const kw = this.tok(j);
|
|
@@ -504,7 +613,7 @@ class Evaluator {
|
|
|
504
613
|
if (b.value !== undefined)
|
|
505
614
|
return b.value;
|
|
506
615
|
b.computing = true;
|
|
507
|
-
const v = this.parseValue(b.at).value;
|
|
616
|
+
const v = b.from !== undefined ? this.required(b.from, b.at) : this.parseValue(b.at).value;
|
|
508
617
|
b.computing = false;
|
|
509
618
|
b.value = v;
|
|
510
619
|
return v;
|
|
@@ -554,6 +663,19 @@ class Evaluator {
|
|
|
554
663
|
}
|
|
555
664
|
/** Parse the expression at `i`. Returns its value and the next index. */
|
|
556
665
|
parseValue(i) {
|
|
666
|
+
if (this.depth >= MAX_DEPTH) {
|
|
667
|
+
this.report(i, `a value nested more than ${MAX_DEPTH} levels deep`);
|
|
668
|
+
return { value: UNRESOLVED, next: this.skipValue(i) };
|
|
669
|
+
}
|
|
670
|
+
this.depth++;
|
|
671
|
+
try {
|
|
672
|
+
return this.parseValueAt(i);
|
|
673
|
+
}
|
|
674
|
+
finally {
|
|
675
|
+
this.depth--;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
parseValueAt(i) {
|
|
557
679
|
const t = this.toks[i];
|
|
558
680
|
if (!t)
|
|
559
681
|
return { value: UNRESOLVED, next: i };
|
|
@@ -658,6 +780,14 @@ class Evaluator {
|
|
|
658
780
|
return { value: UNRESOLVED, next: this.skipValue(i) };
|
|
659
781
|
}
|
|
660
782
|
const after = this.skipBalanced(open);
|
|
783
|
+
// `require("./x")`: one string argument, nothing else
|
|
784
|
+
if (name === "require" && !this.bindings.has("require")) {
|
|
785
|
+
const spec = this.toks[open + 1];
|
|
786
|
+
if (spec?.kind === "str" && this.is(open + 2, ")"))
|
|
787
|
+
return this.postfix(this.required(spec.text, i), after);
|
|
788
|
+
this.report(i, "`require` of something other than one string");
|
|
789
|
+
return { value: UNRESOLVED, next: after };
|
|
790
|
+
}
|
|
661
791
|
// Zero args → the callee's own literal body.
|
|
662
792
|
if (this.is(open + 1, ")")) {
|
|
663
793
|
if (this.bindings.has(name)) {
|
|
@@ -717,6 +847,13 @@ class Evaluator {
|
|
|
717
847
|
out.push(value);
|
|
718
848
|
size += this.sizeOf(value);
|
|
719
849
|
k = next === k ? k + 1 : next;
|
|
850
|
+
// Elements are separated by commas. Whatever else follows a value was not read as part
|
|
851
|
+
// of it (review 2026-10-21: `.concat("x")`'s argument became an element of its own)
|
|
852
|
+
if (k < this.toks.length && !this.is(k, ",") && !this.is(k, "]")) {
|
|
853
|
+
this.report(k, `unexpected \`${this.toks[k].text}\` after an array element`);
|
|
854
|
+
out[out.length - 1] = UNRESOLVED;
|
|
855
|
+
k = this.advance(k);
|
|
856
|
+
}
|
|
720
857
|
}
|
|
721
858
|
return this.postfix(this.bounded(out, size, i), k + 1);
|
|
722
859
|
}
|
|
@@ -732,7 +869,8 @@ class Evaluator {
|
|
|
732
869
|
const { value, next } = this.parseValue(k + 1);
|
|
733
870
|
// `typeof x === "object"` already excludes the UNRESOLVED symbol.
|
|
734
871
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
735
|
-
Object.
|
|
872
|
+
for (const [key, v] of Object.entries(value))
|
|
873
|
+
setKey(out, key, v);
|
|
736
874
|
}
|
|
737
875
|
else if (value !== UNRESOLVED) {
|
|
738
876
|
this.report(k, "spread of a non-object");
|
|
@@ -763,7 +901,7 @@ class Evaluator {
|
|
|
763
901
|
}
|
|
764
902
|
if (this.is(k, ":")) {
|
|
765
903
|
const v = this.parseValue(k + 1);
|
|
766
|
-
out
|
|
904
|
+
setKey(out, key, v.value);
|
|
767
905
|
k = v.next;
|
|
768
906
|
}
|
|
769
907
|
continue;
|
|
@@ -782,14 +920,19 @@ class Evaluator {
|
|
|
782
920
|
}
|
|
783
921
|
if (!this.is(k, ":")) {
|
|
784
922
|
// Shorthand `{ foo }`.
|
|
785
|
-
out
|
|
923
|
+
setKey(out, key, this.bindings.has(key) ? this.resolveBinding(key) : UNRESOLVED);
|
|
786
924
|
if (!this.bindings.has(key))
|
|
787
925
|
this.report(k, `shorthand property \`${key}\``);
|
|
788
926
|
continue;
|
|
789
927
|
}
|
|
790
928
|
const v = this.parseValue(k + 1);
|
|
791
|
-
out
|
|
929
|
+
setKey(out, key, v.value);
|
|
792
930
|
k = v.next === k + 1 ? this.skipValue(k + 1) : v.next;
|
|
931
|
+
if (k < this.toks.length && !this.is(k, ",") && !this.is(k, "}") && !this.is(k, ";")) {
|
|
932
|
+
this.report(k, `unexpected \`${this.toks[k].text}\` after property \`${key}\``);
|
|
933
|
+
setKey(out, key, UNRESOLVED);
|
|
934
|
+
k = this.advance(k);
|
|
935
|
+
}
|
|
793
936
|
}
|
|
794
937
|
let size = 1;
|
|
795
938
|
for (const v of Object.values(out))
|
|
@@ -812,12 +955,36 @@ class Evaluator {
|
|
|
812
955
|
k++;
|
|
813
956
|
continue;
|
|
814
957
|
}
|
|
958
|
+
// A call on a value (`a.concat(…)`, `"ab"("y")`): its result is computed. The arguments
|
|
959
|
+
// are skipped with it, or they were read as the next element of the enclosing list.
|
|
960
|
+
if (this.is(k, "(")) {
|
|
961
|
+
this.report(k, "a call on a value (computed, not a literal)");
|
|
962
|
+
v = UNRESOLVED;
|
|
963
|
+
k = this.skipBalanced(k);
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
// `x[0]`, `x["key"]`: a literal index into a value we have. The index is read, never
|
|
967
|
+
// computed: `nav[i]` stays unresolved (below), and so does a key the value does not own.
|
|
968
|
+
if (this.is(k, "[") && (this.toks[k + 1]?.kind === "num" || this.toks[k + 1]?.kind === "str") && this.is(k + 2, "]")) {
|
|
969
|
+
const index = this.toks[k + 1].kind === "num" ? Number(this.toks[k + 1].text) : this.toks[k + 1].text;
|
|
970
|
+
const own = Array.isArray(v) ? (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < v.length ? v[index] : undefined)
|
|
971
|
+
: v !== null && typeof v === "object" && Object.hasOwn(v, String(index)) ? v[String(index)]
|
|
972
|
+
: undefined;
|
|
973
|
+
if (own === undefined) {
|
|
974
|
+
this.report(k, `index \`[${this.toks[k + 1].text}]\` into a value that does not have it`);
|
|
975
|
+
v = UNRESOLVED;
|
|
976
|
+
}
|
|
977
|
+
else
|
|
978
|
+
v = own;
|
|
979
|
+
k += 3;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
815
982
|
if ((this.is(k, ".") || this.is(k, "?.")) && this.toks[k + 1]?.kind === "ident") {
|
|
816
983
|
const prop = this.toks[k + 1].text;
|
|
817
984
|
const obj = v && typeof v === "object" && !Array.isArray(v)
|
|
818
985
|
? v
|
|
819
986
|
: undefined;
|
|
820
|
-
if (!obj || obj
|
|
987
|
+
if (!obj || !Object.hasOwn(obj, prop)) {
|
|
821
988
|
// Includes every METHOD call — `.map(…)`, `.concat(…)`. Reporting
|
|
822
989
|
// matters: `config.ts` drops UNRESOLVED leaves, so an unreported
|
|
823
990
|
// one vanishes from the config AND from the "could not read" list,
|
|
@@ -833,6 +1000,22 @@ class Evaluator {
|
|
|
833
1000
|
}
|
|
834
1001
|
break;
|
|
835
1002
|
}
|
|
1003
|
+
// `"a" + "b"`: two strings and nothing else. The right side is read as a value, so
|
|
1004
|
+
// `a + b + c` folds from the right, which for strings is the same thing; a number on either
|
|
1005
|
+
// side (`1 + 2 + "x"` is "3x", not "12x") or any other operator after it is unresolved.
|
|
1006
|
+
if (typeof v === "string" && this.is(k, "+") && this.toks[k]?.kind === "punct") {
|
|
1007
|
+
const right = this.parseValue(k + 1);
|
|
1008
|
+
if (typeof right.value !== "string") {
|
|
1009
|
+
if (right.value !== UNRESOLVED)
|
|
1010
|
+
this.report(k, "`+` with something other than a string");
|
|
1011
|
+
return { value: UNRESOLVED, next: right.next };
|
|
1012
|
+
}
|
|
1013
|
+
if (v.length + right.value.length > MAX_STRING) {
|
|
1014
|
+
this.report(k, `a string longer than ${MAX_STRING} characters`);
|
|
1015
|
+
return { value: UNRESOLVED, next: right.next };
|
|
1016
|
+
}
|
|
1017
|
+
return { value: v + right.value, next: right.next };
|
|
1018
|
+
}
|
|
836
1019
|
// An operator after the value means the real expression is a computation
|
|
837
1020
|
// (`base + '/a'`, `isProd ? a : b`, `a || fallback`, `nav[0].items`).
|
|
838
1021
|
// Returning just the LEFT operand handed back a confident, wrong answer —
|
|
@@ -852,8 +1035,8 @@ class Evaluator {
|
|
|
852
1035
|
* there is no `export default`, which covers
|
|
853
1036
|
* `const config = { … }; export { config }`.
|
|
854
1037
|
*/
|
|
855
|
-
export function evaluateModule(source, fallbackExport = "config") {
|
|
856
|
-
const ev = new Evaluator(source);
|
|
1038
|
+
export function evaluateModule(source, fallbackExport = "config", opts = {}) {
|
|
1039
|
+
const ev = new Evaluator(source, opts);
|
|
857
1040
|
let value = UNRESOLVED;
|
|
858
1041
|
if (ev.hasBinding("default"))
|
|
859
1042
|
value = ev.resolveDefaultExport();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/literal",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.134",
|
|
4
4
|
"description": "Read a JavaScript/TypeScript config module as data without running it: object and array literals, spreads, module-level constants, zero-argument helpers and wrapper calls; anything else is reported, never guessed. No Node, no dependencies",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|