UNPKG

@stencila/jesta

Version:

Stencila plugin for executable documents using JavaScript

177 lines (176 loc) 6.83 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.compileCode = exports.compile = void 0; const schema_1 = require("@stencila/schema"); const acorn = __importStar(require("acorn")); const acornWalk = __importStar(require("acorn-walk")); const types_1 = require("./types"); const changes_1 = require("./util/changes"); const timer = __importStar(require("./util/timer")); const walk_1 = require("./util/walk"); /* eslint-disable @typescript-eslint/require-await */ async function compile(node, force) { // Compile code chunks and expressions with Javascript code if (schema_1.isA('CodeChunk', node) || schema_1.isA('CodeExpression', node)) { const { programmingLanguage, text } = node; if (['js', 'javascript'].includes(programmingLanguage !== null && programmingLanguage !== void 0 ? programmingLanguage : '')) { // Skip if not needed if (!force && !changes_1.needed(node, types_1.Method.compile)) return node; const start = timer.start(); const props = exports.compileCode(text); const compiled = { ...node, ...props }; return changes_1.record(compiled, types_1.Method.compile, timer.seconds(start)); } } // Walk over other node types return walk_1.mutate(node, (child) => this.compile(child, force)); } exports.compile = compile; const compileCode = (code) => { // Properties of code nodes which we will derive from special comments or code analysis const alters = []; const assigns = []; const declares = []; const imports = []; const reads = []; const uses = []; const props = { alters, assigns, declares, imports, reads, uses, }; const analyse = { alters: true, assigns: true, declares: true, imports: true, reads: true, uses: true, }; // Parse the code, keeping any comments const comments = []; let ast; try { ast = acorn.parse(code, { sourceType: 'module', ecmaVersion: 'latest', onComment: comments, }); } catch (error) { // Syntax error when parsing code, so just return undefined // for all properties return {}; } // Users can manually specify properties using special comments starting with `@stencila-` // If the user uses one of these comments then code analysis for that property is ignores for (const comment of comments) { const { value } = comment; const match = /^\s*@(?:stencila-)?(alters|assigns|declares|imports|reads|uses)\s+(.+)/.exec(value); if (match) { const prop = match[1]; const values = match[2].trim().split(/\s+/); props[prop].push(...values); analyse[prop] = false; } } // Walk the AST and populate properties // For alternative walking algos see https://github.com/acornjs/acorn/tree/master/acorn-walk#interface acornWalk.ancestor(ast, { // Note: these are very preliminary implementations and do not // take into account things like scope. The type casts should also be replaced // with checks on `node.type` // The following `@ts-expect-error`s are necessary because we are using `estree` types // instead of `acorn` node type (because they are 'better'). // See https://github.com/acornjs/acorn/issues/906#issuecomment-604108455 // @ts-expect-error as above VariableDeclaration(node) { const { kind, declarations } = node; if (kind !== 'var') return; for (const decl of declarations) { const { id } = decl; const { name } = id; if (analyse.declares && !declares.includes(name)) declares.push(name); } }, // @ts-expect-error as above FunctionDeclaration(node) { const { id } = node; const { name } = id; if (analyse.declares && !declares.includes(name)) declares.push(name); }, // @ts-expect-error as above AssignmentExpression(node) { const { left } = node; const { name } = left; if (analyse.assigns && !assigns.includes(name)) assigns.push(name); }, // @ts-expect-error as above CallExpression(node) { const { callee, arguments: args } = node; let name; if (callee.type === 'Identifier') { name = callee.name; } else { return; } if (name === 'require') { const { value } = args[0]; if (typeof value === 'string' && analyse.imports && !imports.includes(value)) imports.push(value); } else if (['readFile', 'readFileSync'].includes(name)) { const { value } = args[0]; if (typeof value === 'string' && analyse.reads && !reads.includes(value)) reads.push(value); } }, // @ts-expect-error as above Identifier(node) { const { name } = node; if (!declares.includes(name) && analyse.uses && !uses.includes(name)) uses.push(name); }, }); return { alters: alters.length > 0 ? alters : undefined, assigns: assigns.length > 0 ? assigns : undefined, declares: declares.length > 0 ? declares : undefined, imports: imports.length > 0 ? imports : undefined, reads: reads.length > 0 ? reads : undefined, uses: uses.length > 0 ? uses : undefined, }; }; exports.compileCode = compileCode;