-
Notifications
You must be signed in to change notification settings - Fork 193
Expand file tree
/
Copy pathparse.ts
More file actions
87 lines (81 loc) · 3.35 KB
/
parse.ts
File metadata and controls
87 lines (81 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import {Parser, tokTypes} from "acorn";
import type {Expression, Identifier, Options, Program} from "acorn";
import type {Params} from "../route.js";
import {checkAssignments} from "./assignments.js";
import {findAwaits} from "./awaits.js";
import {findDeclarations} from "./declarations.js";
import type {FileExpression} from "./files.js";
import {findFiles} from "./files.js";
import type {ImportReference} from "./imports.js";
import {findExports, findImports} from "./imports.js";
import {checkParams} from "./params.js";
import {findReferences} from "./references.js";
import {syntaxError} from "./syntaxError.js";
export interface ParseOptions {
/** The path to the source within the source root. */
path: string;
/** If true, require the input to be an expression. */
inline?: boolean;
/** Any dynamic route parameters for observable.params. */
params?: Params;
}
export const acornOptions: Options = {
ecmaVersion: 13,
sourceType: "module"
};
export interface JavaScriptNode {
body: Program | Expression;
declarations: Identifier[] | null; // null for expressions that can’t declare top-level variables, a.k.a. outputs
references: Identifier[]; // the unbound references, a.k.a. inputs
files: FileExpression[];
imports: ImportReference[];
expression: boolean; // is this an expression or a program cell?
async: boolean; // does this use top-level await?
input: string;
}
/**
* Parses the specified JavaScript code block, or if the inline option is true,
* the specified inline JavaScript expression.
*/
export function parseJavaScript(input: string, options: ParseOptions): JavaScriptNode {
const {inline = false, path, params} = options;
let expression = maybeParseExpression(input); // first attempt to parse as expression
if (expression?.type === "ClassExpression" && expression.id) expression = null; // treat named class as program
if (expression?.type === "FunctionExpression" && expression.id) expression = null; // treat named function as program
if (!expression && inline) throw new SyntaxError("invalid expression"); // inline code must be an expression
const body = expression ?? parseProgram(input); // otherwise parse as a program
const exports = findExports(body);
if (exports.length) throw syntaxError("Unexpected token 'export'", exports[0], input); // disallow exports
const references = findReferences(body);
if (params) checkParams(body, input, params);
checkAssignments(body, references, input);
return {
body,
declarations: expression ? null : findDeclarations(body as Program, input),
references,
files: findFiles(body, path, input, ["FileAttachment"]),
imports: findImports(body, path, input),
expression: !!expression,
async: findAwaits(body).length > 0,
input
};
}
export function parseProgram(input: string, params?: Params): Program {
const body = Parser.parse(input, acornOptions);
if (params) checkParams(body, input, params);
return body;
}
/**
* Parses a single expression; like parseExpressionAt, but returns null if
* additional input follows the expression.
*/
export function maybeParseExpression(input: string): Expression | null {
const parser = new (Parser as any)(acornOptions, input, 0); // private constructor
parser.nextToken();
try {
const node = parser.parseExpression();
return parser.type === tokTypes.eof ? node : null;
} catch {
return null;
}
}