You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
291 lines
9.2 KiB
291 lines
9.2 KiB
'use strict'; |
|
|
|
var esprima = require('esprima'); |
|
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 = esprima.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 = esprima.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 '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, ...) => tranform |
|
var action = node.callee.property.name; |
|
return handlePreprocessorAction(ctx, action, |
|
node.arguments, node.loc); |
|
} |
|
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|umdutils)\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) { |
|
var 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) { |
|
var 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 saveComments = !!ctx.saveComments; |
|
var format = ctx.format || { |
|
indent: { |
|
style: ' ', |
|
adjustMultilineComment: saveComments, |
|
} |
|
}; |
|
var parseComment = { |
|
loc: true, |
|
attachComment: saveComments |
|
}; |
|
var codegenOptions = { |
|
format: format, |
|
comment: saveComments, |
|
parse: esprima.parse |
|
}; |
|
var syntax = esprima.parse(code, parseComment); |
|
traverseTree(ctx, syntax); |
|
return escodegen.generate(syntax, codegenOptions); |
|
} |
|
|
|
exports.preprocessPDFJSCode = preprocessPDFJSCode;
|
|
|