@hpcc-js/observablehq-compiler
Version:
hpcc-js - ObservableHQ Compiler (unoffical)
137 lines (123 loc) • 4.39 kB
text/typescript
// Compare with ../../../node_modules/@observablehq/parser/src/parse.js
import { getLineInfo, tokTypes, Statement, ModuleDeclaration, Expression as ExpressionBase, Node } from "acorn";
import { ancestor, RecursiveVisitors, AncestorVisitors } from "acorn-walk";
import { CellParser, parseCell as ohqParseCell, walk as ohqWalk } from "@observablehq/parser";
import defaultGlobals from "../../../node_modules/@observablehq/parser/src/globals.js";
import findReferences from "../../../node_modules/@observablehq/parser/src/references.js";
import findFeatures from "../../../node_modules/@observablehq/parser/src/features.js";
export interface MutableExpression extends Node {
type: "MutableExpression"
}
export interface ViewExpression extends Node {
type: "ViewExpression"
}
export type Expression = ExpressionBase | MutableExpression | ViewExpression;
// Find references.
// Check for illegal references to arguments.
// Check for illegal assignments to global references.
function parseReferences(cell, input, globals = defaultGlobals) {
if (!cell.body) {
cell.references = [];
} else if (cell.body.type === "ImportDeclaration") {
// This is correct?!?
cell.references = cell.body.specifiers
? cell.body.specifiers.map(i => i.imported)
: [];
} else {
try {
cell.references = findReferences(cell, globals);
} catch (error: any) {
if (error.node) {
const loc = getLineInfo(input, error.node.start);
error.message += ` (${loc.line}:${loc.column})`;
error.pos = error.node.start;
error.loc = loc;
delete error.node;
}
throw error;
}
}
return cell;
}
// Find features: file attachments, secrets, database clients.
// Check for illegal references to arguments.
// Check for illegal assignments to global references.
function parseFeatures(cell, input) {
if (cell.body && cell.body.type !== "ImportDeclaration") {
try {
cell.fileAttachments = findFeatures(cell, "FileAttachment");
cell.databaseClients = findFeatures(cell, "DatabaseClient");
cell.secrets = findFeatures(cell, "Secret");
} catch (error: any) {
if (error.node) {
const loc = getLineInfo(input, error.node.start);
error.message += ` (${loc.line}:${loc.column})`;
error.pos = error.node.start;
error.loc = loc;
delete error.node;
}
throw error;
}
} else {
cell.fileAttachments = new Map();
cell.databaseClients = new Map();
cell.secrets = new Map();
}
return cell;
}
class ModuleParser extends CellParser {
parseTopLevel(node: { cells?: Cell[] }) {
if (!node.cells) node.cells = [];
// @ts-ignore
while (this.type !== tokTypes.eof) {
// @ts-ignore
const cell: Cell = this.parseCell(this.startNode());
// @ts-ignore
cell.input = this.input;
node.cells.push(cell);
}
// @ts-ignore
this.next();
// @ts-ignore
return this.finishNode(node, "Program");
}
}
// @ts-ignore
export function parseModule(input, { globals }: { globals: any } = {}) {
// @ts-ignore
const program = ModuleParser.parse(input, { ecmaVersion: 2020 });
for (const cell of program.cells) {
parseReferences(cell, input, globals);
parseFeatures(cell, input);
}
return program;
}
export interface Cell extends Node {
type: "Cell";
id: Expression;
text: string;
body?: Statement | ModuleDeclaration | Expression;
references: unknown[];
async: boolean;
generator: boolean;
strict: boolean;
}
export function splitModule(input: string): Cell[] {
return (ModuleParser as any)
.parse(input, { ecmaVersion: "latest" })
.cells.map((cell: any) => ({
type: "Cell",
text: input.substring(cell.start, cell.end),
start: cell.start,
end: cell.end
}));
}
export {
type Node,
ancestor,
type AncestorVisitors
};
export function parseCell(input: string): Cell {
return ohqParseCell(input);
}
export const walk: RecursiveVisitors<any> = ohqWalk;