You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

702 lines
26 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ScriptLetContext = void 0;
const common_1 = require("../parser/converts/common");
const scope_1 = require("../scope");
const traverse_1 = require("../traverse");
const unique_1 = require("./unique");
/**
* Get node range
*/
function getNodeRange(node) {
let start = null;
let end = null;
if (node.leadingComments) {
start = (0, common_1.getWithLoc)(node.leadingComments[0]).start;
}
if (node.trailingComments) {
end = (0, common_1.getWithLoc)(node.trailingComments[node.trailingComments.length - 1]).end;
}
const loc = "range" in node
? { start: node.range[0], end: node.range[1] }
: (0, common_1.getWithLoc)(node);
return [
start ? Math.min(start, loc.start) : loc.start,
end ? Math.max(end, loc.end) : loc.end,
];
}
/**
* A class that handles script fragments.
* The script fragment AST node remaps and connects to the original directive AST node.
*/
class ScriptLetContext {
constructor(ctx) {
this.restoreCallbacks = [];
this.programRestoreCallbacks = [];
this.closeScopeCallbacks = [];
this.unique = new unique_1.UniqueIdGenerator();
this.script = ctx.sourceCode.scripts;
this.ctx = ctx;
}
addExpression(expression, parent, typing, ...callbacks) {
const range = getNodeRange(expression);
const part = this.ctx.code.slice(...range);
const isTS = typing && this.ctx.isTypeScript();
this.appendScript(`(${part})${isTS ? `as (${typing})` : ""};`, range[0] - 1, (st, tokens, comments, result) => {
const exprSt = st;
const tsAs = isTS
? exprSt.expression
: null;
const node = (tsAs === null || tsAs === void 0 ? void 0 : tsAs.expression) || exprSt.expression;
// Process for nodes
for (const callback of callbacks) {
callback(node, result);
}
if (isTS) {
for (const scope of extractTypeNodeScopes(tsAs.typeAnnotation, result)) {
(0, scope_1.removeScope)(result.scopeManager, scope);
}
this.remapNodes([
{
offset: range[0] - 1,
range,
newNode: node,
},
], tokens, comments, result.visitorKeys);
}
node.parent = parent;
tokens.shift(); // (
tokens.pop(); // )
tokens.pop(); // ;
// Disconnect the tree structure.
exprSt.expression = null;
});
return callbacks;
}
addObjectShorthandProperty(identifier, parent, ...callbacks) {
const range = getNodeRange(identifier);
const part = this.ctx.code.slice(...range);
this.appendScript(`({${part}});`, range[0] - 2, (st, tokens, _comments, result) => {
const exprSt = st;
const objectExpression = exprSt.expression;
const node = objectExpression.properties[0];
// Process for nodes
for (const callback of callbacks) {
callback(node, result);
}
node.key.parent = parent;
node.value.parent = parent;
tokens.shift(); // (
tokens.shift(); // {
tokens.pop(); // }
tokens.pop(); // )
tokens.pop(); // ;
// Disconnect the tree structure.
exprSt.expression = null;
});
}
addVariableDeclarator(expression, parent, ...callbacks) {
const range = getNodeRange(expression);
const part = this.ctx.code.slice(...range);
this.appendScript(`const ${part};`, range[0] - 6, (st, tokens, _comments, result) => {
const decl = st;
const node = decl.declarations[0];
// Process for nodes
for (const callback of callbacks) {
callback(node, result);
}
const scope = result.getScope(decl);
for (const variable of scope.variables) {
for (const def of variable.defs) {
if (def.parent === decl) {
def.parent = parent;
}
}
}
node.parent = parent;
tokens.shift(); // const
tokens.pop(); // ;
// Disconnect the tree structure.
decl.declarations = [];
});
return callbacks;
}
nestIfBlock(expression, ifBlock, callback) {
const range = getNodeRange(expression);
const part = this.ctx.code.slice(...range);
const restore = this.appendScript(`if(${part}){`, range[0] - 3, (st, tokens, _comments, result) => {
const ifSt = st;
const node = ifSt.test;
const scope = result.getScope(ifSt.consequent);
// Process for nodes
callback(node, result);
node.parent = ifBlock;
// Process for scope
result.registerNodeToScope(ifBlock, scope);
tokens.shift(); // if
tokens.shift(); // (
tokens.pop(); // )
tokens.pop(); // {
tokens.pop(); // }
// Disconnect the tree structure.
ifSt.test = null;
ifSt.consequent = null;
});
this.pushScope(restore, "}");
}
nestEachBlock(expression, context, indexRange, eachBlock, callback) {
const exprRange = getNodeRange(expression);
const ctxRange = getNodeRange(context);
let source = "Array.from(";
const exprOffset = source.length;
source += `${this.ctx.code.slice(...exprRange)}).forEach((`;
const ctxOffset = source.length;
source += this.ctx.code.slice(...ctxRange);
let idxOffset = null;
if (indexRange) {
source += ",";
idxOffset = source.length;
source += this.ctx.code.slice(indexRange.start, indexRange.end);
}
source += ")=>{";
const restore = this.appendScript(source, exprRange[0] - exprOffset, (st, tokens, comments, result) => {
var _a;
const expSt = st;
const call = expSt.expression;
const fn = call.arguments[0];
const callArrayFrom = call.callee
.object;
const expr = callArrayFrom.arguments[0];
const ctx = fn.params[0];
const idx = ((_a = fn.params[1]) !== null && _a !== void 0 ? _a : null);
const scope = result.getScope(fn.body);
// Process for nodes
callback(expr, ctx, idx);
// Process for scope
result.registerNodeToScope(eachBlock, scope);
for (const v of scope.variables) {
for (const def of v.defs) {
if (def.node === fn) {
def.node = eachBlock;
}
}
}
// remove Array reference
const arrayId = callArrayFrom.callee
.object;
const ref = scope.upper.references.find((r) => r.identifier === arrayId);
if (ref) {
(0, scope_1.removeReference)(ref, scope.upper);
}
expr.parent = eachBlock;
ctx.parent = eachBlock;
if (idx) {
idx.parent = eachBlock;
}
tokens.shift(); // Array
tokens.shift(); // .
tokens.shift(); // from
tokens.shift(); // (
tokens.pop(); // )
tokens.pop(); // =>
tokens.pop(); // {
tokens.pop(); // }
tokens.pop(); // )
tokens.pop(); // ;
const map = [
{
offset: exprOffset,
range: exprRange,
newNode: expr,
},
{
offset: ctxOffset,
range: ctxRange,
newNode: ctx,
},
];
if (indexRange) {
map.push({
offset: idxOffset,
range: [indexRange.start, indexRange.end],
newNode: idx,
});
}
this.remapNodes(map, tokens, comments, result.visitorKeys);
// Disconnect the tree structure.
expSt.expression = null;
});
this.pushScope(restore, "});");
}
nestBlock(block, params) {
let resolvedParams;
if (typeof params === "function") {
if (this.ctx.isTypeScript()) {
const generatedTypes = params({
generateUniqueId: (base) => this.generateUniqueId(base),
});
resolvedParams = [generatedTypes.param];
if (generatedTypes.preparationScript) {
for (const preparationScript of generatedTypes.preparationScript) {
this.appendScriptWithoutOffset(preparationScript, (node, tokens, comments, result) => {
tokens.length = 0;
comments.length = 0;
(0, scope_1.removeAllScopeAndVariableAndReference)(node, result);
});
}
}
}
else {
const generatedTypes = params(null);
resolvedParams = [generatedTypes.param];
}
}
else {
resolvedParams = params;
}
if (!resolvedParams || resolvedParams.length === 0) {
const restore = this.appendScript(`{`, block.range[0], (st, tokens, _comments, result) => {
const blockSt = st;
// Process for scope
const scope = result.getScope(blockSt);
result.registerNodeToScope(block, scope);
tokens.length = 0; // clear tokens
// Disconnect the tree structure.
blockSt.body = null;
});
this.pushScope(restore, "}");
}
else {
const sortedParams = [...resolvedParams]
.map((d) => {
return Object.assign(Object.assign({}, d), { range: getNodeRange(d.node) });
})
.sort((a, b) => {
const [pA] = a.range;
const [pB] = b.range;
return pA - pB;
});
const maps = [];
let source = "";
for (let index = 0; index < sortedParams.length; index++) {
const param = sortedParams[index];
const range = param.range;
if (source) {
source += ",";
}
const offset = source.length + 1; /* ( */
source += this.ctx.code.slice(...range);
maps.push({
index: maps.length,
offset,
range,
});
if (this.ctx.isTypeScript()) {
source += `: (${param.typing})`;
}
}
const restore = this.appendScript(`(${source})=>{`, maps[0].range[0] - 1, (st, tokens, comments, result) => {
const exprSt = st;
const fn = exprSt.expression;
const scope = result.getScope(fn.body);
// Process for nodes
for (let index = 0; index < fn.params.length; index++) {
const p = fn.params[index];
sortedParams[index].callback(p, result);
if (this.ctx.isTypeScript()) {
const typeAnnotation = p.typeAnnotation;
delete p.typeAnnotation;
p.range[1] = typeAnnotation.range[0];
p.loc.end = {
line: typeAnnotation.loc.start.line,
column: typeAnnotation.loc.start.column,
};
(0, scope_1.removeAllScopeAndVariableAndReference)(typeAnnotation, result);
}
p.parent = sortedParams[index].parent;
}
// Process for scope
result.registerNodeToScope(block, scope);
for (const v of scope.variables) {
for (const def of v.defs) {
if (def.node === fn) {
def.node = block;
}
}
}
tokens.shift(); // (
tokens.pop(); // )
tokens.pop(); // =>
tokens.pop(); // {
tokens.pop(); // }
tokens.pop(); // ;
this.remapNodes(maps.map((m) => {
return {
offset: m.offset,
range: m.range,
newNode: fn.params[m.index],
};
}), tokens, comments, result.visitorKeys);
// Disconnect the tree structure.
exprSt.expression = null;
});
this.pushScope(restore, "};");
}
}
closeScope() {
this.closeScopeCallbacks.pop()();
}
addProgramRestore(callback) {
this.programRestoreCallbacks.push(callback);
}
appendScript(text, offset, callback) {
const resultCallback = this.appendScriptWithoutOffset(text, (node, tokens, comments, result) => {
this.fixLocations(node, tokens, comments, offset - resultCallback.start, result.visitorKeys);
callback(node, tokens, comments, result);
});
return resultCallback;
}
appendScriptWithoutOffset(text, callback) {
const { start: startOffset, end: endOffset } = this.script.addLet(text);
const restoreCallback = {
start: startOffset,
end: endOffset,
callback,
};
this.restoreCallbacks.push(restoreCallback);
return restoreCallback;
}
pushScope(restoreCallback, closeToken) {
this.closeScopeCallbacks.push(() => {
this.script.addLet(closeToken);
restoreCallback.end = this.script.getCurrentVirtualCodeLength();
});
}
/**
* Restore AST nodes
*/
restore(result) {
const nodeToScope = getNodeToScope(result.scopeManager);
const postprocessList = [];
const callbackOption = {
getScope,
registerNodeToScope,
scopeManager: result.scopeManager,
visitorKeys: result.visitorKeys,
addPostProcess: (cb) => postprocessList.push(cb),
};
this.restoreNodes(result, callbackOption);
this.restoreProgram(result, callbackOption);
postprocessList.forEach((p) => p());
// Helpers
/** Get scope */
function getScope(node) {
return (0, scope_1.getScopeFromNode)(result.scopeManager, node);
}
/** Register node to scope */
function registerNodeToScope(node, scope) {
// If we replace the `scope.block` at this time,
// the scope restore calculation will not work, so we will replace the `scope.block` later.
postprocessList.push(() => {
scope.block = node;
});
const scopes = nodeToScope.get(node);
if (scopes) {
scopes.push(scope);
}
else {
nodeToScope.set(node, [scope]);
}
}
}
/**
* Restore AST nodes
*/
restoreNodes(result, callbackOption) {
let orderedRestoreCallback = this.restoreCallbacks.shift();
if (!orderedRestoreCallback) {
return;
}
const separateIndexes = this.script.separateIndexes;
const tokens = result.ast.tokens;
const processedTokens = [];
const comments = result.ast.comments;
const processedComments = [];
let tok;
while ((tok = tokens.shift())) {
if (separateIndexes.includes(tok.range[0]) && tok.value === ";") {
break;
}
if (orderedRestoreCallback.start <= tok.range[0]) {
tokens.unshift(tok);
break;
}
processedTokens.push(tok);
}
while ((tok = comments.shift())) {
if (orderedRestoreCallback.start <= tok.range[0]) {
comments.unshift(tok);
break;
}
processedComments.push(tok);
}
const targetNodes = new Map();
const removeStatements = [];
(0, traverse_1.traverseNodes)(result.ast, {
visitorKeys: result.visitorKeys,
enterNode: (node) => {
while (node.range && separateIndexes.includes(node.range[1] - 1)) {
node.range[1]--;
node.loc.end.column--;
}
if (node.loc.end.column < 0) {
node.loc.end = this.ctx.getLocFromIndex(node.range[1]);
}
if (node.parent === result.ast &&
separateIndexes[0] <= node.range[0]) {
removeStatements.push(node);
}
if (!orderedRestoreCallback) {
return;
}
if (orderedRestoreCallback.start <= node.range[0] &&
node.range[1] <= orderedRestoreCallback.end) {
targetNodes.set(node, orderedRestoreCallback);
orderedRestoreCallback = this.restoreCallbacks.shift();
}
//
},
leaveNode(node) {
const restoreCallback = targetNodes.get(node);
if (!restoreCallback) {
return;
}
const startIndex = {
token: tokens.findIndex((t) => restoreCallback.start <= t.range[0]),
comment: comments.findIndex((t) => restoreCallback.start <= t.range[0]),
};
if (startIndex.comment === -1) {
startIndex.comment = comments.length;
}
const endIndex = {
token: tokens.findIndex((t) => restoreCallback.end < t.range[1], startIndex.token),
comment: comments.findIndex((t) => restoreCallback.end < t.range[1], startIndex.comment),
};
if (endIndex.token === -1) {
endIndex.token = tokens.length;
}
if (endIndex.comment === -1) {
endIndex.comment = comments.length;
}
const targetTokens = tokens.splice(startIndex.token, endIndex.token - startIndex.token);
const targetComments = comments.splice(startIndex.comment, endIndex.comment - startIndex.comment);
restoreCallback.callback(node, targetTokens, targetComments, callbackOption);
processedTokens.push(...targetTokens);
processedComments.push(...targetComments);
},
});
for (const st of removeStatements) {
const index = result.ast.body.indexOf(st);
result.ast.body.splice(index, 1);
}
result.ast.tokens = processedTokens;
result.ast.comments = processedComments;
}
/**
* Restore program node
*/
restoreProgram(result, callbackOption) {
for (const callback of this.programRestoreCallbacks) {
callback(result.ast, result.ast.tokens, result.ast.comments, callbackOption);
}
}
remapNodes(maps, tokens, comments, visitorKeys) {
const targetMaps = [...maps];
const first = targetMaps.shift();
let tokenIndex = 0;
for (; tokenIndex < tokens.length; tokenIndex++) {
const token = tokens[tokenIndex];
if (first.range[1] <= token.range[0]) {
break;
}
}
for (const map of targetMaps) {
const startOffset = map.offset - first.offset + first.range[0];
const endOffset = startOffset + (map.range[1] - map.range[0]);
let removeCount = 0;
let target = tokens[tokenIndex];
while (target && target.range[1] <= startOffset) {
removeCount++;
target = tokens[tokenIndex + removeCount];
}
if (removeCount) {
tokens.splice(tokenIndex, removeCount);
}
const bufferTokens = [];
for (; tokenIndex < tokens.length; tokenIndex++) {
const token = tokens[tokenIndex];
if (endOffset <= token.range[0]) {
break;
}
bufferTokens.push(token);
}
this.fixLocations(map.newNode, bufferTokens, comments.filter((t) => startOffset <= t.range[0] && t.range[1] <= endOffset), map.range[0] - startOffset, visitorKeys);
}
tokens.splice(tokenIndex);
}
/** Fix locations */
fixLocations(node, tokens, comments, offset, visitorKeys) {
if (offset === 0) {
return;
}
const traversed = new Set();
(0, traverse_1.traverseNodes)(node, {
visitorKeys,
enterNode: (n) => {
if (traversed.has(n)) {
return;
}
traversed.add(n);
if (traversed.has(n.range)) {
if (!traversed.has(n.loc)) {
// However, `Node#loc` may not be shared.
const locs = this.ctx.getConvertLocation({
start: n.range[0],
end: n.range[1],
});
applyLocs(n, locs);
traversed.add(n.loc);
}
}
else {
const start = n.range[0] + offset;
const end = n.range[1] + offset;
const locs = this.ctx.getConvertLocation({ start, end });
applyLocs(n, locs);
traversed.add(n.range);
traversed.add(n.loc);
}
},
leaveNode: Function.prototype,
});
for (const t of tokens) {
const start = t.range[0] + offset;
const end = t.range[1] + offset;
const locs = this.ctx.getConvertLocation({ start, end });
applyLocs(t, locs);
}
for (const t of comments) {
const start = t.range[0] + offset;
const end = t.range[1] + offset;
const locs = this.ctx.getConvertLocation({ start, end });
applyLocs(t, locs);
}
}
generateUniqueId(base) {
return this.unique.generate(base, this.ctx.code, this.script.getCurrentVirtualCode());
}
}
exports.ScriptLetContext = ScriptLetContext;
/**
* applyLocs
*/
function applyLocs(target, locs) {
target.loc = locs.loc;
target.range = locs.range;
if (typeof target.start === "number") {
delete target.start;
}
if (typeof target.end === "number") {
delete target.end;
}
}
/** Get the node to scope map from given scope manager */
function getNodeToScope(scopeManager) {
if ("__nodeToScope" in scopeManager) {
return scopeManager.__nodeToScope;
}
// transform scopeManager
const nodeToScope = new WeakMap();
for (const scope of scopeManager.scopes) {
const scopes = nodeToScope.get(scope.block);
if (scopes) {
scopes.push(scope);
}
else {
nodeToScope.set(scope.block, [scope]);
}
}
scopeManager.acquire = function (node, inner) {
/**
* predicate
*/
function predicate(testScope) {
if (testScope.type === "function" && testScope.functionExpressionScope) {
return false;
}
return true;
}
const scopes = nodeToScope.get(node);
if (!scopes || scopes.length === 0) {
return null;
}
// Heuristic selection from all scopes.
// If you would like to get all scopes, please use ScopeManager#acquireAll.
if (scopes.length === 1) {
return scopes[0];
}
if (inner) {
for (let i = scopes.length - 1; i >= 0; --i) {
const scope = scopes[i];
if (predicate(scope)) {
return scope;
}
}
}
else {
for (let i = 0, iz = scopes.length; i < iz; ++i) {
const scope = scopes[i];
if (predicate(scope)) {
return scope;
}
}
}
return null;
};
return nodeToScope;
}
/** Extract the type scope of the given node. */
function extractTypeNodeScopes(node, result) {
const scopes = new Set();
for (const scope of iterateTypeNodeScopes(node)) {
scopes.add(scope);
}
return scopes;
/** Iterate the type scope of the given node. */
function* iterateTypeNodeScopes(node) {
if (node.type === "TSParenthesizedType") {
// Skip TSParenthesizedType.
yield* iterateTypeNodeScopes(node.typeAnnotation);
}
else if (node.type === "TSConditionalType") {
yield result.getScope(node);
// `falseType` of `TSConditionalType` is sibling scope.
const falseType = node.falseType;
yield* iterateTypeNodeScopes(falseType);
}
else if (node.type === "TSFunctionType" ||
node.type === "TSMappedType" ||
node.type === "TSConstructorType") {
yield result.getScope(node);
}
else {
const typeNode = node;
for (const key of (0, traverse_1.getKeys)(typeNode, result.visitorKeys)) {
for (const child of (0, traverse_1.getNodes)(typeNode, key)) {
yield* iterateTypeNodeScopes(child);
}
}
}
}
}