@observablehq/notebook-kit 1.4.0-rc.1 → 1.4.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 CHANGED
@@ -5,7 +5,7 @@
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/observablehq/notebook-kit.git"
7
7
  },
8
- "version": "1.4.0-rc.1",
8
+ "version": "1.4.1",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",
@@ -9,7 +9,7 @@ export async function getInterpreterCachePath(sourcePath, interpreter, format, i
9
9
  export function getInterpreterCommand(interpreter) {
10
10
  switch (interpreter) {
11
11
  case "node":
12
- return ["node", ["--input-type=module", "--permission", "--allow-fs-read=."]];
12
+ return ["node", ["--input-type=module-typescript", "--permission", "--allow-fs-read=."]];
13
13
  case "python":
14
14
  return ["python3", []];
15
15
  default:
@@ -1,4 +1,3 @@
1
- import { ScriptTarget, transpile as transpileTypeScript } from "typescript";
2
1
  import { toCell } from "../lib/notebook.js";
3
2
  import { rewriteFileExpressions } from "./files.js";
4
3
  import { hasImportDeclaration } from "./imports.js";
@@ -7,6 +6,7 @@ import { transpileObservable } from "./observable.js";
7
6
  import { parseJavaScript } from "./parse.js";
8
7
  import { Sourcemap } from "./sourcemap.js";
9
8
  import { transpileTemplate } from "./template.js";
9
+ import { transpileTypeScript } 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
- ? transpileJavaScript(transpileTypeScript(input, { target: ScriptTarget.ESNext }), options)
23
+ ? transpileJavaScript(transpileTypeScript(input), options)
24
24
  : mode === "ojs"
25
25
  ? transpileObservable(input, options)
26
26
  : mode !== "js"
@@ -0,0 +1 @@
1
+ export declare function transpileTypeScript(input: string): string;
@@ -0,0 +1,112 @@
1
+ import { tokenizer as Tokenizer, tokTypes } from "acorn";
2
+ import { ModuleKind, ScriptTarget, transpile } from "typescript";
3
+ import { createProgram, createSourceFile } from "typescript";
4
+ import { isClassExpression, isFunctionExpression, isParenthesizedExpression } from "typescript";
5
+ import { isExpressionStatement } from "typescript";
6
+ const tokenizerOptions = {
7
+ ecmaVersion: "latest"
8
+ };
9
+ const compilerOptions = {
10
+ target: ScriptTarget.ESNext,
11
+ module: ModuleKind.Preserve,
12
+ verbatimModuleSyntax: true
13
+ };
14
+ export function transpileTypeScript(input) {
15
+ const expr = maybeExpression(input);
16
+ if (expr)
17
+ return trimTrailingSemicolon(transpile(expr, compilerOptions));
18
+ parseTypeScript(input); // enforce valid syntax
19
+ return transpile(input, compilerOptions);
20
+ }
21
+ /** If the given is an expression (not a statement), returns it with parens. */
22
+ function maybeExpression(input) {
23
+ if (!hasMatchedParens(input))
24
+ return; // disallow funny business
25
+ const expr = withParens(input);
26
+ if (!isSolitaryExpression(expr))
27
+ return;
28
+ return expr;
29
+ }
30
+ /** Parses the specified TypeScript input, returning the AST or throwing a SyntaxError. */
31
+ function parseTypeScript(input) {
32
+ const file = createSourceFile("input.ts", input, compilerOptions.target);
33
+ const program = createProgram(["input.ts"], compilerOptions, {
34
+ getSourceFile: (path) => (path === "input.ts" ? file : undefined),
35
+ getDefaultLibFileName: () => "lib.d.ts",
36
+ writeFile: () => { },
37
+ getCurrentDirectory: () => "/",
38
+ getDirectories: () => [],
39
+ getCanonicalFileName: (path) => path,
40
+ useCaseSensitiveFileNames: () => true,
41
+ getNewLine: () => "\n",
42
+ fileExists: (path) => path === "input.ts",
43
+ readFile: (path) => (path === "input.ts" ? input : undefined)
44
+ });
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
+ }
52
+ /** Returns true if the specified input is exactly one parenthesized expression statement. */
53
+ function isSolitaryExpression(input) {
54
+ let file;
55
+ try {
56
+ file = parseTypeScript(input);
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ if (file.statements.length !== 1)
62
+ return false;
63
+ const statement = file.statements[0];
64
+ if (!isExpressionStatement(statement))
65
+ return false;
66
+ const expression = statement.expression;
67
+ if (!isParenthesizedExpression(expression))
68
+ return false;
69
+ const subexpression = expression.expression;
70
+ if (isClassExpression(subexpression) && subexpression.name)
71
+ return false;
72
+ if (isFunctionExpression(subexpression) && subexpression.name)
73
+ return false;
74
+ return true;
75
+ }
76
+ function* tokenize(input) {
77
+ const tokenizer = Tokenizer(input, tokenizerOptions);
78
+ while (true) {
79
+ const t = tokenizer.getToken();
80
+ if (t.type === tokTypes.eof)
81
+ break;
82
+ yield t;
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;
103
+ }
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { assert, expect, it } from "vitest";
2
+ import { transpileTypeScript } from "./typescript.js";
3
+ it("transpiles TypeScript expressions", () => {
4
+ expect(transpileTypeScript("1 + 2")).toMatchSnapshot();
5
+ expect(transpileTypeScript("1, 2")).toMatchSnapshot();
6
+ expect(transpileTypeScript("1, 2 // comment")).toMatchSnapshot();
7
+ expect(transpileTypeScript("(1), (2)")).toMatchSnapshot();
8
+ expect(transpileTypeScript("(1 + 2)")).toMatchSnapshot();
9
+ expect(transpileTypeScript("{x: 42}")).toMatchSnapshot();
10
+ expect(transpileTypeScript("({x: 42})")).toMatchSnapshot();
11
+ });
12
+ it("transpiles TypeScript function expressions", () => {
13
+ expect(transpileTypeScript("function foo() {}")).toMatchSnapshot();
14
+ });
15
+ it("transpiles TypeScript class expressions", () => {
16
+ expect(transpileTypeScript("class Foo {}")).toMatchSnapshot();
17
+ });
18
+ it("transpiles TypeScript statements", () => {
19
+ expect(transpileTypeScript("1 + 2;")).toMatchSnapshot();
20
+ expect(transpileTypeScript("1, 2;")).toMatchSnapshot();
21
+ expect(transpileTypeScript("(1), (2);")).toMatchSnapshot();
22
+ expect(transpileTypeScript("(1 + 2);")).toMatchSnapshot();
23
+ expect(transpileTypeScript("{x: 42};")).toMatchSnapshot();
24
+ expect(transpileTypeScript("({x: 42});")).toMatchSnapshot();
25
+ });
26
+ it("transpiles TypeScript imports", () => {
27
+ expect(transpileTypeScript('import {foo} from "npm:bar";')).toMatchSnapshot();
28
+ expect(transpileTypeScript('import type {foo} from "npm:bar";')).toMatchSnapshot();
29
+ });
30
+ it("throws SyntaxError on invalid syntax", () => {
31
+ assert.throws(() => transpileTypeScript("1) + 2"), SyntaxError);
32
+ assert.throws(() => transpileTypeScript("(1 + 2"), SyntaxError);
33
+ assert.throws(() => transpileTypeScript("1 + 2 /* comment"), SyntaxError);
34
+ });
@@ -47,6 +47,11 @@ export async function highlight(code) {
47
47
  highlightCode(text, tree, highlighter, emit, emitBreak);
48
48
  }
49
49
  async function getParser(language) {
50
+ switch (language) {
51
+ case "node":
52
+ language = "ts";
53
+ break;
54
+ }
50
55
  switch (language) {
51
56
  case "js":
52
57
  case "ts":
@@ -68,7 +73,6 @@ function getLanguage(code) {
68
73
  ?.slice("language-".length)
69
74
  ?.toLowerCase();
70
75
  switch (language) {
71
- case "node":
72
76
  case "javascript":
73
77
  return "js";
74
78
  case "typescript":
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": "1.4.0-rc.1",
8
+ "version": "1.4.1",
9
9
  "type": "module",
10
10
  "scripts": {
11
11
  "test": "vitest",