153 lines
6.8 KiB
JavaScript
153 lines
6.8 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.processScript = exports.isScriptProcessed = void 0;
|
|
const transform_1 = __importDefault(require("./transform"));
|
|
const instruction_1 = __importDefault(require("./instruction"));
|
|
const header_1 = require("./header");
|
|
const acorn_hammerhead_1 = require("acorn-hammerhead");
|
|
const esotope_hammerhead_1 = require("esotope-hammerhead");
|
|
const regexp_escape_1 = __importDefault(require("../../utils/regexp-escape"));
|
|
const get_bom_1 = __importDefault(require("../../utils/get-bom"));
|
|
const HTML_COMMENT_RE = /(^|\n)\s*<!--[^\n]*(\n|$)/g;
|
|
const OBJECT_RE = /^\s*\{.*\}\s*$/;
|
|
const TRAILING_SEMICOLON_RE = /;\s*$/;
|
|
const OBJECT_WRAPPER_RE = /^\s*\((.*)\);\s*$/;
|
|
const SOURCEMAP_RE = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)/gm;
|
|
const PROCESSED_SCRIPT_RE = new RegExp([
|
|
(0, regexp_escape_1.default)(instruction_1.default.getLocation),
|
|
(0, regexp_escape_1.default)(instruction_1.default.setLocation),
|
|
(0, regexp_escape_1.default)(instruction_1.default.getProperty),
|
|
(0, regexp_escape_1.default)(instruction_1.default.setProperty),
|
|
(0, regexp_escape_1.default)(instruction_1.default.callMethod),
|
|
(0, regexp_escape_1.default)(instruction_1.default.processScript),
|
|
(0, regexp_escape_1.default)(instruction_1.default.processHtml),
|
|
(0, regexp_escape_1.default)(instruction_1.default.getPostMessage),
|
|
(0, regexp_escape_1.default)(instruction_1.default.getProxyUrl),
|
|
].join('|'));
|
|
const PARSING_OPTIONS = {
|
|
allowReturnOutsideFunction: true,
|
|
allowImportExportEverywhere: true,
|
|
ecmaVersion: 13,
|
|
};
|
|
// Code pre/post-processing
|
|
function removeHtmlComments(code) {
|
|
// NOTE: The JS parser removes the line that follows'<!--'. (T226589)
|
|
do
|
|
code = code.replace(HTML_COMMENT_RE, '\n');
|
|
while (HTML_COMMENT_RE.test(code));
|
|
return code;
|
|
}
|
|
function preprocess(code) {
|
|
const bom = (0, get_bom_1.default)(code);
|
|
let preprocessed = bom ? code.substring(bom.length) : code;
|
|
preprocessed = (0, header_1.remove)(preprocessed);
|
|
preprocessed = removeSourceMap(preprocessed);
|
|
return { bom, preprocessed };
|
|
}
|
|
function removeSourceMap(code) {
|
|
return code.replace(SOURCEMAP_RE, '');
|
|
}
|
|
function postprocess(processed, withHeader, bom, strictMode, swScopeHeaderValue, proxyless, workerSettings) {
|
|
// NOTE: If the 'use strict' directive is not in the beginning of the file, it is ignored.
|
|
// As we insert our header in the beginning of the script, we must put a new 'use strict'
|
|
// before the header, otherwise it will be ignored.
|
|
if (withHeader)
|
|
processed = (0, header_1.add)(processed, strictMode, swScopeHeaderValue, proxyless, workerSettings);
|
|
return bom ? bom + processed : processed;
|
|
}
|
|
// Parse/generate code
|
|
function removeTrailingSemicolon(processed, src) {
|
|
return TRAILING_SEMICOLON_RE.test(src) ? processed : processed.replace(TRAILING_SEMICOLON_RE, '');
|
|
}
|
|
function getAst(src, isObject) {
|
|
// NOTE: In case of objects (e.g.eval('{ 1: 2}')) without wrapping
|
|
// object will be parsed as label. To avoid this we parenthesize src
|
|
src = isObject ? `(${src})` : src;
|
|
try {
|
|
return (0, acorn_hammerhead_1.parse)(src, PARSING_OPTIONS);
|
|
}
|
|
catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
function getCode(ast, src) {
|
|
const code = (0, esotope_hammerhead_1.generate)(ast, {
|
|
format: {
|
|
quotes: 'double',
|
|
escapeless: true,
|
|
compact: true,
|
|
},
|
|
});
|
|
return src ? removeTrailingSemicolon(code, src) : code;
|
|
}
|
|
// Analyze code
|
|
function analyze(code) {
|
|
let isObject = OBJECT_RE.test(code);
|
|
let ast = getAst(code, isObject);
|
|
// NOTE: `{ const a = 'foo'; }` edge case
|
|
if (!ast && isObject) {
|
|
ast = getAst(code, false);
|
|
isObject = false;
|
|
}
|
|
return { ast, isObject };
|
|
}
|
|
function isArrayDataScript(ast) {
|
|
const firstChild = ast.body[0];
|
|
return ast.body.length === 1 &&
|
|
firstChild.type === esotope_hammerhead_1.Syntax.ExpressionStatement &&
|
|
firstChild.expression.type === esotope_hammerhead_1.Syntax.ArrayExpression;
|
|
}
|
|
function isStrictMode(ast) {
|
|
if (ast.body.length) {
|
|
const firstChild = ast.body[0];
|
|
if (firstChild.type === esotope_hammerhead_1.Syntax.ExpressionStatement && firstChild.expression.type === esotope_hammerhead_1.Syntax.Literal)
|
|
return firstChild.expression.value === 'use strict';
|
|
}
|
|
return false;
|
|
}
|
|
function applyChanges(script, changes, isObject) {
|
|
const indexOffset = isObject ? -1 : 0;
|
|
const chunks = [];
|
|
let index = 0;
|
|
if (!changes.length)
|
|
return script;
|
|
changes.sort((a, b) => (a.start - b.start) || (a.end - b.end) || // eslint-disable-line @typescript-eslint/no-extra-parens
|
|
((a.node.type === esotope_hammerhead_1.Syntax.VariableDeclaration ? 0 : 1) - (b.node.type === esotope_hammerhead_1.Syntax.VariableDeclaration ? 0 : 1))); // eslint-disable-line @typescript-eslint/no-extra-parens
|
|
for (const change of changes) {
|
|
const changeStart = change.start + indexOffset;
|
|
const changeEnd = change.end + indexOffset;
|
|
const parentheses = change.node.type === esotope_hammerhead_1.Syntax.SequenceExpression &&
|
|
change.parentType !== esotope_hammerhead_1.Syntax.ExpressionStatement &&
|
|
change.parentType !== esotope_hammerhead_1.Syntax.SequenceExpression;
|
|
chunks.push(script.substring(index, changeStart));
|
|
chunks.push(parentheses ? '(' : ' ');
|
|
chunks.push(getCode(change.node, script.substring(changeStart, changeEnd)));
|
|
chunks.push(parentheses ? ')' : ' ');
|
|
index += changeEnd - index;
|
|
}
|
|
chunks.push(script.substring(index));
|
|
return chunks.join('');
|
|
}
|
|
function isScriptProcessed(code) {
|
|
return PROCESSED_SCRIPT_RE.test(code);
|
|
}
|
|
exports.isScriptProcessed = isScriptProcessed;
|
|
function processScript(src, withHeader = false, wrapLastExprWithProcessHtml = false, resolver, swScopeHeaderValue, proxyless, workerSettings) {
|
|
const { bom, preprocessed } = preprocess(src);
|
|
const withoutHtmlComments = removeHtmlComments(preprocessed);
|
|
const { ast, isObject } = analyze(withoutHtmlComments);
|
|
if (!ast)
|
|
return src;
|
|
withHeader = withHeader && !isObject && !isArrayDataScript(ast);
|
|
const changes = proxyless ? [] : (0, transform_1.default)(ast, wrapLastExprWithProcessHtml, resolver);
|
|
let processed = changes.length ? applyChanges(withoutHtmlComments, changes, isObject) : preprocessed;
|
|
processed = postprocess(processed, withHeader, bom, isStrictMode(ast), swScopeHeaderValue, proxyless, workerSettings);
|
|
if (isObject)
|
|
processed = processed.replace(OBJECT_WRAPPER_RE, '$1');
|
|
return processed;
|
|
}
|
|
exports.processScript = processScript;
|