pdf.js/external/builder/preprocessor2.js
Jonas Jenwald 8d56a69e74 Reduce usage of SystemJS, in the development viewer, even further
With these changes SystemJS is now only used, during development, on the worker-thread and in the unit/font-tests, since Firefox is currently missing support for worker modules; please see https://bugzilla.mozilla.org/show_bug.cgi?id=1247687

Hence all the JavaScript files in the `web/` and `src/display/` folders are now loaded *natively* by the browser (during development) using standard `import` statements/calls, thanks to a nice `import-maps` polyfill.

*Please note:* As soon as https://bugzilla.mozilla.org/show_bug.cgi?id=1247687 is fixed in Firefox, we should be able to remove all traces of SystemJS and thus finally be able to use every possible modern JavaScript feature.
2020-05-20 13:36:52 +02:00

342 lines
10 KiB
JavaScript

"use strict";
var acorn = require("acorn");
var escodegen = require("escodegen");
var vm = require("vm");
var fs = require("fs");
var path = require("path");
var PDFJS_PREPROCESSOR_NAME = "PDFJSDev";
var ROOT_PREFIX = "$ROOT/";
function isLiteral(obj, value) {
return obj.type === "Literal" && obj.value === value;
}
function isPDFJSPreprocessor(obj) {
return obj.type === "Identifier" && obj.name === PDFJS_PREPROCESSOR_NAME;
}
function evalWithDefines(code, defines, loc) {
if (!code || !code.trim()) {
throw new Error("No JavaScript expression given");
}
return vm.runInNewContext(code, defines, { displayErrors: false });
}
function handlePreprocessorAction(ctx, actionName, args, loc) {
try {
var arg;
switch (actionName) {
case "test":
arg = args[0];
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
throw new Error("No code for testing is given");
}
var isTrue = !!evalWithDefines(arg.value, ctx.defines);
return { type: "Literal", value: isTrue, loc: loc };
case "eval":
arg = args[0];
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
throw new Error("No code for eval is given");
}
var result = evalWithDefines(arg.value, ctx.defines);
if (
typeof result === "boolean" ||
typeof result === "string" ||
typeof result === "number"
) {
return { type: "Literal", value: result, loc: loc };
}
if (typeof result === "object") {
var parsedObj = acorn.parse("(" + JSON.stringify(result) + ")");
parsedObj.body[0].expression.loc = loc;
return parsedObj.body[0].expression;
}
break;
case "json":
arg = args[0];
if (!arg || arg.type !== "Literal" || typeof arg.value !== "string") {
throw new Error("Path to JSON is not provided");
}
var jsonPath = arg.value;
if (jsonPath.indexOf(ROOT_PREFIX) === 0) {
jsonPath = path.join(
ctx.rootPath,
jsonPath.substring(ROOT_PREFIX.length)
);
}
var jsonContent = fs.readFileSync(jsonPath).toString();
var parsedJSON = acorn.parse("(" + jsonContent + ")");
parsedJSON.body[0].expression.loc = loc;
return parsedJSON.body[0].expression;
}
throw new Error("Unsupported action");
} catch (e) {
throw new Error(
"Could not process " +
PDFJS_PREPROCESSOR_NAME +
"." +
actionName +
" at " +
JSON.stringify(loc) +
"\n" +
e.name +
": " +
e.message
);
}
}
function postprocessNode(ctx, node) {
switch (node.type) {
case "ExportNamedDeclaration":
case "ImportDeclaration":
if (
node.source &&
node.source.type === "Literal" &&
ctx.map &&
ctx.map[node.source.value]
) {
var newValue = ctx.map[node.source.value];
node.source.value = node.source.raw = newValue;
}
break;
case "IfStatement":
if (isLiteral(node.test, true)) {
// if (true) stmt1; => stmt1
return node.consequent;
} else if (isLiteral(node.test, false)) {
// if (false) stmt1; else stmt2; => stmt2
return node.alternate || { type: "EmptyStatement", loc: node.loc };
}
break;
case "ConditionalExpression":
if (isLiteral(node.test, true)) {
// true ? stmt1 : stmt2 => stmt1
return node.consequent;
} else if (isLiteral(node.test, false)) {
// false ? stmt1 : stmt2 => stmt2
return node.alternate;
}
break;
case "UnaryExpression":
if (node.operator === "typeof" && isPDFJSPreprocessor(node.argument)) {
// typeof PDFJSDev => 'object'
return { type: "Literal", value: "object", loc: node.loc };
}
if (
node.operator === "!" &&
node.argument.type === "Literal" &&
typeof node.argument.value === "boolean"
) {
// !true => false, !false => true
return { type: "Literal", value: !node.argument.value, loc: node.loc };
}
break;
case "LogicalExpression":
switch (node.operator) {
case "&&":
if (isLiteral(node.left, true)) {
return node.right;
}
if (isLiteral(node.left, false)) {
return node.left;
}
break;
case "||":
if (isLiteral(node.left, true)) {
return node.left;
}
if (isLiteral(node.left, false)) {
return node.right;
}
break;
}
break;
case "BinaryExpression":
switch (node.operator) {
case "==":
case "===":
case "!=":
case "!==":
if (
node.left.type === "Literal" &&
node.right.type === "Literal" &&
typeof node.left.value === typeof node.right.value
) {
// folding two literals == and != check
switch (typeof node.left.value) {
case "string":
case "boolean":
case "number":
var equal = node.left.value === node.right.value;
return {
type: "Literal",
value: (node.operator[0] === "=") === equal,
loc: node.loc,
};
}
}
break;
}
break;
case "CallExpression":
if (
node.callee.type === "MemberExpression" &&
isPDFJSPreprocessor(node.callee.object) &&
node.callee.property.type === "Identifier"
) {
// PDFJSDev.xxxx(arg1, arg2, ...) => transform
var action = node.callee.property.name;
return handlePreprocessorAction(ctx, action, node.arguments, node.loc);
}
// require('string')
if (
node.callee.type === "Identifier" &&
node.callee.name === "require" &&
node.arguments.length === 1 &&
node.arguments[0].type === "Literal" &&
ctx.map &&
ctx.map[node.arguments[0].value]
) {
var requireName = node.arguments[0];
requireName.value = requireName.raw = ctx.map[requireName.value];
}
break;
case "BlockStatement":
var subExpressionIndex = 0;
while (subExpressionIndex < node.body.length) {
switch (node.body[subExpressionIndex].type) {
case "EmptyStatement":
// Removing empty statements from the blocks.
node.body.splice(subExpressionIndex, 1);
continue;
case "BlockStatement":
// Block statements inside a block are moved to the parent one.
var subChildren = node.body[subExpressionIndex].body;
Array.prototype.splice.apply(
node.body,
[subExpressionIndex, 1].concat(subChildren)
);
subExpressionIndex += Math.max(subChildren.length - 1, 0);
continue;
case "ReturnStatement":
case "ThrowStatement":
// Removing dead code after return or throw.
node.body.splice(
subExpressionIndex + 1,
node.body.length - subExpressionIndex - 1
);
break;
}
subExpressionIndex++;
}
break;
case "FunctionDeclaration":
case "FunctionExpression":
var block = node.body;
if (
block.body.length > 0 &&
block.body[block.body.length - 1].type === "ReturnStatement" &&
!block.body[block.body.length - 1].argument
) {
// Function body ends with return without arg -- removing it.
block.body.pop();
}
break;
}
return node;
}
function fixComments(ctx, node) {
if (!ctx.saveComments) {
return;
}
// Fixes double comments in the escodegen output.
delete node.trailingComments;
// Removes ESLint and other service comments.
if (node.leadingComments) {
var CopyrightRegExp = /\bcopyright\b/i;
var BlockCommentRegExp = /^\s*(globals|eslint|falls through)\b/;
var LineCommentRegExp = /^\s*eslint\b/;
var i = 0;
while (i < node.leadingComments.length) {
var type = node.leadingComments[i].type;
var value = node.leadingComments[i].value;
if (ctx.saveComments === "copyright") {
// Remove all comments, except Copyright notices and License headers.
if (!(type === "Block" && CopyrightRegExp.test(value))) {
node.leadingComments.splice(i, 1);
continue;
}
} else if (
(type === "Block" && BlockCommentRegExp.test(value)) ||
(type === "Line" && LineCommentRegExp.test(value))
) {
node.leadingComments.splice(i, 1);
continue;
}
i++;
}
}
}
function traverseTree(ctx, node) {
// generic node processing
for (var i in node) {
var child = node[i];
if (typeof child === "object" && child !== null && child.type) {
const result = traverseTree(ctx, child);
if (result !== child) {
node[i] = result;
}
} else if (Array.isArray(child)) {
child.forEach(function (childItem, index) {
if (
typeof childItem === "object" &&
childItem !== null &&
childItem.type
) {
const result = traverseTree(ctx, childItem);
if (result !== childItem) {
child[index] = result;
}
}
});
}
}
node = postprocessNode(ctx, node) || node;
fixComments(ctx, node);
return node;
}
function preprocessPDFJSCode(ctx, code) {
var format = ctx.format || {
indent: {
style: " ",
},
};
var parseOptions = {
ecmaVersion: 2020,
locations: true,
sourceFile: ctx.sourceFile,
sourceType: "module",
};
var codegenOptions = {
format: format,
parse: acorn.parse,
sourceMap: ctx.sourceMap,
sourceMapWithCode: ctx.sourceMap,
};
var syntax = acorn.parse(code, parseOptions);
traverseTree(ctx, syntax);
return escodegen.generate(syntax, codegenOptions);
}
exports.preprocessPDFJSCode = preprocessPDFJSCode;