@observablehq/notebook-kit 2.1.7 → 2.1.9
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/dist/package.json +2 -2
- package/dist/src/javascript/features.js +1 -1
- package/dist/src/javascript/observable.js +23 -0
- package/dist/src/runtime/define.d.ts +2 -0
- package/dist/src/runtime/define.js +3 -2
- package/dist/src/runtime/stdlib/dsv.d.ts +4 -0
- package/dist/src/runtime/stdlib/dsv.js +71 -0
- package/dist/src/runtime/stdlib/duckdb.js +30 -12
- package/dist/src/runtime/stdlib/fileAttachment.d.ts +2 -6
- package/dist/src/runtime/stdlib/fileAttachment.js +6 -2
- package/dist/src/runtime/stdlib/index.d.ts +1 -5
- package/dist/src/vite/observable.js +1 -0
- package/package.json +2 -2
- package/dist/src/javascript/literal.d.ts +0 -8
- package/dist/src/javascript/literal.js +0 -42
package/dist/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1.
|
|
8
|
+
"version": "2.1.9",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "vitest",
|
|
11
|
+
"test": "TZ=America/Los_Angeles vitest",
|
|
12
12
|
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/bin/*.js && cp -r src/styles src/templates dist/src && cp src/runtime/stdlib/*.css dist/src/runtime/stdlib",
|
|
13
13
|
"lint": "tsc --noEmit && eslint bin src types",
|
|
14
14
|
"notebooks": "tsx bin/notebooks.ts",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getStringLiteralValue, isStringLiteral } from "./
|
|
1
|
+
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
2
2
|
import { findReferences } from "./references.js";
|
|
3
3
|
import { syntaxError } from "./syntaxError.js";
|
|
4
4
|
import { simple } from "./walk.js";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { parseCell } from "@observablehq/parser";
|
|
2
2
|
import { rewriteFileExpressions } from "./files.js";
|
|
3
3
|
import { flatMapImportSpecifiers, rewriteImportDeclarations } from "./imports.js";
|
|
4
|
+
import { resolveNpmImport } from "./imports/npm.js";
|
|
4
5
|
import { Sourcemap } from "./sourcemap.js";
|
|
6
|
+
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
5
7
|
import { transpileJavaScript } from "./transpile.js";
|
|
6
8
|
import { simple } from "./walk.js";
|
|
7
9
|
export function transpileObservable(input, options) {
|
|
@@ -14,6 +16,7 @@ export function transpileObservable(input, options) {
|
|
|
14
16
|
throw new Error("tagged ojs cells are not supported");
|
|
15
17
|
const output = new Sourcemap(input).trim();
|
|
16
18
|
rewriteSpecialReferences(output, cell.body);
|
|
19
|
+
rewriteDynamicImports(output, cell.body);
|
|
17
20
|
if (options?.resolveFiles)
|
|
18
21
|
rewriteFileExpressions(output, cell.body);
|
|
19
22
|
const inputs = Array.from(new Set(cell.references.map(asReference)));
|
|
@@ -82,6 +85,26 @@ function transformObservableImport(body) {
|
|
|
82
85
|
}
|
|
83
86
|
];
|
|
84
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Rewrite a bare dynamic import such as import("d3") to a CDN URL, like
|
|
90
|
+
* import("https://cdn.jsdelivr.net/npm/d3/+esm"), using the same resolution as
|
|
91
|
+
* static imports. Protocol (npm:, https:, …) and local imports are left alone.
|
|
92
|
+
*/
|
|
93
|
+
function rewriteDynamicImports(output, body) {
|
|
94
|
+
simple(body, {
|
|
95
|
+
ImportExpression({ source }) {
|
|
96
|
+
if (!isStringLiteral(source))
|
|
97
|
+
return;
|
|
98
|
+
let value = getStringLiteralValue(source);
|
|
99
|
+
if (/^(\w+:|\.?\.?\/)/.test(value))
|
|
100
|
+
return;
|
|
101
|
+
if (/\.(js|mjs|cjs)$/.test(value))
|
|
102
|
+
value += "/+esm";
|
|
103
|
+
const resolution = resolveNpmImport(`npm:${value}`);
|
|
104
|
+
output.replaceLeft(source.start, source.end, JSON.stringify(resolution));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
85
108
|
/** Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value. */
|
|
86
109
|
function rewriteSpecialReferences(output, body) {
|
|
87
110
|
simple(body, {
|
|
@@ -22,6 +22,8 @@ export type Definition = {
|
|
|
22
22
|
autoview?: boolean;
|
|
23
23
|
/** whether this cell’s singular output is a mutable */
|
|
24
24
|
automutable?: boolean;
|
|
25
|
+
/** whether to define the display and view builtins; defaults to true */
|
|
26
|
+
display?: boolean;
|
|
25
27
|
/** an asset mapping to apply to any autodisplayed assets (e.g., images and videos) */
|
|
26
28
|
assets?: Map<string, string>;
|
|
27
29
|
};
|
|
@@ -2,12 +2,13 @@ import { clear, display, observe } from "./display.js";
|
|
|
2
2
|
import { input } from "./stdlib/generators/index.js";
|
|
3
3
|
import { Mutator } from "./stdlib/mutable.js";
|
|
4
4
|
export function define(main, state, definition, observer = observe) {
|
|
5
|
-
const { id, body, inputs = [], outputs = [], output
|
|
5
|
+
const { id, body, inputs = [], outputs = [], output } = definition;
|
|
6
|
+
const { autodisplay, autoview, automutable, display: defineDisplay = true } = definition;
|
|
6
7
|
const variables = state.variables;
|
|
7
8
|
const v = main.variable(observer(state, definition), { shadow: {} });
|
|
8
9
|
const vid = output ?? (outputs.length ? `cell ${id}` : null);
|
|
9
10
|
state.autoclear = true;
|
|
10
|
-
if (inputs.includes("display") || inputs.includes("view")) {
|
|
11
|
+
if (defineDisplay && (inputs.includes("display") || inputs.includes("view"))) {
|
|
11
12
|
let displayVersion = -1; // the variable._version of currently-displayed values
|
|
12
13
|
const vd = new v.constructor(2, v._module);
|
|
13
14
|
vd.define(inputs.filter((i) => i !== "display" && i !== "view"), () => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function inferTypes<T extends Record<string, string>>(rows: T[], columns: (keyof T)[]): Record<keyof T, unknown>[];
|
|
2
|
+
export declare function coerceBoolean(value: string): boolean | null | undefined;
|
|
3
|
+
export declare function coerceNumber(value: string): number;
|
|
4
|
+
export declare function coerceDate(value: string): Date | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
|
2
|
+
/**
|
|
3
|
+
* Accepts dates in the following forms:
|
|
4
|
+
* - ±YYYYYY-MM-DD
|
|
5
|
+
* - YYYY-MM-DD
|
|
6
|
+
* - MM/DD/YY
|
|
7
|
+
* - MM/DD/YYYY
|
|
8
|
+
*
|
|
9
|
+
* Following a "T" or a space, dates may additionally have a time:
|
|
10
|
+
* - HH:MM
|
|
11
|
+
* - HH:MM:SS
|
|
12
|
+
* - HH:MM:SS.MMM
|
|
13
|
+
*
|
|
14
|
+
* And the time may optionally have a time zone:
|
|
15
|
+
* - Z
|
|
16
|
+
* - ±HH:MM
|
|
17
|
+
*/
|
|
18
|
+
const DATE_TEST = /^([-+]\d{2})?\d{4}-\d{2}-\d{2}|\d{1,2}\/\d{1,2}\/\d{2,4}([T ]\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/; // prettier-ignore
|
|
19
|
+
/** The maximum number of rows to sample (including missing values). */
|
|
20
|
+
const SAMPLE_SIZE = 100;
|
|
21
|
+
export function inferTypes(rows, columns) {
|
|
22
|
+
const output = rows;
|
|
23
|
+
const n = rows.length;
|
|
24
|
+
const k = Math.min(n, SAMPLE_SIZE);
|
|
25
|
+
for (const column of columns) {
|
|
26
|
+
let booleans = 0;
|
|
27
|
+
let numbers = 0;
|
|
28
|
+
let dates = 0;
|
|
29
|
+
let strings = 0;
|
|
30
|
+
for (let i = 0; i < k; ++i) {
|
|
31
|
+
const value = rows[i][column]?.trim();
|
|
32
|
+
if (!value)
|
|
33
|
+
continue;
|
|
34
|
+
++strings;
|
|
35
|
+
if (/^(true|false)$/i.test(value))
|
|
36
|
+
++booleans;
|
|
37
|
+
else if (!isNaN(Number(value)))
|
|
38
|
+
++numbers;
|
|
39
|
+
else if (DATE_TEST.test(value))
|
|
40
|
+
++dates;
|
|
41
|
+
}
|
|
42
|
+
// Chose the non-string type with the greatest count that is also ≥90%; or
|
|
43
|
+
// if no such type meets that criterion, use string.
|
|
44
|
+
let coerce = undefined;
|
|
45
|
+
let typeCount = Math.max(1, Math.ceil(strings * 0.9)) - 1;
|
|
46
|
+
if (booleans > typeCount)
|
|
47
|
+
((coerce = coerceBoolean), (typeCount = booleans));
|
|
48
|
+
if (numbers > typeCount)
|
|
49
|
+
((coerce = coerceNumber), (typeCount = numbers));
|
|
50
|
+
if (dates > typeCount)
|
|
51
|
+
((coerce = coerceDate), (typeCount = dates));
|
|
52
|
+
if (!coerce)
|
|
53
|
+
continue;
|
|
54
|
+
for (let i = 0; i < n; ++i) {
|
|
55
|
+
output[i][column] = coerce(rows[i][column]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
export function coerceBoolean(value) {
|
|
61
|
+
const trimmed = value.trim().toLowerCase();
|
|
62
|
+
return trimmed === "true" ? true : trimmed === "false" ? false : trimmed ? undefined : null;
|
|
63
|
+
}
|
|
64
|
+
export function coerceNumber(value) {
|
|
65
|
+
const trimmed = value.trim();
|
|
66
|
+
return trimmed ? Number(value) : NaN;
|
|
67
|
+
}
|
|
68
|
+
export function coerceDate(value) {
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
return trimmed ? new Date(DATE_TEST.test(trimmed) ? trimmed : NaN) : null;
|
|
71
|
+
}
|
|
@@ -203,6 +203,8 @@ async function insertSource(database, name, source) {
|
|
|
203
203
|
source = await source;
|
|
204
204
|
if (isFileAttachment(source))
|
|
205
205
|
return insertFile(database, name, source);
|
|
206
|
+
if (isFlechetteTable(source))
|
|
207
|
+
return insertFlechetteTable(database, name, source);
|
|
206
208
|
if (isArrowTable(source))
|
|
207
209
|
return insertArrowTable(database, name, source);
|
|
208
210
|
if (Array.isArray(source))
|
|
@@ -215,6 +217,8 @@ async function insertSource(database, name, source) {
|
|
|
215
217
|
if ("data" in source) {
|
|
216
218
|
// data + options
|
|
217
219
|
const { data, ...options } = source;
|
|
220
|
+
if (isFlechetteTable(data))
|
|
221
|
+
return insertFlechetteTable(database, name, data, options);
|
|
218
222
|
if (isArrowTable(data))
|
|
219
223
|
return insertArrowTable(database, name, data, options);
|
|
220
224
|
return insertArray(database, name, data, options);
|
|
@@ -301,22 +305,28 @@ async function insertUntypedCSV(connection, file, name) {
|
|
|
301
305
|
async function insertArrowTable(database, name, table, options) {
|
|
302
306
|
const connection = await database.connect();
|
|
303
307
|
try {
|
|
304
|
-
await connection.insertArrowTable(table, {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
await connection.insertArrowTable(table, { name, schema: "main", ...options });
|
|
309
|
+
}
|
|
310
|
+
finally {
|
|
311
|
+
await connection.close();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async function insertArrowIpc(database, name, buffer, options) {
|
|
315
|
+
const connection = await database.connect();
|
|
316
|
+
try {
|
|
317
|
+
await connection.insertArrowFromIPCStream(buffer, { name, schema: "main", ...options });
|
|
309
318
|
}
|
|
310
319
|
finally {
|
|
311
320
|
await connection.close();
|
|
312
321
|
}
|
|
313
322
|
}
|
|
323
|
+
async function insertFlechetteTable(database, name, table, options) {
|
|
324
|
+
const { tableToIPC } = await import("https://cdn.jsdelivr.net/npm/@uwdata/flechette@2.5.0/+esm");
|
|
325
|
+
return await insertArrowIpc(database, name, tableToIPC(table, { format: "stream" }), options);
|
|
326
|
+
}
|
|
314
327
|
async function insertArqueroTable(database, name, source) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const arrow = await import("https://cdn.jsdelivr.net/npm/apache-arrow@17.0.0/+esm");
|
|
318
|
-
const table = arrow.tableFromIPC(source.toArrowBuffer());
|
|
319
|
-
return await insertArrowTable(database, name, table);
|
|
328
|
+
const buffer = typeof source.toArrowIPC === "function" ? source.toArrowIPC() : source.toArrowBuffer(); // prettier-ignore
|
|
329
|
+
return await insertArrowIpc(database, name, buffer);
|
|
320
330
|
}
|
|
321
331
|
async function insertArray(database, name, array, options) {
|
|
322
332
|
const arrow = await import("https://cdn.jsdelivr.net/npm/apache-arrow@17.0.0/+esm");
|
|
@@ -372,9 +382,17 @@ function isFileAttachment(value) {
|
|
|
372
382
|
typeof value.url === "function" &&
|
|
373
383
|
typeof value.arrayBuffer === "function");
|
|
374
384
|
}
|
|
375
|
-
// Arquero tables have a `toArrowBuffer` function
|
|
385
|
+
// Arquero tables have a `toArrowBuffer` (v5-) or `toArrowIPC` (v6+) function
|
|
376
386
|
function isArqueroTable(value) {
|
|
377
|
-
return value && typeof value.toArrowBuffer === "function";
|
|
387
|
+
return (value && (typeof value.toArrowBuffer === "function" || typeof value.toArrowIPC === "function"));
|
|
388
|
+
}
|
|
389
|
+
// Flechette tables have a `toColumns` method
|
|
390
|
+
function isFlechetteTable(value) {
|
|
391
|
+
return (value &&
|
|
392
|
+
typeof value.getChild === "function" &&
|
|
393
|
+
typeof value.toColumns === "function" &&
|
|
394
|
+
value.schema &&
|
|
395
|
+
Array.isArray(value.schema.fields));
|
|
378
396
|
}
|
|
379
397
|
// Returns true if the value is an Apache Arrow table. This uses a “duck” test
|
|
380
398
|
// (instead of strict instanceof) because we want it to work with a range of
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type DsvOptions = {
|
|
2
2
|
delimiter?: string;
|
|
3
3
|
array?: boolean;
|
|
4
|
-
typed?: boolean;
|
|
4
|
+
typed?: "auto" | boolean;
|
|
5
5
|
};
|
|
6
6
|
export type DsvResult = (Record<string, any>[] | any[][]) & {
|
|
7
7
|
columns: string[];
|
|
@@ -73,11 +73,7 @@ export declare abstract class AbstractFile implements FileAttachment {
|
|
|
73
73
|
text(encoding?: string): Promise<string>;
|
|
74
74
|
json(): Promise<any>;
|
|
75
75
|
stream(): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
76
|
-
dsv({ delimiter, array, typed }?:
|
|
77
|
-
delimiter?: string | undefined;
|
|
78
|
-
array?: boolean | undefined;
|
|
79
|
-
typed?: boolean | undefined;
|
|
80
|
-
}): Promise<DsvResult>;
|
|
76
|
+
dsv({ delimiter, array, typed }?: DsvOptions): Promise<DsvResult>;
|
|
81
77
|
csv(options?: Omit<DsvOptions, "delimiter">): Promise<DsvResult>;
|
|
82
78
|
tsv(options?: Omit<DsvOptions, "delimiter">): Promise<DsvResult>;
|
|
83
79
|
image(props?: Partial<HTMLImageElement>): Promise<HTMLImageElement>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { inferTypes } from "./dsv.js";
|
|
1
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
3
|
const files = new Map();
|
|
3
4
|
let strict = false;
|
|
@@ -92,8 +93,11 @@ export class AbstractFile {
|
|
|
92
93
|
async dsv({ delimiter = ",", array = false, typed = false } = {}) {
|
|
93
94
|
const [text, d3] = await Promise.all([this.text(), import("https://cdn.jsdelivr.net/npm/d3-dsv/+esm")]); // prettier-ignore
|
|
94
95
|
const format = d3.dsvFormat(delimiter);
|
|
95
|
-
const parse = array ? format.parseRows : format.parse;
|
|
96
|
-
|
|
96
|
+
const parse = array ? ((typed && (typed = true)), format.parseRows) : format.parse;
|
|
97
|
+
const output = parse(text, typed === true ? d3.autoType : undefined);
|
|
98
|
+
if (typed === "auto")
|
|
99
|
+
inferTypes(output, output.columns);
|
|
100
|
+
return output;
|
|
97
101
|
}
|
|
98
102
|
async csv(options) {
|
|
99
103
|
return this.dsv({ ...options, delimiter: "," });
|
|
@@ -72,11 +72,7 @@ export declare const library: {
|
|
|
72
72
|
text(encoding?: string): Promise<string>;
|
|
73
73
|
json(): Promise<any>;
|
|
74
74
|
stream(): Promise<ReadableStream<Uint8Array<ArrayBufferLike>>>;
|
|
75
|
-
dsv({ delimiter, array, typed }?:
|
|
76
|
-
delimiter?: string | undefined;
|
|
77
|
-
array?: boolean | undefined;
|
|
78
|
-
typed?: boolean | undefined;
|
|
79
|
-
}): Promise<import("./fileAttachment.js").DsvResult>;
|
|
75
|
+
dsv({ delimiter, array, typed }?: import("./fileAttachment.js").DsvOptions): Promise<import("./fileAttachment.js").DsvResult>;
|
|
80
76
|
csv(options?: Omit<import("./fileAttachment.js").DsvOptions, "delimiter">): Promise<import("./fileAttachment.js").DsvResult>;
|
|
81
77
|
tsv(options?: Omit<import("./fileAttachment.js").DsvOptions, "delimiter">): Promise<import("./fileAttachment.js").DsvResult>;
|
|
82
78
|
image(props?: Partial<HTMLImageElement>): Promise<HTMLImageElement>;
|
|
@@ -173,6 +173,7 @@ define(
|
|
|
173
173
|
inputs: ${JSON.stringify(transpiled.inputs)},
|
|
174
174
|
outputs: ${JSON.stringify(transpiled.outputs)},
|
|
175
175
|
output: ${JSON.stringify(transpiled.output)},
|
|
176
|
+
display: ${cell.mode === "js" || cell.mode === "ts"},
|
|
176
177
|
assets: ${assets.size > 0 ? "assets" : "undefined"},
|
|
177
178
|
autodisplay: ${transpiled.autodisplay},
|
|
178
179
|
autoview: ${transpiled.autoview},
|
package/package.json
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1.
|
|
8
|
+
"version": "2.1.9",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "vitest",
|
|
11
|
+
"test": "TZ=America/Los_Angeles vitest",
|
|
12
12
|
"prepublishOnly": "rm -rf dist && tsc && chmod +x dist/bin/*.js && cp -r src/styles src/templates dist/src && cp src/runtime/stdlib/*.css dist/src/runtime/stdlib",
|
|
13
13
|
"lint": "tsc --noEmit && eslint bin src types",
|
|
14
14
|
"notebooks": "tsx bin/notebooks.ts",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { BinaryExpression, Literal, Node, TemplateLiteral } from "acorn";
|
|
2
|
-
export type StringLiteral = (Literal & {
|
|
3
|
-
value: string;
|
|
4
|
-
}) | TemplateLiteral | BinaryExpression;
|
|
5
|
-
export declare function isLiteral(node: Node): node is Literal;
|
|
6
|
-
export declare function isTemplateLiteral(node: Node): node is TemplateLiteral;
|
|
7
|
-
export declare function isStringLiteral(node: Node): node is StringLiteral;
|
|
8
|
-
export declare function getStringLiteralValue(node: StringLiteral): string;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export function isLiteral(node) {
|
|
2
|
-
return node.type === "Literal";
|
|
3
|
-
}
|
|
4
|
-
export function isTemplateLiteral(node) {
|
|
5
|
-
return node.type === "TemplateLiteral";
|
|
6
|
-
}
|
|
7
|
-
export function isStringLiteral(node) {
|
|
8
|
-
return isLiteral(node)
|
|
9
|
-
? /^['"]/.test(node.raw)
|
|
10
|
-
: isTemplateLiteral(node)
|
|
11
|
-
? node.expressions.every(isStringLiteral)
|
|
12
|
-
: isBinaryExpression(node)
|
|
13
|
-
? node.operator === "+" && isStringLiteral(node.left) && isStringLiteral(node.right)
|
|
14
|
-
: isMemberExpression(node)
|
|
15
|
-
? "value" in node // param
|
|
16
|
-
: false;
|
|
17
|
-
}
|
|
18
|
-
export function getStringLiteralValue(node) {
|
|
19
|
-
return node.type === "TemplateLiteral"
|
|
20
|
-
? getTemplateLiteralValue(node)
|
|
21
|
-
: node.type === "BinaryExpression"
|
|
22
|
-
? getBinaryExpressionValue(node)
|
|
23
|
-
: node.value; // Literal or ParamReference
|
|
24
|
-
}
|
|
25
|
-
function getTemplateLiteralValue(node) {
|
|
26
|
-
let value = node.quasis[0].value.cooked;
|
|
27
|
-
for (let i = 0; i < node.expressions.length; ++i) {
|
|
28
|
-
value += getStringLiteralValue(node.expressions[i]);
|
|
29
|
-
value += node.quasis[i + 1].value.cooked;
|
|
30
|
-
}
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
function getBinaryExpressionValue(node) {
|
|
34
|
-
return (getStringLiteralValue(node.left) +
|
|
35
|
-
getStringLiteralValue(node.right));
|
|
36
|
-
}
|
|
37
|
-
function isMemberExpression(node) {
|
|
38
|
-
return node.type === "MemberExpression";
|
|
39
|
-
}
|
|
40
|
-
function isBinaryExpression(node) {
|
|
41
|
-
return node.type === "BinaryExpression";
|
|
42
|
-
}
|