@observablehq/notebook-kit 2.1.9 → 2.2.1
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 +3 -2
- package/dist/src/javascript/declarations.js +7 -2
- package/dist/src/javascript/imports.d.ts +1 -0
- package/dist/src/javascript/imports.js +16 -5
- package/dist/src/javascript/parse.d.ts +5 -4
- package/dist/src/javascript/parse.js +27 -11
- package/dist/src/javascript/references.js +6 -2
- package/dist/src/javascript/sourcemap.d.ts +1 -0
- package/dist/src/javascript/sourcemap.js +7 -0
- package/dist/src/javascript/template.js +5 -2
- package/dist/src/javascript/transpile.d.ts +1 -0
- package/dist/src/javascript/transpile.js +17 -7
- package/dist/src/javascript/typescript.d.ts +7 -1
- package/dist/src/javascript/typescript.js +219 -104
- package/dist/src/javascript/walk.d.ts +2 -2
- package/dist/src/javascript/walk.js +41 -9
- package/dist/src/runtime/stdlib/files/buffer.d.ts +1 -1
- package/dist/src/runtime/stdlib/files/buffer.js +2 -7
- package/dist/src/runtime/stdlib/files/text.d.ts +1 -1
- package/dist/src/runtime/stdlib/files/text.js +2 -7
- package/dist/src/styles/inspector.css +1 -4
- package/package.json +3 -2
package/dist/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1
|
|
8
|
+
"version": "2.2.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "TZ=America/Los_Angeles vitest",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"@observablehq/parser": "^6.1.0",
|
|
57
57
|
"@observablehq/runtime": "^6.0.0",
|
|
58
58
|
"@sindresorhus/slugify": "^2.2.1",
|
|
59
|
+
"@sveltejs/acorn-typescript": "^1.0.11",
|
|
59
60
|
"acorn": "^8.16.0",
|
|
60
61
|
"acorn-walk": "^8.3.5",
|
|
61
62
|
"jsdom": "^26.1.0",
|
|
62
63
|
"markdown-it": "^14.1.1",
|
|
63
64
|
"markdown-it-anchor": "^9.2.0",
|
|
64
|
-
"typescript": "^5.9.3",
|
|
65
65
|
"vite": "^7.0.0 || ^8.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"postgres": "^3.4.9",
|
|
81
81
|
"snowflake-sdk": "^2.4.2",
|
|
82
82
|
"tsx": "^4.22.3",
|
|
83
|
+
"typescript": "^5.9.3",
|
|
83
84
|
"typescript-eslint": "^8.59.4",
|
|
84
85
|
"vitest": "^4.1.7"
|
|
85
86
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isTypeImport } from "./imports.js";
|
|
1
2
|
import { defaultGlobals } from "./globals.js";
|
|
2
3
|
import { syntaxError } from "./syntaxError.js";
|
|
3
4
|
export function findDeclarations(node, input) {
|
|
@@ -14,7 +15,7 @@ export function findDeclarations(node, input) {
|
|
|
14
15
|
declareLocal(node);
|
|
15
16
|
break;
|
|
16
17
|
case "ObjectPattern":
|
|
17
|
-
node.properties.forEach((node) => declarePattern(node.type === "Property" ? node.value : node));
|
|
18
|
+
node.properties.forEach((node) => declarePattern(node.type === "Property" ? node.value : node)); // prettier-ignore
|
|
18
19
|
break;
|
|
19
20
|
case "ArrayPattern":
|
|
20
21
|
node.elements.forEach((node) => node && declarePattern(node));
|
|
@@ -37,7 +38,11 @@ export function findDeclarations(node, input) {
|
|
|
37
38
|
declareLocal(child.id);
|
|
38
39
|
break;
|
|
39
40
|
case "ImportDeclaration":
|
|
40
|
-
|
|
41
|
+
if (isTypeImport(child))
|
|
42
|
+
break;
|
|
43
|
+
child.specifiers
|
|
44
|
+
.filter((node) => !isTypeImport(node))
|
|
45
|
+
.forEach((node) => declareLocal(node.local));
|
|
41
46
|
break;
|
|
42
47
|
}
|
|
43
48
|
}
|
|
@@ -13,6 +13,7 @@ export declare function hasImportDeclaration(body: Node): boolean;
|
|
|
13
13
|
export declare function isImportMetaResolve(node: CallExpression): boolean;
|
|
14
14
|
/** Returns true if the given node is an import.meta.url expression. */
|
|
15
15
|
export declare function isImportMetaUrl(node: MemberExpression): boolean;
|
|
16
|
+
export declare function isTypeImport(node: AnyImportSpecifier | ImportDeclaration): boolean;
|
|
16
17
|
export type RewriteImportOptions = {
|
|
17
18
|
/** If true, resolve local imports relative to document.baseURI. */
|
|
18
19
|
resolveLocalImports?: boolean;
|
|
@@ -44,6 +44,9 @@ export function isImportMetaUrl(node) {
|
|
|
44
44
|
node.property.type === "Identifier" &&
|
|
45
45
|
node.property.name === "url");
|
|
46
46
|
}
|
|
47
|
+
export function isTypeImport(node) {
|
|
48
|
+
return node.importKind === "type";
|
|
49
|
+
}
|
|
47
50
|
function resolveImport(specifier) {
|
|
48
51
|
return resolveObservableImport(resolveJsrImport(resolveNpmImport(specifier)));
|
|
49
52
|
}
|
|
@@ -80,7 +83,10 @@ export function rewriteImportDeclarations(output, body, inputs, { resolveLocalIm
|
|
|
80
83
|
const declarations = [];
|
|
81
84
|
simple(body, {
|
|
82
85
|
ImportDeclaration(node) {
|
|
83
|
-
if (
|
|
86
|
+
if (isTypeImport(node)) {
|
|
87
|
+
output.delete(node.start, node.end);
|
|
88
|
+
}
|
|
89
|
+
else if (isStringLiteral(node.source)) {
|
|
84
90
|
declarations.push([node, node.source]);
|
|
85
91
|
}
|
|
86
92
|
}
|
|
@@ -106,8 +112,11 @@ export function rewriteImportDeclarations(output, body, inputs, { resolveLocalIm
|
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
function renderImport(source, node, input) {
|
|
109
|
-
const names = node.specifiers
|
|
110
|
-
|
|
115
|
+
const names = node.specifiers
|
|
116
|
+
.filter((node) => !isTypeImport(node))
|
|
117
|
+
.filter(isNamedSpecifier)
|
|
118
|
+
.map(getImportedName);
|
|
119
|
+
return `import(${source}${node.attributes?.length > 0
|
|
111
120
|
? `, {with: {${input.slice(node.attributes[0].start, node.attributes[node.attributes.length - 1].end)}}}`
|
|
112
121
|
: ""})${names.length > 0
|
|
113
122
|
? `.then((module) => {${names
|
|
@@ -140,7 +149,7 @@ function isNamedSpecifier(node) {
|
|
|
140
149
|
function rewriteImportSpecifiers(node) {
|
|
141
150
|
return node.specifiers.some(isNamedSpecifier)
|
|
142
151
|
? `{${flatMapImportSpecifiers(node, rewriteImportSpecifier).join(", ")}}`
|
|
143
|
-
: (node.specifiers.find(isNamespaceSpecifier)?.local.name ?? "{}");
|
|
152
|
+
: (node.specifiers.find((node) => isNamespaceSpecifier(node) && !isTypeImport(node))?.local.name ?? "{}"); // prettier-ignore
|
|
144
153
|
}
|
|
145
154
|
export function flatMapImportSpecifiers(node, f) {
|
|
146
155
|
return node.specifiers.flatMap((node) => {
|
|
@@ -150,7 +159,9 @@ export function flatMapImportSpecifiers(node, f) {
|
|
|
150
159
|
? [f(node), f(toSpecialImportSpecifier(node, "viewof"))]
|
|
151
160
|
: isMutableImport(node)
|
|
152
161
|
? [f(node), f(toSpecialImportSpecifier(node, "mutable"))]
|
|
153
|
-
:
|
|
162
|
+
: isTypeImport(node)
|
|
163
|
+
? []
|
|
164
|
+
: f(node);
|
|
154
165
|
});
|
|
155
166
|
}
|
|
156
167
|
function rewriteImportSpecifier(node) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Expression, Identifier,
|
|
1
|
+
import type { Expression, Identifier, Program } from "acorn";
|
|
2
2
|
import type { FeatureExpression } from "./features.js";
|
|
3
|
-
export declare const acornOptions: Options;
|
|
4
3
|
export interface JavaScriptCell {
|
|
5
4
|
body: Program | Expression;
|
|
6
5
|
declarations: Identifier[] | null;
|
|
@@ -11,5 +10,7 @@ export interface JavaScriptCell {
|
|
|
11
10
|
expression: boolean;
|
|
12
11
|
async: boolean;
|
|
13
12
|
}
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
13
|
+
type Dialect = "js" | "ts";
|
|
14
|
+
export declare function maybeParseJavaScript(input: string, dialect?: Dialect): JavaScriptCell | undefined;
|
|
15
|
+
export declare function parseJavaScript(input: string, dialect?: Dialect): JavaScriptCell;
|
|
16
|
+
export {};
|
|
@@ -1,15 +1,30 @@
|
|
|
1
|
+
import { tsPlugin } from "@sveltejs/acorn-typescript";
|
|
1
2
|
import { Parser, tokTypes } from "acorn";
|
|
2
3
|
import { findAwaits } from "./awaits.js";
|
|
3
4
|
import { findDeclarations } from "./declarations.js";
|
|
4
5
|
import { findFeatures } from "./features.js";
|
|
5
6
|
import { findReferences } from "./references.js";
|
|
6
|
-
|
|
7
|
+
import { checkErasableSyntax } from "./typescript.js";
|
|
8
|
+
const jsOptions = {
|
|
7
9
|
ecmaVersion: "latest",
|
|
8
10
|
sourceType: "module"
|
|
9
11
|
};
|
|
10
|
-
|
|
12
|
+
const tsOptions = {
|
|
13
|
+
...jsOptions,
|
|
14
|
+
locations: true,
|
|
15
|
+
preserveParens: true // needed for accurate type stripping
|
|
16
|
+
};
|
|
17
|
+
const JsParser = Parser;
|
|
18
|
+
const TsParser = Parser.extend(tsPlugin());
|
|
19
|
+
function getParser(dialect = "js") {
|
|
20
|
+
return dialect === "ts" ? TsParser : JsParser;
|
|
21
|
+
}
|
|
22
|
+
function getOptions(dialect = "js") {
|
|
23
|
+
return dialect === "ts" ? tsOptions : jsOptions;
|
|
24
|
+
}
|
|
25
|
+
export function maybeParseJavaScript(input, dialect) {
|
|
11
26
|
try {
|
|
12
|
-
return parseJavaScript(input);
|
|
27
|
+
return parseJavaScript(input, dialect);
|
|
13
28
|
}
|
|
14
29
|
catch (error) {
|
|
15
30
|
if (!(error instanceof SyntaxError))
|
|
@@ -17,13 +32,15 @@ export function maybeParseJavaScript(input) {
|
|
|
17
32
|
return;
|
|
18
33
|
}
|
|
19
34
|
}
|
|
20
|
-
export function parseJavaScript(input) {
|
|
21
|
-
let expression = maybeParseExpression(input); // first attempt to parse as expression
|
|
35
|
+
export function parseJavaScript(input, dialect) {
|
|
36
|
+
let expression = maybeParseExpression(input, dialect); // first attempt to parse as expression
|
|
22
37
|
if (expression?.type === "ClassExpression" && expression.id)
|
|
23
38
|
expression = null; // treat named class as program
|
|
24
39
|
if (expression?.type === "FunctionExpression" && expression.id)
|
|
25
40
|
expression = null; // treat named function as program
|
|
26
|
-
const body = expression ?? parseProgram(input); // otherwise parse as a program
|
|
41
|
+
const body = expression ?? parseProgram(input, dialect); // otherwise parse as a program
|
|
42
|
+
if (dialect === "ts")
|
|
43
|
+
checkErasableSyntax(body, input);
|
|
27
44
|
return {
|
|
28
45
|
body,
|
|
29
46
|
declarations: expression ? null : findDeclarations(body, input),
|
|
@@ -35,12 +52,11 @@ export function parseJavaScript(input) {
|
|
|
35
52
|
async: findAwaits(body).length > 0
|
|
36
53
|
};
|
|
37
54
|
}
|
|
38
|
-
function parseProgram(input) {
|
|
39
|
-
return
|
|
55
|
+
function parseProgram(input, dialect) {
|
|
56
|
+
return getParser(dialect).parse(input, getOptions(dialect));
|
|
40
57
|
}
|
|
41
|
-
function maybeParseExpression(input) {
|
|
42
|
-
// eslint-disable-
|
|
43
|
-
const parser = new Parser(acornOptions, input, 0); // private constructor
|
|
58
|
+
function maybeParseExpression(input, dialect) {
|
|
59
|
+
const parser = new (getParser(dialect))(getOptions(dialect), input, 0); // eslint-disable-line @typescript-eslint/no-explicit-any -- private constructor
|
|
44
60
|
parser.nextToken();
|
|
45
61
|
try {
|
|
46
62
|
const node = parser.parseExpression();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { checkAssignments } from "./assignments.js";
|
|
2
2
|
import { defaultGlobals } from "./globals.js";
|
|
3
|
-
import { checkExports } from "./imports.js";
|
|
3
|
+
import { checkExports, isTypeImport } from "./imports.js";
|
|
4
4
|
import { ancestor } from "./walk.js";
|
|
5
5
|
function isScope(node) {
|
|
6
6
|
return (node.type === "FunctionExpression" ||
|
|
@@ -102,7 +102,11 @@ export function findReferences(node, { input, globals = defaultGlobals, filterRe
|
|
|
102
102
|
ClassExpression: declareClass,
|
|
103
103
|
CatchClause: declareCatchClause,
|
|
104
104
|
ImportDeclaration(node, _state, [root]) {
|
|
105
|
-
|
|
105
|
+
if (isTypeImport(node))
|
|
106
|
+
return;
|
|
107
|
+
node.specifiers
|
|
108
|
+
.filter((specifier) => !isTypeImport(specifier))
|
|
109
|
+
.forEach((specifier) => declareLocal(root, specifier.local));
|
|
106
110
|
}
|
|
107
111
|
});
|
|
108
112
|
function identifier(node, _state, parents) {
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
import { lineBreakG } from "acorn";
|
|
3
3
|
export class Sourcemap {
|
|
4
4
|
constructor(input) {
|
|
5
|
+
Object.defineProperty(this, "length", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
writable: true,
|
|
9
|
+
value: void 0
|
|
10
|
+
});
|
|
5
11
|
Object.defineProperty(this, "input", {
|
|
6
12
|
enumerable: true,
|
|
7
13
|
configurable: true,
|
|
@@ -14,6 +20,7 @@ export class Sourcemap {
|
|
|
14
20
|
writable: true,
|
|
15
21
|
value: void 0
|
|
16
22
|
});
|
|
23
|
+
this.length = input.length;
|
|
17
24
|
this.input = input;
|
|
18
25
|
this._edits = [];
|
|
19
26
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { TokContext, tokTypes as tt, Parser } from "acorn";
|
|
2
2
|
import { getInterpreterMethod, isInterpreter } from "../lib/interpreters.js";
|
|
3
|
-
import { acornOptions } from "./parse.js";
|
|
4
3
|
import { Sourcemap } from "./sourcemap.js";
|
|
4
|
+
const options = {
|
|
5
|
+
ecmaVersion: "latest",
|
|
6
|
+
sourceType: "module"
|
|
7
|
+
};
|
|
5
8
|
const CODE_DOLLAR = 36;
|
|
6
9
|
const CODE_BACKSLASH = 92;
|
|
7
10
|
const CODE_BACKTICK = 96;
|
|
@@ -68,7 +71,7 @@ class TemplateCellParser extends Parser {
|
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
73
|
export function parseTemplate(input) {
|
|
71
|
-
return TemplateCellParser.parse(input,
|
|
74
|
+
return TemplateCellParser.parse(input, options);
|
|
72
75
|
}
|
|
73
76
|
export function transpileTemplate(input, tag = "", raw = false) {
|
|
74
77
|
let cell;
|
|
@@ -30,4 +30,5 @@ export type TranspileOptions = {
|
|
|
30
30
|
/** @deprecated */
|
|
31
31
|
export declare function transpile(input: string, mode: Cell["mode"], options?: TranspileOptions): TranspiledJavaScript;
|
|
32
32
|
export declare function transpile(input: Cell, options?: TranspileOptions): TranspiledJavaScript;
|
|
33
|
+
export declare function transpileTypeScript(input: string, options?: TranspileOptions): TranspiledJavaScript;
|
|
33
34
|
export declare function transpileJavaScript(input: string, options?: TranspileOptions): TranspiledJavaScript;
|
|
@@ -6,7 +6,7 @@ import { transpileObservable } from "./observable.js";
|
|
|
6
6
|
import { parseJavaScript } from "./parse.js";
|
|
7
7
|
import { Sourcemap } from "./sourcemap.js";
|
|
8
8
|
import { transpileTemplate } from "./template.js";
|
|
9
|
-
import {
|
|
9
|
+
import { stripTypes } from "./typescript.js";
|
|
10
10
|
export function transpile(input, mode, options) {
|
|
11
11
|
let cell;
|
|
12
12
|
if (typeof input === "string") {
|
|
@@ -20,7 +20,7 @@ export function transpile(input, mode, options) {
|
|
|
20
20
|
input = cell.value;
|
|
21
21
|
}
|
|
22
22
|
const transpiled = mode === "ts"
|
|
23
|
-
?
|
|
23
|
+
? transpileTypeScript(input, options)
|
|
24
24
|
: mode === "ojs"
|
|
25
25
|
? transpileObservable(input, options)
|
|
26
26
|
: mode !== "js"
|
|
@@ -38,14 +38,24 @@ export function transpile(input, mode, options) {
|
|
|
38
38
|
}
|
|
39
39
|
return transpiled;
|
|
40
40
|
}
|
|
41
|
+
export function transpileTypeScript(input, options) {
|
|
42
|
+
const cell = parseJavaScript(input, "ts");
|
|
43
|
+
const output = new Sourcemap(input);
|
|
44
|
+
stripTypes(cell, output);
|
|
45
|
+
return transpileCell(cell, output, options);
|
|
46
|
+
}
|
|
41
47
|
export function transpileJavaScript(input, options) {
|
|
42
|
-
const cell = parseJavaScript(input);
|
|
48
|
+
const cell = parseJavaScript(input, "js");
|
|
49
|
+
const output = new Sourcemap(input);
|
|
50
|
+
return transpileCell(cell, output, options);
|
|
51
|
+
}
|
|
52
|
+
function transpileCell(cell, output, options) {
|
|
43
53
|
let async = cell.async;
|
|
44
54
|
const inputs = Array.from(new Set(cell.references.map((r) => r.name)));
|
|
45
55
|
if (hasImportDeclaration(cell.body))
|
|
46
56
|
async = true;
|
|
47
57
|
const outputs = Array.from(new Set(cell.declarations?.map((r) => r.name)));
|
|
48
|
-
|
|
58
|
+
output.trim();
|
|
49
59
|
rewriteImportDeclarations(output, cell.body, inputs, options);
|
|
50
60
|
rewriteImportExpressions(output, cell.body, options);
|
|
51
61
|
if (options?.resolveFiles)
|
|
@@ -54,10 +64,10 @@ export function transpileJavaScript(input, options) {
|
|
|
54
64
|
output.insertLeft(0, `return (\n`);
|
|
55
65
|
output.insertLeft(0, `${async ? "async " : ""}(${inputs.map(deat)}) => {\n`);
|
|
56
66
|
if (outputs.length > 0)
|
|
57
|
-
output.insertRight(
|
|
67
|
+
output.insertRight(output.length, `\nreturn {${outputs}};`);
|
|
58
68
|
if (cell.expression)
|
|
59
|
-
output.insertRight(
|
|
60
|
-
output.insertRight(
|
|
69
|
+
output.insertRight(output.length, `\n)`);
|
|
70
|
+
output.insertRight(output.length, "\n}");
|
|
61
71
|
const body = String(output);
|
|
62
72
|
const autodisplay = cell.expression && !(inputs.includes("display") || inputs.includes("view"));
|
|
63
73
|
const files = new Set(cell.files.map((f) => f.argument));
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Node } from "acorn";
|
|
2
|
+
import type { JavaScriptCell } from "./parse.js";
|
|
3
|
+
import type { Sourcemap } from "./sourcemap.js";
|
|
4
|
+
/** Throws a SyntaxError for any non-erasable TypeScript syntax. */
|
|
5
|
+
export declare function checkErasableSyntax(node: Node, input: string): void;
|
|
6
|
+
/** Erases TypeScript-specific syntax from output, leaving equivalent JavaScript. */
|
|
7
|
+
export declare function stripTypes(cell: JavaScriptCell, output: Sourcemap): void;
|
|
@@ -1,112 +1,227 @@
|
|
|
1
|
-
import { tokenizer
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { tokenizer, tokTypes } from "acorn";
|
|
2
|
+
import { base, recursive } from "acorn-walk";
|
|
3
|
+
import { syntaxError } from "./syntaxError.js";
|
|
4
|
+
import { simple } from "./walk.js";
|
|
5
|
+
// TypeScript-only modifier keywords; static/get/set/async are valid JavaScript
|
|
6
|
+
// and so are deliberately excluded (and preserved).
|
|
7
|
+
const MODIFIERS = new Set([
|
|
8
|
+
"public",
|
|
9
|
+
"private",
|
|
10
|
+
"protected",
|
|
11
|
+
"readonly",
|
|
12
|
+
"override",
|
|
13
|
+
"declare",
|
|
14
|
+
"abstract"
|
|
15
|
+
]);
|
|
16
|
+
const checkErasableVisitors = {
|
|
17
|
+
TSEnumDeclaration: checkErasable,
|
|
18
|
+
TSModuleDeclaration: checkErasable,
|
|
19
|
+
TSParameterProperty: checkErasable,
|
|
20
|
+
TSImportEqualsDeclaration: checkErasable,
|
|
21
|
+
TSExportAssignment: checkErasable
|
|
8
22
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
23
|
+
/** Throws a SyntaxError for any non-erasable TypeScript syntax. */
|
|
24
|
+
export function checkErasableSyntax(node, input) {
|
|
25
|
+
simple(node, checkErasableVisitors, input);
|
|
26
|
+
}
|
|
27
|
+
function checkErasable(node, input) {
|
|
28
|
+
if (node.declare)
|
|
29
|
+
return; // ambient declarations are erasable
|
|
30
|
+
throw syntaxError(`unsupported syntax: ${node.type}`, node, input);
|
|
31
|
+
}
|
|
32
|
+
/** Erases TypeScript-specific syntax from output, leaving equivalent JavaScript. */
|
|
33
|
+
export function stripTypes(cell, output) {
|
|
34
|
+
// The AST is traversed with acorn-walk’s recursive walker. Because acorn-walk’s
|
|
35
|
+
// base visitors only know about (and therefore only recurse into) the value
|
|
36
|
+
// children of each node, type annotations and generics are naturally skipped;
|
|
37
|
+
// we simply delete their source text from the output without descending into
|
|
38
|
+
// them. TypeScript-specific nodes that acorn-walk doesn’t know about (expression
|
|
39
|
+
// wrappers such as `x as T`, type-only declarations, parameter properties) are
|
|
40
|
+
// handled by the dedicated visitors below.
|
|
41
|
+
//
|
|
42
|
+
// Positions are taken from the AST wherever possible. A handful of tokens have
|
|
43
|
+
// no dedicated AST node (modifier keywords, the optional `?` and definite `!`
|
|
44
|
+
// markers), so they are located by re-tokenizing the small, AST-bounded span in
|
|
45
|
+
// which they must occur. Re-tokenizing (rather than string matching) correctly
|
|
46
|
+
// ignores comments and string literals within that span.
|
|
47
|
+
recursive(cell.body, output, stripTypesVisitors);
|
|
48
|
+
}
|
|
49
|
+
const stripTypesVisitors = {
|
|
50
|
+
// Type-only declarations, erased entirely.
|
|
51
|
+
TSTypeAliasDeclaration: deleteStatement,
|
|
52
|
+
TSInterfaceDeclaration: deleteStatement,
|
|
53
|
+
TSDeclareFunction: deleteStatement,
|
|
54
|
+
TSDeclareMethod: deleteStatement,
|
|
55
|
+
TSIndexSignature: deleteStatement,
|
|
56
|
+
TSNamespaceExportDeclaration: deleteStatement,
|
|
57
|
+
// Ambient (declare) enums and namespaces; non-ambient forms, which require
|
|
58
|
+
// runtime support, are rejected earlier by checkErasableSyntax.
|
|
59
|
+
TSEnumDeclaration: deleteStatement,
|
|
60
|
+
TSModuleDeclaration: deleteStatement,
|
|
61
|
+
// TypeScript expression wrappers, unwrapped to their value expression.
|
|
62
|
+
TSAsExpression: unwrapType, // x as T
|
|
63
|
+
TSSatisfiesExpression: unwrapType, // x satisfies T
|
|
64
|
+
TSNonNullExpression: unwrapType, // x!
|
|
65
|
+
TSInstantiationExpression: unwrapType, // f<T>
|
|
66
|
+
TSTypeAssertion: unwrapAssertion, // <T>x
|
|
67
|
+
TSTypeCastExpression: unwrapCast, // (x: T)
|
|
68
|
+
// Standard nodes that may carry type syntax. Function and Class are aggregate
|
|
69
|
+
// types dispatched by acorn-walk’s base walkers, so overriding them covers
|
|
70
|
+
// every function and class form.
|
|
71
|
+
Function: stripFunction,
|
|
72
|
+
Class: stripClass,
|
|
73
|
+
PropertyDefinition: stripMember,
|
|
74
|
+
MethodDefinition: stripMember,
|
|
75
|
+
AccessorProperty: stripMember,
|
|
76
|
+
VariablePattern: stripBindingIdentifier,
|
|
77
|
+
ObjectPattern: stripPattern,
|
|
78
|
+
ArrayPattern: stripPattern,
|
|
79
|
+
RestElement: stripPattern,
|
|
80
|
+
VariableDeclarator: stripVariableDeclarator,
|
|
81
|
+
CallExpression: stripTypeArguments,
|
|
82
|
+
NewExpression: stripTypeArguments,
|
|
83
|
+
TaggedTemplateExpression: stripTypeArguments
|
|
13
84
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
85
|
+
// Deletes the given node, plus any trailing semicolon and newline.
|
|
86
|
+
function deleteStatement(node, output) {
|
|
87
|
+
let end = node.end;
|
|
88
|
+
const input = output.input;
|
|
89
|
+
if (input[end] === ";")
|
|
90
|
+
++end;
|
|
91
|
+
if (input[end] === "\n")
|
|
92
|
+
++end;
|
|
93
|
+
output.delete(node.start, end);
|
|
94
|
+
}
|
|
95
|
+
// Unwraps a trailing type wrapper (x as T, x satisfies T, x!, f<T>), keeping the
|
|
96
|
+
// value expression.
|
|
97
|
+
function unwrapType(node, output, next) {
|
|
98
|
+
output.delete(node.expression.end, node.end);
|
|
99
|
+
next(node.expression, output);
|
|
100
|
+
}
|
|
101
|
+
// Unwraps a prefix type assertion (<T>x), keeping the value expression.
|
|
102
|
+
function unwrapAssertion(node, output, next) {
|
|
103
|
+
output.delete(node.start, node.expression.start);
|
|
104
|
+
next(node.expression, output);
|
|
105
|
+
}
|
|
106
|
+
// Unwraps a parenthesized type cast (x: T), keeping the value expression.
|
|
107
|
+
function unwrapCast(node, output, next) {
|
|
108
|
+
output.delete(node.typeAnnotation.start, node.typeAnnotation.end);
|
|
109
|
+
next(node.expression, output);
|
|
110
|
+
}
|
|
111
|
+
// Removes the return type and generic type parameters from a function.
|
|
112
|
+
function stripFunction(node, output, next) {
|
|
113
|
+
deleteType(output, node, "returnType");
|
|
114
|
+
deleteType(output, node, "typeParameters");
|
|
115
|
+
base.Function(node, output, next);
|
|
116
|
+
}
|
|
117
|
+
// Removes generic type parameters, heritage type arguments, the abstract
|
|
118
|
+
// modifier, and the implements clause from a class.
|
|
119
|
+
function stripClass(node, output, next) {
|
|
120
|
+
deleteType(output, node, "typeParameters");
|
|
121
|
+
deleteType(output, node, "superTypeParameters");
|
|
122
|
+
deleteType(output, node, "superTypeArguments");
|
|
123
|
+
if (node.abstract && node.id)
|
|
124
|
+
deleteModifiers(output, findModifiersStart(node), node.id.start);
|
|
125
|
+
if (node.implements?.length)
|
|
126
|
+
deleteImplements(output, node);
|
|
127
|
+
base.Class(node, output, next);
|
|
128
|
+
}
|
|
129
|
+
// Erases abstract/ambient members and overload signatures entirely; otherwise
|
|
130
|
+
// removes modifiers, optional/definite markers, and type annotations.
|
|
131
|
+
function stripMember(node, output, next) {
|
|
132
|
+
if (node.abstract || node.declare || node.value?.type === "TSDeclareMethod")
|
|
133
|
+
return deleteStatement(node, output); // prettier-ignore
|
|
134
|
+
deleteModifiers(output, findModifiersStart(node), node.key.start);
|
|
135
|
+
if (node.optional || node.definite)
|
|
136
|
+
deleteMarker(output, node.key.end, findMarkerEnd(node));
|
|
137
|
+
deleteType(output, node, "typeParameters");
|
|
138
|
+
deleteType(output, node, "typeAnnotation");
|
|
139
|
+
base.PropertyDefinition(node, output, next);
|
|
140
|
+
}
|
|
141
|
+
// Removes the optional marker and type annotation from a binding identifier.
|
|
142
|
+
function stripBindingIdentifier(node, output) {
|
|
143
|
+
if (node.optional)
|
|
144
|
+
deleteMarker(output, node.start, node.typeAnnotation?.start ?? node.end);
|
|
145
|
+
deleteType(output, node, "typeAnnotation");
|
|
146
|
+
}
|
|
147
|
+
// Removes the type annotation from a destructuring pattern, then recurses.
|
|
148
|
+
function stripPattern(node, output, next) {
|
|
149
|
+
deleteType(output, node, "typeAnnotation");
|
|
150
|
+
base[node.type](node, output, next);
|
|
151
|
+
}
|
|
152
|
+
// Removes the definite assignment marker from a variable declarator, then
|
|
153
|
+
// recurses (the annotation on the binding is handled by the pattern visitor).
|
|
154
|
+
function stripVariableDeclarator(node, output, next) {
|
|
155
|
+
if (node.definite && node.id.type === "Identifier")
|
|
156
|
+
deleteMarker(output, node.id.start, node.id.typeAnnotation?.start ?? node.init?.start ?? node.id.end); // prettier-ignore
|
|
157
|
+
base.VariableDeclarator(node, output, next);
|
|
158
|
+
}
|
|
159
|
+
// Removes generic type arguments from a call, construction, or tagged template
|
|
160
|
+
// (e.g. f<T>(), new Map<K, V>()), then recurses.
|
|
161
|
+
function stripTypeArguments(node, output, next) {
|
|
162
|
+
deleteType(output, node, "typeArguments");
|
|
163
|
+
base[node.type](node, output, next);
|
|
164
|
+
}
|
|
165
|
+
// Deletes the given child node from the output, if present.
|
|
166
|
+
function deleteType(output, node, key) {
|
|
167
|
+
const child = node[key];
|
|
168
|
+
if (child)
|
|
169
|
+
output.delete(child.start, child.end);
|
|
170
|
+
}
|
|
171
|
+
// The position after any leading decorators, where modifier keywords begin.
|
|
172
|
+
function findModifiersStart(node) {
|
|
173
|
+
const decorators = node.decorators;
|
|
174
|
+
return decorators?.length ? decorators[decorators.length - 1].end : node.start;
|
|
175
|
+
}
|
|
176
|
+
// The position bounding an optional/definite marker on a class member.
|
|
177
|
+
function findMarkerEnd(node) {
|
|
178
|
+
return node.typeAnnotation?.start ?? node.value?.start ?? node.end;
|
|
179
|
+
}
|
|
180
|
+
// Deletes the `?` or `!` marker token found between start and end.
|
|
181
|
+
function deleteMarker(output, start, end) {
|
|
182
|
+
forEachToken(output.input, start, end, (t) => {
|
|
183
|
+
if (t.type === tokTypes.question || t.type === tokTypes.prefix)
|
|
184
|
+
output.delete(t.start, t.end);
|
|
44
185
|
});
|
|
45
|
-
const diagnostics = program.getSyntacticDiagnostics(file);
|
|
46
|
-
if (diagnostics.length > 0) {
|
|
47
|
-
const [diagnostic] = diagnostics;
|
|
48
|
-
throw new SyntaxError(String(diagnostic.messageText));
|
|
49
|
-
}
|
|
50
|
-
return file;
|
|
51
186
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
187
|
+
// Deletes TypeScript-only modifier keyword tokens found between start and end,
|
|
188
|
+
// preserving JavaScript keywords such as static, get, set, and async.
|
|
189
|
+
function deleteModifiers(output, start, end) {
|
|
190
|
+
const input = output.input;
|
|
191
|
+
forEachToken(input, start, end, (t) => {
|
|
192
|
+
if (t.type !== tokTypes.name || !MODIFIERS.has(t.value))
|
|
193
|
+
return;
|
|
194
|
+
let e = t.end;
|
|
195
|
+
while (input[e] === " " || input[e] === "\t")
|
|
196
|
+
++e; // consume trailing space
|
|
197
|
+
output.delete(t.start, e);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Deletes an `implements I, J` heritage clause. The clause spans from the end of
|
|
201
|
+
// the preceding heritage node to the end of the last implemented type, so the
|
|
202
|
+
// `implements` keyword itself needs no separate lookup.
|
|
203
|
+
function deleteImplements(output, node) {
|
|
204
|
+
const list = node.implements;
|
|
205
|
+
const start = node.superTypeParameters?.end ??
|
|
206
|
+
node.superTypeArguments?.end ??
|
|
207
|
+
node.superClass?.end ??
|
|
208
|
+
node.id?.end ??
|
|
209
|
+
node.start;
|
|
210
|
+
output.delete(start, list[list.length - 1].end);
|
|
211
|
+
}
|
|
212
|
+
// Invokes fn for each token in the given span, with absolute offsets. The span is
|
|
213
|
+
// re-tokenized with acorn so that comments and strings are handled correctly; any
|
|
214
|
+
// tokenizing error (e.g. an unexpected character) is ignored.
|
|
215
|
+
function forEachToken(input, start, end, fn) {
|
|
216
|
+
if (start >= end)
|
|
217
|
+
return;
|
|
218
|
+
const T = tokenizer(input, { ecmaVersion: "latest" });
|
|
219
|
+
T.pos = start;
|
|
220
|
+
for (const t of T) {
|
|
80
221
|
if (t.type === tokTypes.eof)
|
|
81
222
|
break;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/** Returns true if the specified input has matched parens. */
|
|
86
|
-
function hasMatchedParens(input) {
|
|
87
|
-
let depth = 0;
|
|
88
|
-
for (const t of tokenize(input)) {
|
|
89
|
-
if (t.type === tokTypes.parenL)
|
|
90
|
-
++depth;
|
|
91
|
-
else if (t.type === tokTypes.parenR && --depth < 0)
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
return depth === 0;
|
|
95
|
-
}
|
|
96
|
-
/** Wraps the specified input with parentheses. */
|
|
97
|
-
function withParens(input) {
|
|
98
|
-
let start;
|
|
99
|
-
let end;
|
|
100
|
-
for (const t of tokenize(input)) {
|
|
101
|
-
start ?? (start = t);
|
|
102
|
-
end = t;
|
|
223
|
+
if (t.start >= end)
|
|
224
|
+
break;
|
|
225
|
+
fn(t);
|
|
103
226
|
}
|
|
104
|
-
return `(${input.slice(start?.start, end?.end)})`;
|
|
105
|
-
}
|
|
106
|
-
/** Removes a trailing semicolon, if present. */
|
|
107
|
-
function trimTrailingSemicolon(input) {
|
|
108
|
-
let end;
|
|
109
|
-
for (const t of tokenize(input))
|
|
110
|
-
end = t;
|
|
111
|
-
return end?.type === tokTypes.semi ? input.slice(0, end.start) : input;
|
|
112
227
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Node } from "acorn";
|
|
2
2
|
import type { AncestorVisitors, RecursiveVisitors, SimpleVisitors } from "acorn-walk";
|
|
3
|
-
export declare function ancestor<T>(node: Node, visitors: AncestorVisitors<T
|
|
3
|
+
export declare function ancestor<T>(node: Node, visitors: AncestorVisitors<T>, state?: T): void;
|
|
4
4
|
export declare function recursive<T>(node: Node, state: T, functions: RecursiveVisitors<T>): void;
|
|
5
|
-
export declare function simple<T>(node: Node, visitors: SimpleVisitors<T
|
|
5
|
+
export declare function simple<T>(node: Node, visitors: SimpleVisitors<T>, state?: T): void;
|
|
@@ -1,13 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ancestor as
|
|
3
|
-
import { recursive as
|
|
4
|
-
import { simple as
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { make } from "acorn-walk";
|
|
2
|
+
import { ancestor as baseAncestor } from "acorn-walk";
|
|
3
|
+
import { recursive as baseRecursive } from "acorn-walk";
|
|
4
|
+
import { simple as baseSimple } from "acorn-walk";
|
|
5
|
+
function callbackId(node, state, c) {
|
|
6
|
+
c(node.id, state, "Identifier");
|
|
7
|
+
}
|
|
8
|
+
function callbackExpression(node, state, c) {
|
|
9
|
+
c(node.expression, state, "Expression");
|
|
10
|
+
}
|
|
11
|
+
function callbackParameter(node, state, c) {
|
|
12
|
+
c(node.parameter, state, "Pattern");
|
|
13
|
+
}
|
|
14
|
+
function skip() { }
|
|
15
|
+
const visitors = {
|
|
16
|
+
ViewExpression: callbackId,
|
|
17
|
+
MutableExpression: callbackId,
|
|
18
|
+
TSTypeAliasDeclaration: skip,
|
|
19
|
+
TSInterfaceDeclaration: skip,
|
|
20
|
+
TSDeclareFunction: skip,
|
|
21
|
+
TSDeclareMethod: skip,
|
|
22
|
+
TSIndexSignature: skip,
|
|
23
|
+
TSNamespaceExportDeclaration: skip,
|
|
24
|
+
TSEnumDeclaration: skip,
|
|
25
|
+
TSModuleDeclaration: skip,
|
|
26
|
+
TSImportEqualsDeclaration: skip,
|
|
27
|
+
TSExportAssignment: skip,
|
|
28
|
+
TSAsExpression: callbackExpression,
|
|
29
|
+
TSSatisfiesExpression: callbackExpression,
|
|
30
|
+
TSNonNullExpression: callbackExpression,
|
|
31
|
+
TSTypeAssertion: callbackExpression,
|
|
32
|
+
TSInstantiationExpression: callbackExpression,
|
|
33
|
+
TSTypeCastExpression: callbackExpression,
|
|
34
|
+
TSParameterProperty: callbackParameter
|
|
35
|
+
};
|
|
36
|
+
const walk = make(visitors);
|
|
37
|
+
export function ancestor(node, visitors, state) {
|
|
38
|
+
return baseAncestor(node, visitors, walk, state);
|
|
7
39
|
}
|
|
8
40
|
export function recursive(node, state, functions) {
|
|
9
|
-
return
|
|
41
|
+
return baseRecursive(node, state, functions, walk);
|
|
10
42
|
}
|
|
11
|
-
export function simple(node, visitors) {
|
|
12
|
-
return
|
|
43
|
+
export function simple(node, visitors, state) {
|
|
44
|
+
return baseSimple(node, visitors, walk, state);
|
|
13
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function buffer(
|
|
1
|
+
export declare function buffer(blob: Blob): Promise<ArrayBuffer>;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export function buffer(
|
|
2
|
-
return
|
|
3
|
-
const reader = new FileReader();
|
|
4
|
-
reader.onload = () => resolve(reader.result);
|
|
5
|
-
reader.onerror = reject;
|
|
6
|
-
reader.readAsArrayBuffer(file);
|
|
7
|
-
});
|
|
1
|
+
export function buffer(blob) {
|
|
2
|
+
return blob.arrayBuffer();
|
|
8
3
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function text(
|
|
1
|
+
export declare function text(blob: Blob): Promise<string>;
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
export function text(
|
|
2
|
-
return
|
|
3
|
-
const reader = new FileReader();
|
|
4
|
-
reader.onload = () => resolve(reader.result);
|
|
5
|
-
reader.onerror = reject;
|
|
6
|
-
reader.readAsText(file);
|
|
7
|
-
});
|
|
1
|
+
export function text(blob) {
|
|
2
|
+
return blob.text();
|
|
8
3
|
}
|
|
@@ -59,14 +59,11 @@
|
|
|
59
59
|
color: var(--syntax-string);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
.observablehq--undefined,
|
|
62
63
|
.observablehq--null {
|
|
63
64
|
color: var(--syntax-keyword);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
.observablehq--undefined {
|
|
67
|
-
color: var(--syntax-variable);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
67
|
.observablehq--prototype-key {
|
|
71
68
|
color: var(--syntax-comment);
|
|
72
69
|
}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/observablehq/notebook-kit.git"
|
|
7
7
|
},
|
|
8
|
-
"version": "2.1
|
|
8
|
+
"version": "2.2.1",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "TZ=America/Los_Angeles vitest",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"@observablehq/parser": "^6.1.0",
|
|
57
57
|
"@observablehq/runtime": "^6.0.0",
|
|
58
58
|
"@sindresorhus/slugify": "^2.2.1",
|
|
59
|
+
"@sveltejs/acorn-typescript": "^1.0.11",
|
|
59
60
|
"acorn": "^8.16.0",
|
|
60
61
|
"acorn-walk": "^8.3.5",
|
|
61
62
|
"jsdom": "^26.1.0",
|
|
62
63
|
"markdown-it": "^14.1.1",
|
|
63
64
|
"markdown-it-anchor": "^9.2.0",
|
|
64
|
-
"typescript": "^5.9.3",
|
|
65
65
|
"vite": "^7.0.0 || ^8.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"postgres": "^3.4.9",
|
|
81
81
|
"snowflake-sdk": "^2.4.2",
|
|
82
82
|
"tsx": "^4.22.3",
|
|
83
|
+
"typescript": "^5.9.3",
|
|
83
84
|
"typescript-eslint": "^8.59.4",
|
|
84
85
|
"vitest": "^4.1.7"
|
|
85
86
|
},
|