mirror of
https://github.com/javascript-obfuscator/javascript-obfuscator
synced 2026-08-09 12:42:29 +00:00
Dead code injection refactoring #1
This commit is contained in:
+94
-54
@@ -184,19 +184,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
|
||||
return astTree;
|
||||
}
|
||||
|
||||
// first pass: AST-tree preparation
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Preparing);
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Preparing
|
||||
);
|
||||
astTree = this.runPreparingStage(astTree);
|
||||
|
||||
// second pass: AST-tree analyzing
|
||||
this.logger.info(LoggingMessage.AnalyzingASTTreeStage);
|
||||
const stackTraceData: IStackTraceData[] = this.stackTraceAnalyzer.analyze(astTree);
|
||||
|
||||
// initialize custom node groups and configure custom nodes
|
||||
this.customNodeGroupStorage
|
||||
.getStorage()
|
||||
.forEach((customNodeGroup: ICustomNodeGroup) => {
|
||||
@@ -210,51 +202,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
|
||||
|
||||
this.obfuscationEventEmitter.emit(ObfuscationEvent.BeforeObfuscation, astTree, stackTraceData);
|
||||
|
||||
// third pass: dead code injection transformer
|
||||
if (this.options.deadCodeInjection) {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.DeadCodeInjection);
|
||||
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.DeadCodeInjection
|
||||
);
|
||||
}
|
||||
|
||||
// fourth pass: control flow flattening transformers
|
||||
if (this.options.controlFlowFlattening) {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.ControlFlowFlattening);
|
||||
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.ControlFlowFlattening
|
||||
);
|
||||
}
|
||||
|
||||
// fifth pass: converting transformers
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Converting);
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Converting
|
||||
);
|
||||
|
||||
// sixth pass: obfuscating transformers
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Obfuscating);
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Obfuscating
|
||||
);
|
||||
|
||||
// seventh pass: finalizing transformers
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Finalizing);
|
||||
astTree = this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Finalizing
|
||||
);
|
||||
astTree = this.runDeadCodeInjectionStage(astTree);
|
||||
astTree = this.runControlFlowFlatteningStage(astTree);
|
||||
astTree = this.runConvertingStage(astTree);
|
||||
astTree = this.runObfuscatingStage(astTree);
|
||||
astTree = this.runFinalizingStage(astTree);
|
||||
|
||||
this.obfuscationEventEmitter.emit(ObfuscationEvent.AfterObfuscation, astTree, stackTraceData);
|
||||
|
||||
@@ -298,4 +250,92 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
|
||||
generatorOutput.map
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runPreparingStage (astTree: ESTree.Program): ESTree.Program {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Preparing);
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Preparing
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runDeadCodeInjectionStage (astTree: ESTree.Program): ESTree.Program {
|
||||
if (this.options.deadCodeInjection) {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.DeadCodeInjection);
|
||||
}
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.DeadCodeInjection
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runControlFlowFlatteningStage (astTree: ESTree.Program): ESTree.Program {
|
||||
if (this.options.controlFlowFlattening) {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.ControlFlowFlattening);
|
||||
}
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.ControlFlowFlattening
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runConvertingStage (astTree: ESTree.Program): ESTree.Program {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Converting);
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Converting
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runObfuscatingStage (astTree: ESTree.Program): ESTree.Program {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Obfuscating);
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Obfuscating
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Program} astTree
|
||||
* @returns {Program}
|
||||
*/
|
||||
private runFinalizingStage (astTree: ESTree.Program): ESTree.Program {
|
||||
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Finalizing);
|
||||
|
||||
return this.transformersRunner.transform(
|
||||
astTree,
|
||||
JavaScriptObfuscator.transformersList,
|
||||
TransformationStage.Finalizing
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
|
||||
* @type {BlockStatement}
|
||||
*/
|
||||
@initializable()
|
||||
private randomBlockStatementNode: BlockStatement;
|
||||
private deadCodeInjectionRootAstHostNode: BlockStatement;
|
||||
|
||||
/**
|
||||
* @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
|
||||
@@ -45,14 +45,14 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
|
||||
|
||||
/**
|
||||
* @param {BlockStatement} blockStatementNode
|
||||
* @param {BlockStatement} randomBlockStatementNode
|
||||
* @param {BlockStatement} deadCodeInjectionRootAstHostNode
|
||||
*/
|
||||
public initialize (
|
||||
blockStatementNode: BlockStatement,
|
||||
randomBlockStatementNode: BlockStatement
|
||||
deadCodeInjectionRootAstHostNode: BlockStatement
|
||||
): void {
|
||||
this.blockStatementNode = blockStatementNode;
|
||||
this.randomBlockStatementNode = randomBlockStatementNode;
|
||||
this.deadCodeInjectionRootAstHostNode = deadCodeInjectionRootAstHostNode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,8 +67,8 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
|
||||
const rightString: string = random2 ? leftString : this.randomGenerator.getRandomString(5);
|
||||
|
||||
const [consequent, alternate]: [BlockStatement, BlockStatement] = random1 === random2
|
||||
? [this.blockStatementNode, this.randomBlockStatementNode]
|
||||
: [this.randomBlockStatementNode, this.blockStatementNode];
|
||||
? [this.blockStatementNode, this.deadCodeInjectionRootAstHostNode]
|
||||
: [this.deadCodeInjectionRootAstHostNode, this.blockStatementNode];
|
||||
|
||||
const structure: BlockStatement = Nodes.getBlockStatementNode([
|
||||
Nodes.getIfStatementNode(
|
||||
|
||||
@@ -18,6 +18,12 @@ export interface INodeTransformer {
|
||||
*/
|
||||
analyzeNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {Node | null} parentNode
|
||||
*/
|
||||
restoreNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @param {Node | null} parentNode
|
||||
|
||||
+4
@@ -102,6 +102,10 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme
|
||||
* @returns {IVisitor | null}
|
||||
*/
|
||||
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
|
||||
if (!this.options.controlFlowFlattening) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (transformationStage) {
|
||||
case TransformationStage.ControlFlowFlattening:
|
||||
return {
|
||||
|
||||
@@ -106,6 +106,10 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
|
||||
* @returns {IVisitor | null}
|
||||
*/
|
||||
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
|
||||
if (!this.options.controlFlowFlattening) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (transformationStage) {
|
||||
case TransformationStage.ControlFlowFlattening:
|
||||
return {
|
||||
|
||||
+61
-35
@@ -10,16 +10,15 @@ import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
|
||||
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
|
||||
import { IOptions } from '../../interfaces/options/IOptions';
|
||||
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
|
||||
import { ITransformersRunner } from '../../interfaces/node-transformers/ITransformersRunner';
|
||||
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
|
||||
|
||||
import { DeadCodeInjectionCustomNode } from '../../enums/custom-nodes/DeadCodeInjectionCustomNode';
|
||||
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
|
||||
import { NodeType } from '../../enums/node/NodeType';
|
||||
import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
|
||||
|
||||
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
|
||||
import { NodeGuards } from '../../node/NodeGuards';
|
||||
import { Nodes } from '../../node/Nodes';
|
||||
import { NodeUtils } from '../../node/NodeUtils';
|
||||
|
||||
@injectable()
|
||||
@@ -35,16 +34,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
private static readonly minCollectedBlockStatementsCount: number = 5;
|
||||
|
||||
/**
|
||||
* @type {NodeTransformer[]}
|
||||
* @type {WeakSet <BlockStatement>}
|
||||
*/
|
||||
private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
|
||||
NodeTransformer.CatchClauseTransformer,
|
||||
NodeTransformer.ClassDeclarationTransformer,
|
||||
NodeTransformer.FunctionDeclarationTransformer,
|
||||
NodeTransformer.FunctionTransformer,
|
||||
NodeTransformer.LabeledStatementTransformer,
|
||||
NodeTransformer.VariableDeclarationTransformer
|
||||
];
|
||||
private readonly deadCodeInjectionRootAstHostNodeSet: WeakSet <ESTree.BlockStatement> = new WeakSet();
|
||||
|
||||
/**
|
||||
* @type {ESTree.BlockStatement[]}
|
||||
@@ -61,28 +53,20 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
*/
|
||||
private readonly deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory;
|
||||
|
||||
/**
|
||||
* @type {ITransformersRunner}
|
||||
*/
|
||||
private readonly transformersRunner: ITransformersRunner;
|
||||
|
||||
/**
|
||||
* @param {TControlFlowCustomNodeFactory} deadCodeInjectionCustomNodeFactory
|
||||
* @param {ITransformersRunner} transformersRunner
|
||||
* @param {IRandomGenerator} randomGenerator
|
||||
* @param {IOptions} options
|
||||
*/
|
||||
constructor (
|
||||
@inject(ServiceIdentifiers.Factory__IDeadCodeInjectionCustomNode)
|
||||
deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory,
|
||||
@inject(ServiceIdentifiers.ITransformersRunner) transformersRunner: ITransformersRunner,
|
||||
@inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
|
||||
@inject(ServiceIdentifiers.IOptions) options: IOptions
|
||||
) {
|
||||
super(randomGenerator, options);
|
||||
|
||||
this.deadCodeInjectionCustomNodeFactory = deadCodeInjectionCustomNodeFactory;
|
||||
this.transformersRunner = transformersRunner;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +85,10 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
* @returns {IVisitor | null}
|
||||
*/
|
||||
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
|
||||
if (!this.options.deadCodeInjection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (transformationStage) {
|
||||
case TransformationStage.DeadCodeInjection:
|
||||
return {
|
||||
@@ -118,6 +106,15 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
}
|
||||
};
|
||||
|
||||
case TransformationStage.Finalizing:
|
||||
return {
|
||||
enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
|
||||
if (parentNode && this.isDeadCodeInjectionRootAstHostNode(node)) {
|
||||
return this.restoreNode(node, parentNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -176,6 +173,21 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode, parentNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FunctionExpression} deadCodeInjectionRootAstHostNode
|
||||
* @param {Node} parentNode
|
||||
* @returns {Node}
|
||||
*/
|
||||
public restoreNode (deadCodeInjectionRootAstHostNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
|
||||
const hostNodeFirstStatement: ESTree.Statement = deadCodeInjectionRootAstHostNode.body[0];
|
||||
|
||||
if (!NodeGuards.isFunctionDeclarationNode(hostNodeFirstStatement)) {
|
||||
throw new Error('Wrong dead code injection root AST host node. Host node should contain `FunctionDeclaration` node');
|
||||
}
|
||||
|
||||
return hostNodeFirstStatement.body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BlockStatement} blockStatementNode
|
||||
* @param {BlockStatement[]} collectedBlockStatements
|
||||
@@ -186,8 +198,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
collectedBlockStatements: ESTree.BlockStatement[],
|
||||
parentNode: ESTree.Node
|
||||
): void {
|
||||
let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode),
|
||||
nestedBlockStatementsCount: number = 0,
|
||||
const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode);
|
||||
|
||||
let nestedBlockStatementsCount: number = 0,
|
||||
isValidBlockStatementNode: boolean = true;
|
||||
|
||||
estraverse.replace(clonedBlockStatementNode, {
|
||||
@@ -220,19 +233,17 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* We should transform identifiers in the dead code block statement to avoid conflicts with original code
|
||||
*/
|
||||
NodeUtils.parentizeNode(clonedBlockStatementNode, parentNode);
|
||||
clonedBlockStatementNode = this.transformersRunner.transform(
|
||||
clonedBlockStatementNode,
|
||||
DeadCodeInjectionTransformer.transformersToRenameBlockScopeIdentifiers,
|
||||
TransformationStage.Obfuscating
|
||||
);
|
||||
|
||||
collectedBlockStatements.push(clonedBlockStatementNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isDeadCodeInjectionRootAstHostNode (node: ESTree.Node): node is ESTree.BlockStatement {
|
||||
return NodeGuards.isBlockStatementNode(node) && this.deadCodeInjectionRootAstHostNodeSet.has(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BlockStatement} blockStatementNode
|
||||
* @param {BlockStatement} randomBlockStatementNode
|
||||
@@ -244,14 +255,29 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
|
||||
randomBlockStatementNode: ESTree.BlockStatement,
|
||||
parentNode: ESTree.Node
|
||||
): ESTree.BlockStatement {
|
||||
/**
|
||||
* we should wrap original random block statement node into the parent block statement node (ast root host node)
|
||||
* with function declaration node. This function declaration node will create block scope for all identifiers
|
||||
* inside random block statement node and this identifiers won't affect identifiers of the rest AST tree.
|
||||
*/
|
||||
const deadCodeInjectionRootAstHostNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
|
||||
Nodes.getFunctionDeclarationNode(
|
||||
'foobar',
|
||||
[],
|
||||
randomBlockStatementNode
|
||||
)
|
||||
]);
|
||||
|
||||
/**
|
||||
* we should store that host node and then extract random block statement node on the `finalizing` stage
|
||||
*/
|
||||
this.deadCodeInjectionRootAstHostNodeSet.add(deadCodeInjectionRootAstHostNode);
|
||||
|
||||
const blockStatementDeadCodeInjectionCustomNode: ICustomNode = this.deadCodeInjectionCustomNodeFactory(
|
||||
DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode
|
||||
);
|
||||
|
||||
blockStatementDeadCodeInjectionCustomNode.initialize(
|
||||
blockStatementNode,
|
||||
randomBlockStatementNode
|
||||
);
|
||||
blockStatementDeadCodeInjectionCustomNode.initialize(blockStatementNode, deadCodeInjectionRootAstHostNode);
|
||||
|
||||
const newBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>blockStatementDeadCodeInjectionCustomNode.getNode()[0];
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
|
||||
/**
|
||||
* @type {WeakSet <FunctionExpression>}
|
||||
*/
|
||||
private readonly evalRootAstHostNodeStorage: WeakSet <ESTree.FunctionExpression> = new WeakSet();
|
||||
private readonly evalRootAstHostNodeSet: WeakSet <ESTree.FunctionExpression> = new WeakSet();
|
||||
|
||||
/**
|
||||
* @param {IRandomGenerator} randomGenerator
|
||||
@@ -100,7 +100,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
|
||||
return {
|
||||
leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
|
||||
if (parentNode && this.isEvalRootAstHostNode(node)) {
|
||||
return this.restoreEvalExpressionNode(node, parentNode);
|
||||
return this.restoreNode(node, parentNode);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -138,28 +138,27 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
|
||||
return callExpressionNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* we should wrap AST-tree into the parent function expression node (ast root host node).
|
||||
* This function expression node will help to correctly transform AST-tree.
|
||||
*/
|
||||
const evalRootAstHostNode: ESTree.FunctionExpression = Nodes
|
||||
.getFunctionExpressionNode([], Nodes.getBlockStatementNode(<any>ast));
|
||||
|
||||
this.evalRootAstHostNodeStorage.add(evalRootAstHostNode);
|
||||
/**
|
||||
* we should store that host node and then extract AST-tree on the `finalizing` stage
|
||||
*/
|
||||
this.evalRootAstHostNodeSet.add(evalRootAstHostNode);
|
||||
|
||||
return evalRootAstHostNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
|
||||
return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeStorage.has(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FunctionExpression} evalRootAstHostNode
|
||||
* @param {Node} parentNode
|
||||
* @returns {Node}
|
||||
*/
|
||||
private restoreEvalExpressionNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
|
||||
public restoreNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
|
||||
const targetAst: ESTree.Statement[] = evalRootAstHostNode.body.body;
|
||||
const obfuscatedCode: string = NodeUtils.convertStructureToCode(targetAst);
|
||||
|
||||
@@ -170,4 +169,12 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
|
||||
return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeSet.has(node);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user