@stencila/jesta
Version:
Stencila plugin for executable documents using JavaScript
177 lines (176 loc) • 6.83 kB
JavaScript
;
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;