@observablehq/notebook-kit 1.0.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/LICENSE +13 -0
- package/README.md +7 -0
- package/dist/bin/build.d.ts +2 -0
- package/dist/bin/build.js +63 -0
- package/dist/bin/download.d.ts +2 -0
- package/dist/bin/download.js +49 -0
- package/dist/bin/notebooks.d.ts +2 -0
- package/dist/bin/notebooks.js +33 -0
- package/dist/bin/preview.d.ts +2 -0
- package/dist/bin/preview.js +48 -0
- package/dist/package.json +73 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +5 -0
- package/dist/src/javascript/assignments.d.ts +2 -0
- package/dist/src/javascript/assignments.js +37 -0
- package/dist/src/javascript/assignments.test.d.ts +1 -0
- package/dist/src/javascript/assignments.test.js +33 -0
- package/dist/src/javascript/awaits.d.ts +2 -0
- package/dist/src/javascript/awaits.js +23 -0
- package/dist/src/javascript/awaits.test.d.ts +1 -0
- package/dist/src/javascript/awaits.test.js +22 -0
- package/dist/src/javascript/declarations.d.ts +2 -0
- package/dist/src/javascript/declarations.js +45 -0
- package/dist/src/javascript/files.d.ts +3 -0
- package/dist/src/javascript/files.js +18 -0
- package/dist/src/javascript/globals.d.ts +1 -0
- package/dist/src/javascript/globals.js +86 -0
- package/dist/src/javascript/imports/jsr.d.ts +2 -0
- package/dist/src/javascript/imports/jsr.js +6 -0
- package/dist/src/javascript/imports/npm.d.ts +2 -0
- package/dist/src/javascript/imports/npm.js +47 -0
- package/dist/src/javascript/imports/npm.test.d.ts +1 -0
- package/dist/src/javascript/imports/npm.test.js +32 -0
- package/dist/src/javascript/imports/observable.d.ts +8 -0
- package/dist/src/javascript/imports/observable.js +64 -0
- package/dist/src/javascript/imports/observable.test.d.ts +1 -0
- package/dist/src/javascript/imports/observable.test.js +13 -0
- package/dist/src/javascript/imports.d.ts +21 -0
- package/dist/src/javascript/imports.js +146 -0
- package/dist/src/javascript/observable.d.ts +2 -0
- package/dist/src/javascript/observable.js +72 -0
- package/dist/src/javascript/parse.d.ts +11 -0
- package/dist/src/javascript/parse.js +53 -0
- package/dist/src/javascript/references.d.ts +8 -0
- package/dist/src/javascript/references.js +129 -0
- package/dist/src/javascript/references.test.d.ts +1 -0
- package/dist/src/javascript/references.test.js +38 -0
- package/dist/src/javascript/sourcemap.d.ts +21 -0
- package/dist/src/javascript/sourcemap.js +143 -0
- package/dist/src/javascript/sourcemap.test.d.ts +1 -0
- package/dist/src/javascript/sourcemap.test.js +88 -0
- package/dist/src/javascript/strings.d.ts +8 -0
- package/dist/src/javascript/strings.js +42 -0
- package/dist/src/javascript/strings.test.d.ts +1 -0
- package/dist/src/javascript/strings.test.js +31 -0
- package/dist/src/javascript/syntaxError.d.ts +2 -0
- package/dist/src/javascript/syntaxError.js +5 -0
- package/dist/src/javascript/template.d.ts +3 -0
- package/dist/src/javascript/template.js +141 -0
- package/dist/src/javascript/template.test.d.ts +1 -0
- package/dist/src/javascript/template.test.js +32 -0
- package/dist/src/javascript/transpile.d.ts +25 -0
- package/dist/src/javascript/transpile.js +42 -0
- package/dist/src/javascript/transpile.test.d.ts +1 -0
- package/dist/src/javascript/transpile.test.js +29 -0
- package/dist/src/javascript/walk.d.ts +5 -0
- package/dist/src/javascript/walk.js +13 -0
- package/dist/src/lib/notebook.d.ts +35 -0
- package/dist/src/lib/notebook.js +19 -0
- package/dist/src/lib/notebook.test.d.ts +1 -0
- package/dist/src/lib/notebook.test.js +26 -0
- package/dist/src/lib/serialize.d.ts +5 -0
- package/dist/src/lib/serialize.js +97 -0
- package/dist/src/lib/serialize.test.d.ts +1 -0
- package/dist/src/lib/serialize.test.js +125 -0
- package/dist/src/lib/text.d.ts +1 -0
- package/dist/src/lib/text.js +3 -0
- package/dist/src/runtime/define.d.ts +28 -0
- package/dist/src/runtime/define.js +62 -0
- package/dist/src/runtime/display.d.ts +16 -0
- package/dist/src/runtime/display.js +60 -0
- package/dist/src/runtime/index.d.ts +11 -0
- package/dist/src/runtime/index.js +14 -0
- package/dist/src/runtime/inspect.d.ts +3 -0
- package/dist/src/runtime/inspect.js +43 -0
- package/dist/src/runtime/stdlib/assets.d.ts +4 -0
- package/dist/src/runtime/stdlib/assets.js +110 -0
- package/dist/src/runtime/stdlib/assets.test.d.ts +1 -0
- package/dist/src/runtime/stdlib/assets.test.js +78 -0
- package/dist/src/runtime/stdlib/dom/canvas.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/canvas.js +6 -0
- package/dist/src/runtime/stdlib/dom/context2d.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/context2d.js +9 -0
- package/dist/src/runtime/stdlib/dom/index.d.ts +5 -0
- package/dist/src/runtime/stdlib/dom/index.js +5 -0
- package/dist/src/runtime/stdlib/dom/svg.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/svg.js +7 -0
- package/dist/src/runtime/stdlib/dom/text.d.ts +1 -0
- package/dist/src/runtime/stdlib/dom/text.js +3 -0
- package/dist/src/runtime/stdlib/dom/uid.d.ts +8 -0
- package/dist/src/runtime/stdlib/dom/uid.js +25 -0
- package/dist/src/runtime/stdlib/dot.d.ts +2 -0
- package/dist/src/runtime/stdlib/dot.js +34 -0
- package/dist/src/runtime/stdlib/duckdb.d.ts +24 -0
- package/dist/src/runtime/stdlib/duckdb.js +379 -0
- package/dist/src/runtime/stdlib/fileAttachment.d.ts +71 -0
- package/dist/src/runtime/stdlib/fileAttachment.js +199 -0
- package/dist/src/runtime/stdlib/generators/index.d.ts +5 -0
- package/dist/src/runtime/stdlib/generators/index.js +5 -0
- package/dist/src/runtime/stdlib/generators/input.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/input.js +45 -0
- package/dist/src/runtime/stdlib/generators/now.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/now.js +5 -0
- package/dist/src/runtime/stdlib/generators/observe.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/observe.js +31 -0
- package/dist/src/runtime/stdlib/generators/queue.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/queue.js +27 -0
- package/dist/src/runtime/stdlib/generators/width.d.ts +1 -0
- package/dist/src/runtime/stdlib/generators/width.js +13 -0
- package/dist/src/runtime/stdlib/highlight.d.ts +2 -0
- package/dist/src/runtime/stdlib/highlight.js +76 -0
- package/dist/src/runtime/stdlib/index.d.ts +56 -0
- package/dist/src/runtime/stdlib/index.js +23 -0
- package/dist/src/runtime/stdlib/inputs.css +15 -0
- package/dist/src/runtime/stdlib/inputs.d.ts +2 -0
- package/dist/src/runtime/stdlib/inputs.js +2 -0
- package/dist/src/runtime/stdlib/leaflet.d.ts +1 -0
- package/dist/src/runtime/stdlib/leaflet.js +7 -0
- package/dist/src/runtime/stdlib/mapboxgl.d.ts +1 -0
- package/dist/src/runtime/stdlib/mapboxgl.js +5 -0
- package/dist/src/runtime/stdlib/md.d.ts +5 -0
- package/dist/src/runtime/stdlib/md.js +72 -0
- package/dist/src/runtime/stdlib/mermaid.d.ts +2 -0
- package/dist/src/runtime/stdlib/mermaid.js +11 -0
- package/dist/src/runtime/stdlib/mutable.d.ts +8 -0
- package/dist/src/runtime/stdlib/mutable.js +30 -0
- package/dist/src/runtime/stdlib/observer.d.ts +16 -0
- package/dist/src/runtime/stdlib/observer.js +42 -0
- package/dist/src/runtime/stdlib/recommendedLibraries.d.ts +25 -0
- package/dist/src/runtime/stdlib/recommendedLibraries.js +26 -0
- package/dist/src/runtime/stdlib/require.d.ts +4 -0
- package/dist/src/runtime/stdlib/require.js +40 -0
- package/dist/src/runtime/stdlib/sampleDatasets.d.ts +12 -0
- package/dist/src/runtime/stdlib/sampleDatasets.js +31 -0
- package/dist/src/runtime/stdlib/sql.d.ts +5 -0
- package/dist/src/runtime/stdlib/sql.js +5 -0
- package/dist/src/runtime/stdlib/template.d.ts +7 -0
- package/dist/src/runtime/stdlib/template.js +2 -0
- package/dist/src/runtime/stdlib/tex.d.ts +7 -0
- package/dist/src/runtime/stdlib/tex.js +18 -0
- package/dist/src/runtime/stdlib/vega-lite.d.ts +1 -0
- package/dist/src/runtime/stdlib/vega-lite.js +4 -0
- package/dist/src/styles/abstract-dark.css +14 -0
- package/dist/src/styles/abstract-light.css +14 -0
- package/dist/src/styles/global.css +266 -0
- package/dist/src/styles/highlight.css +47 -0
- package/dist/src/styles/index.css +14 -0
- package/dist/src/styles/inspector.css +89 -0
- package/dist/src/styles/plot.css +7 -0
- package/dist/src/styles/syntax-dark.css +12 -0
- package/dist/src/styles/syntax-light.css +12 -0
- package/dist/src/styles/theme-air.css +7 -0
- package/dist/src/styles/theme-coffee.css +7 -0
- package/dist/src/styles/theme-cotton.css +7 -0
- package/dist/src/styles/theme-deep-space.css +16 -0
- package/dist/src/styles/theme-glacier.css +7 -0
- package/dist/src/styles/theme-ink.css +7 -0
- package/dist/src/styles/theme-midnight.css +7 -0
- package/dist/src/styles/theme-near-midnight.css +7 -0
- package/dist/src/styles/theme-ocean-floor.css +7 -0
- package/dist/src/styles/theme-parchment.css +7 -0
- package/dist/src/styles/theme-slate.css +7 -0
- package/dist/src/styles/theme-stark.css +16 -0
- package/dist/src/styles/theme-sun-faded.css +7 -0
- package/dist/src/templates/default.html +31 -0
- package/dist/src/vite/config.d.ts +2 -0
- package/dist/src/vite/config.js +30 -0
- package/dist/src/vite/index.d.ts +2 -0
- package/dist/src/vite/index.js +2 -0
- package/dist/src/vite/observable.d.ts +12 -0
- package/dist/src/vite/observable.js +176 -0
- package/package.json +73 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Note: specifier here does not start with `npm:`. */
|
|
2
|
+
function parseNpmSpecifier(specifier) {
|
|
3
|
+
const parts = specifier.split("/");
|
|
4
|
+
const namerange = specifier.startsWith("@")
|
|
5
|
+
? [parts.shift(), parts.shift()].join("/")
|
|
6
|
+
: parts.shift();
|
|
7
|
+
const ranged = namerange.indexOf("@", 1);
|
|
8
|
+
const name = ranged > 0 ? namerange.slice(0, ranged) : namerange;
|
|
9
|
+
const range = ranged > 0 ? namerange.slice(ranged) : "";
|
|
10
|
+
const path = parts.length > 0 ? `/${parts.join("/")}` : "";
|
|
11
|
+
return { name, range, path };
|
|
12
|
+
}
|
|
13
|
+
/** If specifier is an npm: protocol import, resolves it. */
|
|
14
|
+
export function resolveNpmImport(specifier) {
|
|
15
|
+
if (!specifier.startsWith("npm:"))
|
|
16
|
+
return specifier;
|
|
17
|
+
const { name, range, path } = parseNpmSpecifier(specifier.slice(4));
|
|
18
|
+
return `https://cdn.jsdelivr.net/npm/${name}${range || getDefaultRange(name)}${path
|
|
19
|
+
? !/(\.\w+|\/|\/\+esm)$/.test(path) // if not file, directory, or /+esm
|
|
20
|
+
? `${path}/+esm` // then append /+esm
|
|
21
|
+
: path // otherwise keep as-is
|
|
22
|
+
: getDefaultPath(name)}`;
|
|
23
|
+
}
|
|
24
|
+
function getDefaultRange(name) {
|
|
25
|
+
switch (name) {
|
|
26
|
+
case "@duckdb/duckdb-wasm":
|
|
27
|
+
return "@1.29.0"; // https://github.com/duckdb/duckdb-wasm/issues/1994
|
|
28
|
+
default:
|
|
29
|
+
return "";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getDefaultPath(name) {
|
|
33
|
+
switch (name) {
|
|
34
|
+
case "mermaid":
|
|
35
|
+
return "/dist/mermaid.esm.min.mjs/+esm";
|
|
36
|
+
case "echarts":
|
|
37
|
+
return "/dist/echarts.esm.min.js/+esm";
|
|
38
|
+
case "jquery-ui":
|
|
39
|
+
return "/dist/jquery-ui.js/+esm";
|
|
40
|
+
case "deck.gl":
|
|
41
|
+
return "/dist.min.js/+esm";
|
|
42
|
+
case "react-dom":
|
|
43
|
+
return "/client/+esm";
|
|
44
|
+
default:
|
|
45
|
+
return "/+esm";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { assert, test } from "vitest";
|
|
2
|
+
import { resolveNpmImport } from "./npm.js";
|
|
3
|
+
test("adds /+esm as expected", () => {
|
|
4
|
+
assert.strictEqual(resolveNpmImport("npm:prettier"), "https://cdn.jsdelivr.net/npm/prettier/+esm" // defaults to /+esm
|
|
5
|
+
);
|
|
6
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/"), "https://cdn.jsdelivr.net/npm/prettier/" // no /+esm because trailing slash
|
|
7
|
+
);
|
|
8
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/+esm"), "https://cdn.jsdelivr.net/npm/prettier/+esm" // preserve existing /+esm
|
|
9
|
+
);
|
|
10
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/+esm");
|
|
11
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn/+esm"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/+esm" // preserve existing /+esm
|
|
12
|
+
);
|
|
13
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn/"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn/" // no /+esm because trailing slash
|
|
14
|
+
);
|
|
15
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn.js"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn.js" // no /+esm because file extension
|
|
16
|
+
);
|
|
17
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/acorn.js/+esm"), "https://cdn.jsdelivr.net/npm/prettier/plugins/acorn.js/+esm" // preserve existing /+esm
|
|
18
|
+
);
|
|
19
|
+
assert.strictEqual(resolveNpmImport("npm:prettier/plugins/estree"), "https://cdn.jsdelivr.net/npm/prettier/plugins/estree/+esm");
|
|
20
|
+
});
|
|
21
|
+
test("sets the default version of @duckdb/duckdb-wasm", () => {
|
|
22
|
+
assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm");
|
|
23
|
+
assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm/+esm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/+esm");
|
|
24
|
+
assert.strictEqual(resolveNpmImport("npm:@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm"), "https://cdn.jsdelivr.net/npm/@duckdb/duckdb-wasm@1.29.0/dist/duckdb-mvp.wasm");
|
|
25
|
+
});
|
|
26
|
+
test("sets the default path for various libraries", () => {
|
|
27
|
+
assert.strictEqual(resolveNpmImport("npm:mermaid"), "https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.esm.min.mjs/+esm");
|
|
28
|
+
assert.strictEqual(resolveNpmImport("npm:echarts"), "https://cdn.jsdelivr.net/npm/echarts/dist/echarts.esm.min.js/+esm");
|
|
29
|
+
assert.strictEqual(resolveNpmImport("npm:jquery-ui"), "https://cdn.jsdelivr.net/npm/jquery-ui/dist/jquery-ui.js/+esm");
|
|
30
|
+
assert.strictEqual(resolveNpmImport("npm:deck.gl"), "https://cdn.jsdelivr.net/npm/deck.gl/dist.min.js/+esm");
|
|
31
|
+
assert.strictEqual(resolveNpmImport("npm:react-dom"), "https://cdn.jsdelivr.net/npm/react-dom/client/+esm");
|
|
32
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImportDeclaration } from "acorn";
|
|
2
|
+
/** If specifier is an observable: protocol import, resolves it. */
|
|
3
|
+
export declare function resolveObservableImport(specifier: string): string;
|
|
4
|
+
export declare function isObservableImport(node: ImportDeclaration, specifier: string): boolean;
|
|
5
|
+
/** Note: mutates inputs! */
|
|
6
|
+
export declare function renderObservableImport(source: string, node: ImportDeclaration, inputs: string[]): string;
|
|
7
|
+
/** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
|
|
8
|
+
export declare function dedollar(input: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getImportedName } from "../imports.js";
|
|
2
|
+
const CODE_DOLLAR = 36;
|
|
3
|
+
/** If specifier is an observable: protocol import, resolves it. */
|
|
4
|
+
export function resolveObservableImport(specifier) {
|
|
5
|
+
if (!specifier.startsWith("observable:"))
|
|
6
|
+
return specifier;
|
|
7
|
+
return `https://api.observablehq.com/${specifier.slice("observable:".length)}.js?v=4`;
|
|
8
|
+
}
|
|
9
|
+
export function isObservableImport(node, specifier) {
|
|
10
|
+
const type = node.attributes?.find((a) => getImportAttributeKey(a) === "type")?.value;
|
|
11
|
+
return type ? type.value === "observable" : specifier.startsWith("observable:");
|
|
12
|
+
}
|
|
13
|
+
function getImportAttributeKey(node) {
|
|
14
|
+
return node.key.type === "Identifier" ? node.key.name : String(node.key.value);
|
|
15
|
+
}
|
|
16
|
+
/** Note: mutates inputs! */
|
|
17
|
+
export function renderObservableImport(source, node, inputs) {
|
|
18
|
+
if (!inputs.includes("__ojs_runtime"))
|
|
19
|
+
inputs.push("__ojs_runtime");
|
|
20
|
+
if (!inputs.includes("__ojs_observer"))
|
|
21
|
+
inputs.push("__ojs_observer");
|
|
22
|
+
return `(import(${JSON.stringify(source)}).then((_) => {
|
|
23
|
+
const observers = {};
|
|
24
|
+
const module = __ojs_runtime.module(_.default);
|
|
25
|
+
const main = __ojs_runtime.module();${node.specifiers
|
|
26
|
+
.map((specifier) => {
|
|
27
|
+
if (specifier.type === "ImportNamespaceSpecifier")
|
|
28
|
+
throw new SyntaxError("observable namespace imports are not supported");
|
|
29
|
+
const iname = getImportedName(specifier);
|
|
30
|
+
const vname = dedollar(iname);
|
|
31
|
+
return `
|
|
32
|
+
if (!module.defines("${vname}")) throw new SyntaxError(\`export '${vname}' not found\`);
|
|
33
|
+
main.variable(observers.${iname} = __ojs_observer()).import("${vname}", module);`;
|
|
34
|
+
})
|
|
35
|
+
.join("")}
|
|
36
|
+
return observers;
|
|
37
|
+
}))`;
|
|
38
|
+
}
|
|
39
|
+
/** Turns e.g. "viewof$foo" into "viewof foo", and "$$" into "$". */
|
|
40
|
+
export function dedollar(input) {
|
|
41
|
+
const start = 0;
|
|
42
|
+
const end = input.length;
|
|
43
|
+
let dollars = 0;
|
|
44
|
+
for (let i = start; i < end; ++i) {
|
|
45
|
+
switch (input.charCodeAt(i)) {
|
|
46
|
+
case CODE_DOLLAR: {
|
|
47
|
+
++dollars;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
default: {
|
|
51
|
+
if (dollars > 0) {
|
|
52
|
+
input = `${input.slice(0, i - 1)}${dollars === 1 ? " " : ""}${input.slice(i)}`;
|
|
53
|
+
dollars = 0;
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (dollars > 0) {
|
|
60
|
+
input = `${input.slice(0, end - 1)}${dollars === 1 ? " " : ""}`;
|
|
61
|
+
dollars = 0;
|
|
62
|
+
}
|
|
63
|
+
return input;
|
|
64
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { assert, test } from "vitest";
|
|
2
|
+
import { dedollar } from "./observable.js";
|
|
3
|
+
test("unescapes dollar signs in imported symbols", () => {
|
|
4
|
+
assert.strictEqual(dedollar("viewof$foo"), "viewof foo");
|
|
5
|
+
assert.strictEqual(dedollar("viewof$"), "viewof ");
|
|
6
|
+
assert.strictEqual(dedollar("$viewof"), " viewof");
|
|
7
|
+
assert.strictEqual(dedollar("viewof$$foo"), "viewof$foo");
|
|
8
|
+
assert.strictEqual(dedollar("viewof$$$foo"), "viewof$$foo");
|
|
9
|
+
assert.strictEqual(dedollar("$"), " ");
|
|
10
|
+
assert.strictEqual(dedollar("$$"), "$");
|
|
11
|
+
assert.strictEqual(dedollar("$$$"), "$$");
|
|
12
|
+
assert.strictEqual(dedollar("$$_"), "$_");
|
|
13
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CallExpression, MemberExpression, Node } from "acorn";
|
|
2
|
+
import type { ImportDefaultSpecifier, ImportSpecifier } from "acorn";
|
|
3
|
+
import type { Sourcemap } from "./sourcemap.js";
|
|
4
|
+
type NamedImportSpecifier = ImportSpecifier | ImportDefaultSpecifier;
|
|
5
|
+
/** Throws a syntax error if any export declarations are found. */
|
|
6
|
+
export declare function checkExports(body: Node, input: string): void;
|
|
7
|
+
/** Returns true if the body includes an import declaration. */
|
|
8
|
+
export declare function hasImportDeclaration(body: Node): boolean;
|
|
9
|
+
/** Returns true if the given node is a import.meta.resolve(…) call. */
|
|
10
|
+
export declare function isImportMetaResolve(node: CallExpression): boolean;
|
|
11
|
+
/** Returns true if the given node is a import.meta.url expression. */
|
|
12
|
+
export declare function isImportMetaUrl(node: MemberExpression): boolean;
|
|
13
|
+
export type RewriteImportOptions = {
|
|
14
|
+
/** If true, resolve local imports relative to document.baseURI. */
|
|
15
|
+
resolveLocalImports?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare function rewriteImportExpressions(output: Sourcemap, body: Node, { resolveLocalImports }?: RewriteImportOptions): void;
|
|
18
|
+
/** Note: mutates inputs! */
|
|
19
|
+
export declare function rewriteImportDeclarations(output: Sourcemap, body: Node, inputs: string[], { resolveLocalImports }?: RewriteImportOptions): void;
|
|
20
|
+
export declare function getImportedName(node: NamedImportSpecifier): string;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { resolveJsrImport } from "./imports/jsr.js";
|
|
2
|
+
import { resolveNpmImport } from "./imports/npm.js";
|
|
3
|
+
import { resolveObservableImport } from "./imports/observable.js";
|
|
4
|
+
import { isObservableImport, renderObservableImport } from "./imports/observable.js";
|
|
5
|
+
import { getStringLiteralValue, isStringLiteral } from "./strings.js";
|
|
6
|
+
import { syntaxError } from "./syntaxError.js";
|
|
7
|
+
import { simple } from "./walk.js";
|
|
8
|
+
/** Throws a syntax error if any export declarations are found. */
|
|
9
|
+
export function checkExports(body, input) {
|
|
10
|
+
function checkExport(child) {
|
|
11
|
+
throw syntaxError("Unexpected token 'export'", child, input);
|
|
12
|
+
}
|
|
13
|
+
simple(body, {
|
|
14
|
+
ExportAllDeclaration: checkExport,
|
|
15
|
+
ExportNamedDeclaration: checkExport
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Returns true if the body includes an import declaration. */
|
|
19
|
+
export function hasImportDeclaration(body) {
|
|
20
|
+
let has = false;
|
|
21
|
+
simple(body, {
|
|
22
|
+
ImportDeclaration() {
|
|
23
|
+
has = true;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return has;
|
|
27
|
+
}
|
|
28
|
+
/** Returns true if the given node is a import.meta.resolve(…) call. */
|
|
29
|
+
export function isImportMetaResolve(node) {
|
|
30
|
+
return (node.callee.type === "MemberExpression" &&
|
|
31
|
+
node.callee.object.type === "MetaProperty" &&
|
|
32
|
+
node.callee.object.meta.name === "import" &&
|
|
33
|
+
node.callee.object.property.name === "meta" &&
|
|
34
|
+
node.callee.property.type === "Identifier" &&
|
|
35
|
+
node.callee.property.name === "resolve" &&
|
|
36
|
+
node.arguments.length > 0);
|
|
37
|
+
}
|
|
38
|
+
/** Returns true if the given node is a import.meta.url expression. */
|
|
39
|
+
export function isImportMetaUrl(node) {
|
|
40
|
+
return (node.object.type === "MetaProperty" &&
|
|
41
|
+
node.object.meta.name === "import" &&
|
|
42
|
+
node.object.property.name === "meta" &&
|
|
43
|
+
node.property.type === "Identifier" &&
|
|
44
|
+
node.property.name === "url");
|
|
45
|
+
}
|
|
46
|
+
function resolveImport(specifier) {
|
|
47
|
+
return resolveObservableImport(resolveJsrImport(resolveNpmImport(specifier)));
|
|
48
|
+
}
|
|
49
|
+
export function rewriteImportExpressions(output, body, { resolveLocalImports } = {}) {
|
|
50
|
+
function rewriteImportSource(source, node = source) {
|
|
51
|
+
const value = getStringLiteralValue(source);
|
|
52
|
+
const resolution = resolveImport(value);
|
|
53
|
+
output.replaceLeft(node.start, node.end, resolveLocalImports && isLocalImport(resolution)
|
|
54
|
+
? `new URL(${JSON.stringify(resolution)}, document.baseURI).href`
|
|
55
|
+
: JSON.stringify(resolution));
|
|
56
|
+
}
|
|
57
|
+
simple(body, {
|
|
58
|
+
ImportExpression(node) {
|
|
59
|
+
const source = node.source;
|
|
60
|
+
if (isStringLiteral(source)) {
|
|
61
|
+
rewriteImportSource(source);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
MemberExpression(node) {
|
|
65
|
+
if (resolveLocalImports && isImportMetaUrl(node)) {
|
|
66
|
+
output.replaceLeft(node.start, node.end, "document.baseURI");
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
CallExpression(node) {
|
|
70
|
+
const source = node.arguments[0];
|
|
71
|
+
if (isImportMetaResolve(node) && isStringLiteral(source)) {
|
|
72
|
+
rewriteImportSource(source, node);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** Note: mutates inputs! */
|
|
78
|
+
export function rewriteImportDeclarations(output, body, inputs, { resolveLocalImports } = {}) {
|
|
79
|
+
const declarations = [];
|
|
80
|
+
simple(body, {
|
|
81
|
+
ImportDeclaration(node) {
|
|
82
|
+
if (isStringLiteral(node.source)) {
|
|
83
|
+
declarations.push([node, node.source]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
const specifiers = [];
|
|
88
|
+
const imports = [];
|
|
89
|
+
for (const [node, source] of declarations) {
|
|
90
|
+
output.delete(node.start, node.end + +(output.input[node.end] === "\n"));
|
|
91
|
+
specifiers.push(rewriteImportSpecifiers(node));
|
|
92
|
+
const value = getStringLiteralValue(source);
|
|
93
|
+
const resolution = resolveImport(value);
|
|
94
|
+
imports.push(isObservableImport(node, value)
|
|
95
|
+
? renderObservableImport(resolution, node, inputs)
|
|
96
|
+
: renderImport(resolveLocalImports && isLocalImport(resolution)
|
|
97
|
+
? `new URL(${JSON.stringify(resolution)}, document.baseURI)`
|
|
98
|
+
: JSON.stringify(resolution), node, output.input));
|
|
99
|
+
}
|
|
100
|
+
if (declarations.length > 1) {
|
|
101
|
+
output.insertLeft(0, `const [${specifiers.join(", ")}] = await Promise.all([${imports.join(", ")}]);\n`);
|
|
102
|
+
}
|
|
103
|
+
else if (declarations.length === 1) {
|
|
104
|
+
output.insertLeft(0, `const ${specifiers[0]} = await ${imports[0]};\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function renderImport(source, node, input) {
|
|
108
|
+
const names = node.specifiers.filter(isNamedSpecifier).map(getImportedName);
|
|
109
|
+
return `import(${source}${node.attributes.length > 0
|
|
110
|
+
? `, {with: {${input.slice(node.attributes[0].start, node.attributes[node.attributes.length - 1].end)}}}`
|
|
111
|
+
: ""})${names.length > 0
|
|
112
|
+
? `.then((module) => {${names.map((name) => `
|
|
113
|
+
if (!("${name}" in module)) throw new SyntaxError(\`export '${name}' not found\`);`).join("")}
|
|
114
|
+
return module;
|
|
115
|
+
})`
|
|
116
|
+
: ""}`;
|
|
117
|
+
}
|
|
118
|
+
function getLocalName(node) {
|
|
119
|
+
return node.local.name;
|
|
120
|
+
}
|
|
121
|
+
export function getImportedName(node) {
|
|
122
|
+
return node.type === "ImportDefaultSpecifier"
|
|
123
|
+
? "default"
|
|
124
|
+
: node.imported.type === "Identifier"
|
|
125
|
+
? node.imported.name
|
|
126
|
+
: node.imported.raw;
|
|
127
|
+
}
|
|
128
|
+
function isLocalImport(source) {
|
|
129
|
+
return ["./", "../", "/"].some((prefix) => source.startsWith(prefix));
|
|
130
|
+
}
|
|
131
|
+
function isNamespaceSpecifier(node) {
|
|
132
|
+
return node.type === "ImportNamespaceSpecifier";
|
|
133
|
+
}
|
|
134
|
+
function isNamedSpecifier(node) {
|
|
135
|
+
return node.type !== "ImportNamespaceSpecifier";
|
|
136
|
+
}
|
|
137
|
+
function rewriteImportSpecifiers(node) {
|
|
138
|
+
return node.specifiers.some(isNamedSpecifier)
|
|
139
|
+
? `{${node.specifiers.filter(isNamedSpecifier).map(rewriteImportSpecifier).join(", ")}}`
|
|
140
|
+
: (node.specifiers.find(isNamespaceSpecifier)?.local.name ?? "{}");
|
|
141
|
+
}
|
|
142
|
+
function rewriteImportSpecifier(node) {
|
|
143
|
+
return getImportedName(node) === getLocalName(node)
|
|
144
|
+
? getLocalName(node)
|
|
145
|
+
: `${getImportedName(node)}: ${getLocalName(node)}`;
|
|
146
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { parseCell } from "@observablehq/parser";
|
|
2
|
+
import { rewriteFileExpressions } from "./files.js";
|
|
3
|
+
import { Sourcemap } from "./sourcemap.js";
|
|
4
|
+
import { transpileJavaScript } from "./transpile.js";
|
|
5
|
+
import { simple } from "./walk.js";
|
|
6
|
+
export function transpileObservable(input, options) {
|
|
7
|
+
const cell = parseCell(input);
|
|
8
|
+
if (!cell.body)
|
|
9
|
+
return transpileJavaScript(input);
|
|
10
|
+
if (cell.tag)
|
|
11
|
+
throw new Error("tagged ojs cells are not supported");
|
|
12
|
+
const output = new Sourcemap(input).trim();
|
|
13
|
+
if (cell.body.type === "ImportDeclaration") {
|
|
14
|
+
rewriteImportSource(output, cell.body);
|
|
15
|
+
return transpileJavaScript(String(output));
|
|
16
|
+
}
|
|
17
|
+
if (options?.resolveFiles)
|
|
18
|
+
rewriteFileExpressions(output, cell.body);
|
|
19
|
+
rewriteSpecialReferences(output, cell.body);
|
|
20
|
+
const inputs = Array.from(new Set(cell.references.map(asReference)));
|
|
21
|
+
let start = "";
|
|
22
|
+
let end = "";
|
|
23
|
+
start += `${cell.async ? "async " : ""}function${cell.generator ? "*" : ""}`;
|
|
24
|
+
if (cell.id)
|
|
25
|
+
start += ` ${asReference(cell.id)}`;
|
|
26
|
+
start += `(${inputs})`;
|
|
27
|
+
if (cell.body.type !== "BlockStatement") {
|
|
28
|
+
start += "{return(";
|
|
29
|
+
end += ")}";
|
|
30
|
+
}
|
|
31
|
+
output.replaceLeft(0, cell.body.start, `${start}\n`);
|
|
32
|
+
output.replaceRight(cell.body.end, input.length, `\n${end}`);
|
|
33
|
+
return {
|
|
34
|
+
body: String(output),
|
|
35
|
+
inputs,
|
|
36
|
+
output: cell.id ? asDeclaration(cell.id) : undefined,
|
|
37
|
+
autodisplay: true,
|
|
38
|
+
automutable: cell.id?.type === "MutableExpression",
|
|
39
|
+
autoview: cell.id?.type === "ViewExpression"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function rewriteImportSource(output, body) {
|
|
43
|
+
const specifier = body.source.value;
|
|
44
|
+
if (typeof specifier === "string" && !/^\w+:/.test(specifier))
|
|
45
|
+
output.insertLeft(body.source.start + 1, "observable:");
|
|
46
|
+
output.insertRight(body.end, ";");
|
|
47
|
+
}
|
|
48
|
+
// Rewrite viewof x ↦ viewof$x, and mutable x ↦ mutable$x.value.
|
|
49
|
+
function rewriteSpecialReferences(output, body) {
|
|
50
|
+
simple(body, {
|
|
51
|
+
MutableExpression(node) {
|
|
52
|
+
output.replaceLeft(node.start, node.end, `${asReference(node)}.value`);
|
|
53
|
+
},
|
|
54
|
+
ViewExpression(node) {
|
|
55
|
+
output.replaceLeft(node.start, node.end, asReference(node));
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function asReference(ref) {
|
|
60
|
+
return ref.type === "ViewExpression"
|
|
61
|
+
? `viewof$${ref.id.name}`
|
|
62
|
+
: ref.type === "MutableExpression"
|
|
63
|
+
? `mutable$${ref.id.name}`
|
|
64
|
+
: ref.name;
|
|
65
|
+
}
|
|
66
|
+
function asDeclaration(ref) {
|
|
67
|
+
return ref.type === "ViewExpression"
|
|
68
|
+
? `viewof$${ref.id.name}`
|
|
69
|
+
: ref.type === "MutableExpression"
|
|
70
|
+
? `mutable ${ref.id.name}` // "initial x", really
|
|
71
|
+
: ref.name;
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Expression, Identifier, Options, Program } from "acorn";
|
|
2
|
+
export declare const acornOptions: Options;
|
|
3
|
+
export interface JavaScriptCell {
|
|
4
|
+
body: Program | Expression;
|
|
5
|
+
declarations: Identifier[] | null;
|
|
6
|
+
references: Identifier[];
|
|
7
|
+
expression: boolean;
|
|
8
|
+
async: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function maybeParseJavaScript(input: string): JavaScriptCell | undefined;
|
|
11
|
+
export declare function parseJavaScript(input: string): JavaScriptCell;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Parser, tokTypes } from "acorn";
|
|
2
|
+
import { checkExports } from "./imports.js";
|
|
3
|
+
import { findReferences } from "./references.js";
|
|
4
|
+
import { checkAssignments } from "./assignments.js";
|
|
5
|
+
import { findDeclarations } from "./declarations.js";
|
|
6
|
+
import { findAwaits } from "./awaits.js";
|
|
7
|
+
export const acornOptions = {
|
|
8
|
+
ecmaVersion: "latest",
|
|
9
|
+
sourceType: "module"
|
|
10
|
+
};
|
|
11
|
+
export function maybeParseJavaScript(input) {
|
|
12
|
+
try {
|
|
13
|
+
return parseJavaScript(input);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (!(error instanceof SyntaxError))
|
|
17
|
+
throw error;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function parseJavaScript(input) {
|
|
22
|
+
let expression = maybeParseExpression(input); // first attempt to parse as expression
|
|
23
|
+
if (expression?.type === "ClassExpression" && expression.id)
|
|
24
|
+
expression = null; // treat named class as program
|
|
25
|
+
if (expression?.type === "FunctionExpression" && expression.id)
|
|
26
|
+
expression = null; // treat named function as program
|
|
27
|
+
const body = expression ?? parseProgram(input); // otherwise parse as a program
|
|
28
|
+
checkExports(body, input);
|
|
29
|
+
const references = findReferences(body);
|
|
30
|
+
checkAssignments(body, references, input);
|
|
31
|
+
return {
|
|
32
|
+
body,
|
|
33
|
+
declarations: expression ? null : findDeclarations(body, input),
|
|
34
|
+
references,
|
|
35
|
+
expression: !!expression,
|
|
36
|
+
async: findAwaits(body).length > 0
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseProgram(input) {
|
|
40
|
+
return Parser.parse(input, acornOptions);
|
|
41
|
+
}
|
|
42
|
+
function maybeParseExpression(input) {
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
const parser = new Parser(acornOptions, input, 0); // private constructor
|
|
45
|
+
parser.nextToken();
|
|
46
|
+
try {
|
|
47
|
+
const node = parser.parseExpression();
|
|
48
|
+
return parser.type === tokTypes.eof ? node : null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Identifier, Node } from "acorn";
|
|
2
|
+
export declare function findReferences(node: Node, { globals, filterReference, filterDeclaration }?: {
|
|
3
|
+
globals?: Set<string>;
|
|
4
|
+
filterReference?: (identifier: Identifier) => boolean;
|
|
5
|
+
filterDeclaration?: (identifier: {
|
|
6
|
+
name: string;
|
|
7
|
+
}) => boolean;
|
|
8
|
+
}): Identifier[];
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { defaultGlobals } from "./globals.js";
|
|
2
|
+
import { ancestor } from "./walk.js";
|
|
3
|
+
function isScope(node) {
|
|
4
|
+
return (node.type === "FunctionExpression" ||
|
|
5
|
+
node.type === "FunctionDeclaration" ||
|
|
6
|
+
node.type === "ArrowFunctionExpression" ||
|
|
7
|
+
node.type === "Program");
|
|
8
|
+
}
|
|
9
|
+
// prettier-ignore
|
|
10
|
+
function isBlockScope(node) {
|
|
11
|
+
return (node.type === "BlockStatement" ||
|
|
12
|
+
node.type === "SwitchStatement" ||
|
|
13
|
+
node.type === "ForInStatement" ||
|
|
14
|
+
node.type === "ForOfStatement" ||
|
|
15
|
+
node.type === "ForStatement" ||
|
|
16
|
+
isScope(node));
|
|
17
|
+
}
|
|
18
|
+
export function findReferences(node, { globals = defaultGlobals, filterReference = (identifier) => !globals.has(identifier.name), filterDeclaration = () => true } = {}) {
|
|
19
|
+
const locals = new Map();
|
|
20
|
+
const references = [];
|
|
21
|
+
function hasLocal(node, name) {
|
|
22
|
+
const l = locals.get(node);
|
|
23
|
+
return l ? l.has(name) : false;
|
|
24
|
+
}
|
|
25
|
+
function declareLocal(node, id) {
|
|
26
|
+
if (!filterDeclaration(id))
|
|
27
|
+
return;
|
|
28
|
+
const l = locals.get(node);
|
|
29
|
+
if (l)
|
|
30
|
+
l.add(id.name);
|
|
31
|
+
else
|
|
32
|
+
locals.set(node, new Set([id.name]));
|
|
33
|
+
}
|
|
34
|
+
function declareClass(node) {
|
|
35
|
+
if (node.id)
|
|
36
|
+
declareLocal(node, node.id);
|
|
37
|
+
}
|
|
38
|
+
function declareFunction(node) {
|
|
39
|
+
node.params.forEach((param) => declarePattern(param, node));
|
|
40
|
+
if (node.id)
|
|
41
|
+
declareLocal(node, node.id);
|
|
42
|
+
if (node.type !== "ArrowFunctionExpression")
|
|
43
|
+
declareLocal(node, { name: "arguments" });
|
|
44
|
+
}
|
|
45
|
+
function declareCatchClause(node) {
|
|
46
|
+
if (node.param)
|
|
47
|
+
declarePattern(node.param, node);
|
|
48
|
+
}
|
|
49
|
+
function declarePattern(node, parent) {
|
|
50
|
+
switch (node.type) {
|
|
51
|
+
case "Identifier":
|
|
52
|
+
declareLocal(parent, node);
|
|
53
|
+
break;
|
|
54
|
+
case "ObjectPattern":
|
|
55
|
+
node.properties.forEach((node) => declarePattern(node.type === "Property" ? node.value : node, parent));
|
|
56
|
+
break;
|
|
57
|
+
case "ArrayPattern":
|
|
58
|
+
node.elements.forEach((node) => node && declarePattern(node, parent));
|
|
59
|
+
break;
|
|
60
|
+
case "RestElement":
|
|
61
|
+
declarePattern(node.argument, parent);
|
|
62
|
+
break;
|
|
63
|
+
case "AssignmentPattern":
|
|
64
|
+
declarePattern(node.left, parent);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
ancestor(node, {
|
|
69
|
+
VariableDeclaration(node, _state, parents) {
|
|
70
|
+
let parent = null;
|
|
71
|
+
for (let i = parents.length - 1; i >= 0 && parent === null; --i) {
|
|
72
|
+
if (node.kind === "var" ? isScope(parents[i]) : isBlockScope(parents[i])) {
|
|
73
|
+
parent = parents[i];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
node.declarations.forEach((declaration) => declarePattern(declaration.id, parent));
|
|
77
|
+
},
|
|
78
|
+
FunctionDeclaration(node, _state, parents) {
|
|
79
|
+
let parent = null;
|
|
80
|
+
for (let i = parents.length - 2; i >= 0 && parent === null; --i) {
|
|
81
|
+
if (isScope(parents[i])) {
|
|
82
|
+
parent = parents[i];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (node.id)
|
|
86
|
+
declareLocal(parent, node.id);
|
|
87
|
+
declareFunction(node);
|
|
88
|
+
},
|
|
89
|
+
FunctionExpression: declareFunction,
|
|
90
|
+
ArrowFunctionExpression: declareFunction,
|
|
91
|
+
ClassDeclaration(node, _state, parents) {
|
|
92
|
+
let parent = null;
|
|
93
|
+
for (let i = parents.length - 2; i >= 0 && parent === null; --i) {
|
|
94
|
+
if (isScope(parents[i])) {
|
|
95
|
+
parent = parents[i];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (node.id)
|
|
99
|
+
declareLocal(parent, node.id);
|
|
100
|
+
},
|
|
101
|
+
ClassExpression: declareClass,
|
|
102
|
+
CatchClause: declareCatchClause,
|
|
103
|
+
ImportDeclaration(node, _state, [root]) {
|
|
104
|
+
node.specifiers.forEach((specifier) => declareLocal(root, specifier.local));
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
function identifier(node, _state, parents) {
|
|
108
|
+
const name = node.name;
|
|
109
|
+
if (name === "undefined")
|
|
110
|
+
return;
|
|
111
|
+
for (let i = parents.length - 2; i >= 0; --i) {
|
|
112
|
+
if (hasLocal(parents[i], name)) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (filterReference(node)) {
|
|
117
|
+
references.push(node);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ancestor(node, {
|
|
121
|
+
Pattern(node, _state, parents) {
|
|
122
|
+
if (node.type === "Identifier") {
|
|
123
|
+
identifier(node, _state, parents);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
Identifier: identifier
|
|
127
|
+
});
|
|
128
|
+
return references;
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { expect, test } from "vitest";
|
|
2
|
+
import { parseJavaScript } from "./parse.js";
|
|
3
|
+
function find(input) {
|
|
4
|
+
return parseJavaScript(input).references;
|
|
5
|
+
}
|
|
6
|
+
test("finds references in expressions", () => {
|
|
7
|
+
expect(find(`foo + bar`)).toMatchSnapshot();
|
|
8
|
+
});
|
|
9
|
+
test("finds references in statements", () => {
|
|
10
|
+
expect(find(`foo + bar;`)).toMatchSnapshot();
|
|
11
|
+
});
|
|
12
|
+
test("finds multiple references", () => {
|
|
13
|
+
expect(find(`const a = b + c;\nconst d = c - b;`)).toMatchSnapshot();
|
|
14
|
+
});
|
|
15
|
+
test("doesn’t consider the identifier a reference", () => {
|
|
16
|
+
expect(find(`function foo() { return bar; }`)).toMatchSnapshot();
|
|
17
|
+
});
|
|
18
|
+
test("local variables can mask references", () => {
|
|
19
|
+
expect(find(`{ let foo; foo + bar; }`)).toMatchSnapshot();
|
|
20
|
+
});
|
|
21
|
+
test("local variables can not mask references", () => {
|
|
22
|
+
expect(find(`{ foo + bar; { let foo; } }`)).toMatchSnapshot();
|
|
23
|
+
});
|
|
24
|
+
test("function parameters can mask references", () => {
|
|
25
|
+
expect(find(`foo => foo + bar`)).toMatchSnapshot();
|
|
26
|
+
});
|
|
27
|
+
test("function rest parameters can mask references", () => {
|
|
28
|
+
expect(find(`(...foo) => foo + bar`)).toMatchSnapshot();
|
|
29
|
+
});
|
|
30
|
+
test("destructured variables can mask references", () => {
|
|
31
|
+
expect(find(`{ let {foo} = null; foo + bar; }`)).toMatchSnapshot();
|
|
32
|
+
});
|
|
33
|
+
test("destructured rest variables can mask references", () => {
|
|
34
|
+
expect(find(`{ let {...foo} = null; foo + bar; }`)).toMatchSnapshot();
|
|
35
|
+
});
|
|
36
|
+
test("ignores globals", () => {
|
|
37
|
+
expect(find(`window + bar`)).toMatchSnapshot();
|
|
38
|
+
});
|