Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F102344337
JavascriptParser.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Wed, Feb 19, 17:35
Size
107 KB
Mime Type
text/x-c++
Expires
Fri, Feb 21, 17:35 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
24335172
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
JavascriptParser.js
View Options
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict"
;
const
{
Parser
:
AcornParser
}
=
require
(
"acorn"
);
const
{
SyncBailHook
,
HookMap
}
=
require
(
"tapable"
);
const
vm
=
require
(
"vm"
);
const
Parser
=
require
(
"../Parser"
);
const
StackedMap
=
require
(
"../util/StackedMap"
);
const
memorize
=
require
(
"../util/memorize"
);
const
BasicEvaluatedExpression
=
require
(
"./BasicEvaluatedExpression"
);
/** @typedef {import("acorn").Options} AcornOptions */
/** @typedef {import("estree").ArrayExpression} ArrayExpressionNode */
/** @typedef {import("estree").BinaryExpression} BinaryExpressionNode */
/** @typedef {import("estree").BlockStatement} BlockStatementNode */
/** @typedef {import("estree").SequenceExpression} SequenceExpressionNode */
/** @typedef {import("estree").CallExpression} CallExpressionNode */
/** @typedef {import("estree").ClassDeclaration} ClassDeclarationNode */
/** @typedef {import("estree").ClassExpression} ClassExpressionNode */
/** @typedef {import("estree").Comment} CommentNode */
/** @typedef {import("estree").ConditionalExpression} ConditionalExpressionNode */
/** @typedef {import("estree").Declaration} DeclarationNode */
/** @typedef {import("estree").Expression} ExpressionNode */
/** @typedef {import("estree").Identifier} IdentifierNode */
/** @typedef {import("estree").IfStatement} IfStatementNode */
/** @typedef {import("estree").LabeledStatement} LabeledStatementNode */
/** @typedef {import("estree").Literal} LiteralNode */
/** @typedef {import("estree").LogicalExpression} LogicalExpressionNode */
/** @typedef {import("estree").ChainExpression} ChainExpressionNode */
/** @typedef {import("estree").MemberExpression} MemberExpressionNode */
/** @typedef {import("estree").MetaProperty} MetaPropertyNode */
/** @typedef {import("estree").MethodDefinition} MethodDefinitionNode */
/** @typedef {import("estree").ModuleDeclaration} ModuleDeclarationNode */
/** @typedef {import("estree").NewExpression} NewExpressionNode */
/** @typedef {import("estree").Node} AnyNode */
/** @typedef {import("estree").Program} ProgramNode */
/** @typedef {import("estree").Statement} StatementNode */
/** @typedef {import("estree").Super} SuperNode */
/** @typedef {import("estree").TaggedTemplateExpression} TaggedTemplateExpressionNode */
/** @typedef {import("estree").TemplateLiteral} TemplateLiteralNode */
/** @typedef {import("estree").ThisExpression} ThisExpressionNode */
/** @typedef {import("estree").UnaryExpression} UnaryExpressionNode */
/** @typedef {import("estree").VariableDeclarator} VariableDeclaratorNode */
/** @template T @typedef {import("tapable").AsArray<T>} AsArray<T> */
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
/** @typedef {{declaredScope: ScopeInfo, freeName: string | true, tagInfo: TagInfo | undefined}} VariableInfoInterface */
/** @typedef {{ name: string | VariableInfo, rootInfo: string | VariableInfo, getMembers: () => string[] }} GetInfoResult */
const
EMPTY_ARRAY
=
[];
const
ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
=
0
b01
;
const
ALLOWED_MEMBER_TYPES_EXPRESSION
=
0
b10
;
const
ALLOWED_MEMBER_TYPES_ALL
=
0
b11
;
// Syntax: https://developer.mozilla.org/en/SpiderMonkey/Parser_API
const
parser
=
AcornParser
;
class
VariableInfo
{
/**
* @param {ScopeInfo} declaredScope scope in which the variable is declared
* @param {string | true} freeName which free name the variable aliases, or true when none
* @param {TagInfo | undefined} tagInfo info about tags
*/
constructor
(
declaredScope
,
freeName
,
tagInfo
)
{
this
.
declaredScope
=
declaredScope
;
this
.
freeName
=
freeName
;
this
.
tagInfo
=
tagInfo
;
}
}
/** @typedef {string | ScopeInfo | VariableInfo} ExportedVariableInfo */
/** @typedef {LiteralNode | string | null | undefined} ImportSource */
/** @typedef {Omit<AcornOptions, "sourceType" | "ecmaVersion"> & { sourceType: "module" | "script" | "auto", ecmaVersion?: AcornOptions["ecmaVersion"] }} ParseOptions */
/**
* @typedef {Object} TagInfo
* @property {any} tag
* @property {any} data
* @property {TagInfo | undefined} next
*/
/**
* @typedef {Object} ScopeInfo
* @property {StackedMap<string, VariableInfo | ScopeInfo>} definitions
* @property {boolean | "arrow"} topLevelScope
* @property {boolean} inShorthand
* @property {boolean} isStrict
* @property {boolean} isAsmJs
* @property {boolean} inTry
*/
const
joinRanges
=
(
startRange
,
endRange
)
=>
{
if
(
!
endRange
)
return
startRange
;
if
(
!
startRange
)
return
endRange
;
return
[
startRange
[
0
],
endRange
[
1
]];
};
const
objectAndMembersToName
=
(
object
,
membersReversed
)
=>
{
let
name
=
object
;
for
(
let
i
=
membersReversed
.
length
-
1
;
i
>=
0
;
i
--
)
{
name
=
name
+
"."
+
membersReversed
[
i
];
}
return
name
;
};
const
getRootName
=
expression
=>
{
switch
(
expression
.
type
)
{
case
"Identifier"
:
return
expression
.
name
;
case
"ThisExpression"
:
return
"this"
;
case
"MetaProperty"
:
return
`
$
{
expression
.
meta
.
name
}.
$
{
expression
.
property
.
name
}
`
;
default
:
return
undefined
;
}
};
/** @type {AcornOptions} */
const
defaultParserOptions
=
{
ranges
:
true
,
locations
:
true
,
ecmaVersion
:
"latest"
,
sourceType
:
"module"
,
allowAwaitOutsideFunction
:
true
,
onComment
:
null
};
// regexp to match at least one "magic comment"
const
webpackCommentRegExp
=
new
RegExp
(
/(^|\W)webpack[A-Z]{1,}[A-Za-z]{1,}:/
);
const
EMPTY_COMMENT_OPTIONS
=
{
options
:
null
,
errors
:
null
};
class
JavascriptParser
extends
Parser
{
/**
* @param {"module" | "script" | "auto"} sourceType default source type
*/
constructor
(
sourceType
=
"auto"
)
{
super
();
this
.
hooks
=
Object
.
freeze
({
/** @type {HookMap<SyncBailHook<[UnaryExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
evaluateTypeof
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[ExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
evaluate
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode | MetaPropertyNode], BasicEvaluatedExpression | undefined | null>>} */
evaluateIdentifier
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[IdentifierNode | ThisExpressionNode | MemberExpressionNode], BasicEvaluatedExpression | undefined | null>>} */
evaluateDefinedIdentifier
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
])
),
/** @type {HookMap<SyncBailHook<[CallExpressionNode, BasicEvaluatedExpression | undefined], BasicEvaluatedExpression | undefined | null>>} */
evaluateCallExpressionMember
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"param"
])
),
/** @type {HookMap<SyncBailHook<[ExpressionNode | DeclarationNode, number], boolean | void>>} */
isPure
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"commentsStartPosition"
])
),
/** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
preStatement
:
new
SyncBailHook
([
"statement"
]),
/** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
blockPreStatement
:
new
SyncBailHook
([
"declaration"
]),
/** @type {SyncBailHook<[StatementNode | ModuleDeclarationNode], boolean | void>} */
statement
:
new
SyncBailHook
([
"statement"
]),
/** @type {SyncBailHook<[IfStatementNode], boolean | void>} */
statementIf
:
new
SyncBailHook
([
"statement"
]),
/** @type {SyncBailHook<[ExpressionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
classExtendsExpression
:
new
SyncBailHook
([
"expression"
,
"statement"
]),
/** @type {SyncBailHook<[MethodDefinitionNode, ClassExpressionNode | ClassDeclarationNode], boolean | void>} */
classBodyElement
:
new
SyncBailHook
([
"element"
,
"statement"
]),
/** @type {HookMap<SyncBailHook<[LabeledStatementNode], boolean | void>>} */
label
:
new
HookMap
(()
=>
new
SyncBailHook
([
"statement"
])),
/** @type {SyncBailHook<[StatementNode, ImportSource], boolean | void>} */
import
:
new
SyncBailHook
([
"statement"
,
"source"
]),
/** @type {SyncBailHook<[StatementNode, ImportSource, string, string], boolean | void>} */
importSpecifier
:
new
SyncBailHook
([
"statement"
,
"source"
,
"exportName"
,
"identifierName"
]),
/** @type {SyncBailHook<[StatementNode], boolean | void>} */
export
:
new
SyncBailHook
([
"statement"
]),
/** @type {SyncBailHook<[StatementNode, ImportSource], boolean | void>} */
exportImport
:
new
SyncBailHook
([
"statement"
,
"source"
]),
/** @type {SyncBailHook<[StatementNode, DeclarationNode], boolean | void>} */
exportDeclaration
:
new
SyncBailHook
([
"statement"
,
"declaration"
]),
/** @type {SyncBailHook<[StatementNode, DeclarationNode], boolean | void>} */
exportExpression
:
new
SyncBailHook
([
"statement"
,
"declaration"
]),
/** @type {SyncBailHook<[StatementNode, string, string, number | undefined], boolean | void>} */
exportSpecifier
:
new
SyncBailHook
([
"statement"
,
"identifierName"
,
"exportName"
,
"index"
]),
/** @type {SyncBailHook<[StatementNode, ImportSource, string, string, number | undefined], boolean | void>} */
exportImportSpecifier
:
new
SyncBailHook
([
"statement"
,
"source"
,
"identifierName"
,
"exportName"
,
"index"
]),
/** @type {SyncBailHook<[VariableDeclaratorNode, StatementNode], boolean | void>} */
preDeclarator
:
new
SyncBailHook
([
"declarator"
,
"statement"
]),
/** @type {SyncBailHook<[VariableDeclaratorNode, StatementNode], boolean | void>} */
declarator
:
new
SyncBailHook
([
"declarator"
,
"statement"
]),
/** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
varDeclaration
:
new
HookMap
(()
=>
new
SyncBailHook
([
"declaration"
])),
/** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
varDeclarationLet
:
new
HookMap
(()
=>
new
SyncBailHook
([
"declaration"
])),
/** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
varDeclarationConst
:
new
HookMap
(()
=>
new
SyncBailHook
([
"declaration"
])),
/** @type {HookMap<SyncBailHook<[DeclarationNode], boolean | void>>} */
varDeclarationVar
:
new
HookMap
(()
=>
new
SyncBailHook
([
"declaration"
])),
pattern
:
new
HookMap
(()
=>
new
SyncBailHook
([
"pattern"
])),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
canRename
:
new
HookMap
(()
=>
new
SyncBailHook
([
"initExpression"
])),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
rename
:
new
HookMap
(()
=>
new
SyncBailHook
([
"initExpression"
])),
/** @type {HookMap<SyncBailHook<[import("estree").AssignmentExpression], boolean | void>>} */
assign
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[import("estree").AssignmentExpression, string[]], boolean | void>>} */
assignMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"members"
])
),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
typeof
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
importCall
:
new
SyncBailHook
([
"expression"
]),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
topLevelAwait
:
new
SyncBailHook
([
"expression"
]),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
call
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** Something like "a.b()" */
/** @type {HookMap<SyncBailHook<[CallExpressionNode, string[]], boolean | void>>} */
callMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"members"
])
),
/** Something like "a.b().c.d" */
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[], CallExpressionNode, string[]], boolean | void>>} */
memberChainOfCallMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"calleeMembers"
,
"callExpression"
,
"members"
])
),
/** Something like "a.b().c.d()"" */
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[], CallExpressionNode, string[]], boolean | void>>} */
callMemberChainOfCallMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"calleeMembers"
,
"innerCallExpression"
,
"members"
])
),
/** @type {SyncBailHook<[ChainExpressionNode], boolean | void>} */
optionalChaining
:
new
SyncBailHook
([
"optionalChaining"
]),
/** @type {HookMap<SyncBailHook<[NewExpressionNode], boolean | void>>} */
new
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[ExpressionNode], boolean | void>>} */
expression
:
new
HookMap
(()
=>
new
SyncBailHook
([
"expression"
])),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
expressionMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"members"
])
),
/** @type {HookMap<SyncBailHook<[ExpressionNode, string[]], boolean | void>>} */
unhandledExpressionMemberChain
:
new
HookMap
(
()
=>
new
SyncBailHook
([
"expression"
,
"members"
])
),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
expressionConditionalOperator
:
new
SyncBailHook
([
"expression"
]),
/** @type {SyncBailHook<[ExpressionNode], boolean | void>} */
expressionLogicalOperator
:
new
SyncBailHook
([
"expression"
]),
/** @type {SyncBailHook<[ProgramNode, CommentNode[]], boolean | void>} */
program
:
new
SyncBailHook
([
"ast"
,
"comments"
]),
/** @type {SyncBailHook<[ProgramNode, CommentNode[]], boolean | void>} */
finish
:
new
SyncBailHook
([
"ast"
,
"comments"
])
});
this
.
sourceType
=
sourceType
;
/** @type {ScopeInfo} */
this
.
scope
=
undefined
;
/** @type {ParserState} */
this
.
state
=
undefined
;
this
.
comments
=
undefined
;
this
.
semicolons
=
undefined
;
/** @type {(StatementNode|ExpressionNode)[]} */
this
.
statementPath
=
undefined
;
this
.
prevStatement
=
undefined
;
this
.
currentTagData
=
undefined
;
this
.
_initializeEvaluating
();
}
_initializeEvaluating
()
{
this
.
hooks
.
evaluate
.
for
(
"Literal"
).
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {LiteralNode} */
(
_expr
);
switch
(
typeof
expr
.
value
)
{
case
"number"
:
return
new
BasicEvaluatedExpression
()
.
setNumber
(
expr
.
value
)
.
setRange
(
expr
.
range
);
case
"bigint"
:
return
new
BasicEvaluatedExpression
()
.
setBigInt
(
expr
.
value
)
.
setRange
(
expr
.
range
);
case
"string"
:
return
new
BasicEvaluatedExpression
()
.
setString
(
expr
.
value
)
.
setRange
(
expr
.
range
);
case
"boolean"
:
return
new
BasicEvaluatedExpression
()
.
setBoolean
(
expr
.
value
)
.
setRange
(
expr
.
range
);
}
if
(
expr
.
value
===
null
)
{
return
new
BasicEvaluatedExpression
().
setNull
().
setRange
(
expr
.
range
);
}
if
(
expr
.
value
instanceof
RegExp
)
{
return
new
BasicEvaluatedExpression
()
.
setRegExp
(
expr
.
value
)
.
setRange
(
expr
.
range
);
}
});
this
.
hooks
.
evaluate
.
for
(
"NewExpression"
).
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {NewExpressionNode} */
(
_expr
);
const
callee
=
expr
.
callee
;
if
(
callee
.
type
!==
"Identifier"
||
callee
.
name
!==
"RegExp"
||
expr
.
arguments
.
length
>
2
||
this
.
getVariableInfo
(
"RegExp"
)
!==
"RegExp"
)
return
;
let
regExp
,
flags
;
const
arg1
=
expr
.
arguments
[
0
];
if
(
arg1
)
{
if
(
arg1
.
type
===
"SpreadElement"
)
return
;
const
evaluatedRegExp
=
this
.
evaluateExpression
(
arg1
);
if
(
!
evaluatedRegExp
)
return
;
regExp
=
evaluatedRegExp
.
asString
();
if
(
!
regExp
)
return
;
}
else
{
return
new
BasicEvaluatedExpression
()
.
setRegExp
(
new
RegExp
(
""
))
.
setRange
(
expr
.
range
);
}
const
arg2
=
expr
.
arguments
[
1
];
if
(
arg2
)
{
if
(
arg2
.
type
===
"SpreadElement"
)
return
;
const
evaluatedFlags
=
this
.
evaluateExpression
(
arg2
);
if
(
!
evaluatedFlags
)
return
;
if
(
!
evaluatedFlags
.
isUndefined
())
{
flags
=
evaluatedFlags
.
asString
();
if
(
flags
===
undefined
||
!
BasicEvaluatedExpression
.
isValidRegExpFlags
(
flags
)
)
return
;
}
}
return
new
BasicEvaluatedExpression
()
.
setRegExp
(
flags
?
new
RegExp
(
regExp
,
flags
)
:
new
RegExp
(
regExp
))
.
setRange
(
expr
.
range
);
});
this
.
hooks
.
evaluate
.
for
(
"LogicalExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {LogicalExpressionNode} */
(
_expr
);
const
left
=
this
.
evaluateExpression
(
expr
.
left
);
if
(
!
left
)
return
;
if
(
expr
.
operator
===
"&&"
)
{
const
leftAsBool
=
left
.
asBool
();
if
(
leftAsBool
===
false
)
return
left
.
setRange
(
expr
.
range
);
if
(
leftAsBool
!==
true
)
return
;
}
else
if
(
expr
.
operator
===
"||"
)
{
const
leftAsBool
=
left
.
asBool
();
if
(
leftAsBool
===
true
)
return
left
.
setRange
(
expr
.
range
);
if
(
leftAsBool
!==
false
)
return
;
}
else
if
(
expr
.
operator
===
"??"
)
{
const
leftAsNullish
=
left
.
asNullish
();
if
(
leftAsNullish
===
false
)
return
left
.
setRange
(
expr
.
range
);
if
(
leftAsNullish
!==
true
)
return
;
}
else
return
;
const
right
=
this
.
evaluateExpression
(
expr
.
right
);
if
(
!
right
)
return
;
if
(
left
.
couldHaveSideEffects
())
right
.
setSideEffects
();
return
right
.
setRange
(
expr
.
range
);
});
const
valueAsExpression
=
(
value
,
expr
,
sideEffects
)
=>
{
switch
(
typeof
value
)
{
case
"boolean"
:
return
new
BasicEvaluatedExpression
()
.
setBoolean
(
value
)
.
setSideEffects
(
sideEffects
)
.
setRange
(
expr
.
range
);
case
"number"
:
return
new
BasicEvaluatedExpression
()
.
setNumber
(
value
)
.
setSideEffects
(
sideEffects
)
.
setRange
(
expr
.
range
);
case
"bigint"
:
return
new
BasicEvaluatedExpression
()
.
setBigInt
(
value
)
.
setSideEffects
(
sideEffects
)
.
setRange
(
expr
.
range
);
case
"string"
:
return
new
BasicEvaluatedExpression
()
.
setString
(
value
)
.
setSideEffects
(
sideEffects
)
.
setRange
(
expr
.
range
);
}
};
this
.
hooks
.
evaluate
.
for
(
"BinaryExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {BinaryExpressionNode} */
(
_expr
);
const
handleConstOperation
=
fn
=>
{
const
left
=
this
.
evaluateExpression
(
expr
.
left
);
if
(
!
left
||
!
left
.
isCompileTimeValue
())
return
;
const
right
=
this
.
evaluateExpression
(
expr
.
right
);
if
(
!
right
||
!
right
.
isCompileTimeValue
())
return
;
const
result
=
fn
(
left
.
asCompileTimeValue
(),
right
.
asCompileTimeValue
()
);
return
valueAsExpression
(
result
,
expr
,
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
};
const
isAlwaysDifferent
=
(
a
,
b
)
=>
(
a
===
true
&&
b
===
false
)
||
(
a
===
false
&&
b
===
true
);
const
handleTemplateStringCompare
=
(
left
,
right
,
res
,
eql
)
=>
{
const
getPrefix
=
parts
=>
{
let
value
=
""
;
for
(
const
p
of
parts
)
{
const
v
=
p
.
asString
();
if
(
v
!==
undefined
)
value
+=
v
;
else
break
;
}
return
value
;
};
const
getSuffix
=
parts
=>
{
let
value
=
""
;
for
(
let
i
=
parts
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
v
=
parts
[
i
].
asString
();
if
(
v
!==
undefined
)
value
=
v
+
value
;
else
break
;
}
return
value
;
};
const
leftPrefix
=
getPrefix
(
left
.
parts
);
const
rightPrefix
=
getPrefix
(
right
.
parts
);
const
leftSuffix
=
getSuffix
(
left
.
parts
);
const
rightSuffix
=
getSuffix
(
right
.
parts
);
const
lenPrefix
=
Math
.
min
(
leftPrefix
.
length
,
rightPrefix
.
length
);
const
lenSuffix
=
Math
.
min
(
leftSuffix
.
length
,
rightSuffix
.
length
);
if
(
leftPrefix
.
slice
(
0
,
lenPrefix
)
!==
rightPrefix
.
slice
(
0
,
lenPrefix
)
||
leftSuffix
.
slice
(
-
lenSuffix
)
!==
rightSuffix
.
slice
(
-
lenSuffix
)
)
{
return
res
.
setBoolean
(
!
eql
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
};
const
handleStrictEqualityComparison
=
eql
=>
{
const
left
=
this
.
evaluateExpression
(
expr
.
left
);
if
(
!
left
)
return
;
const
right
=
this
.
evaluateExpression
(
expr
.
right
);
if
(
!
right
)
return
;
const
res
=
new
BasicEvaluatedExpression
();
res
.
setRange
(
expr
.
range
);
const
leftConst
=
left
.
isCompileTimeValue
();
const
rightConst
=
right
.
isCompileTimeValue
();
if
(
leftConst
&&
rightConst
)
{
return
res
.
setBoolean
(
eql
===
(
left
.
asCompileTimeValue
()
===
right
.
asCompileTimeValue
())
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
if
(
left
.
isArray
()
&&
right
.
isArray
())
{
return
res
.
setBoolean
(
!
eql
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
if
(
left
.
isTemplateString
()
&&
right
.
isTemplateString
())
{
return
handleTemplateStringCompare
(
left
,
right
,
res
,
eql
);
}
const
leftPrimitive
=
left
.
isPrimitiveType
();
const
rightPrimitive
=
right
.
isPrimitiveType
();
if
(
// Primitive !== Object or
// compile-time object types are never equal to something at runtime
(
leftPrimitive
===
false
&&
(
leftConst
||
rightPrimitive
===
true
))
||
(
rightPrimitive
===
false
&&
(
rightConst
||
leftPrimitive
===
true
))
||
// Different nullish or boolish status also means not equal
isAlwaysDifferent
(
left
.
asBool
(),
right
.
asBool
())
||
isAlwaysDifferent
(
left
.
asNullish
(),
right
.
asNullish
())
)
{
return
res
.
setBoolean
(
!
eql
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
};
const
handleAbstractEqualityComparison
=
eql
=>
{
const
left
=
this
.
evaluateExpression
(
expr
.
left
);
if
(
!
left
)
return
;
const
right
=
this
.
evaluateExpression
(
expr
.
right
);
if
(
!
right
)
return
;
const
res
=
new
BasicEvaluatedExpression
();
res
.
setRange
(
expr
.
range
);
const
leftConst
=
left
.
isCompileTimeValue
();
const
rightConst
=
right
.
isCompileTimeValue
();
if
(
leftConst
&&
rightConst
)
{
return
res
.
setBoolean
(
eql
===
// eslint-disable-next-line eqeqeq
(
left
.
asCompileTimeValue
()
==
right
.
asCompileTimeValue
())
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
if
(
left
.
isArray
()
&&
right
.
isArray
())
{
return
res
.
setBoolean
(
!
eql
)
.
setSideEffects
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
()
);
}
if
(
left
.
isTemplateString
()
&&
right
.
isTemplateString
())
{
return
handleTemplateStringCompare
(
left
,
right
,
res
,
eql
);
}
};
if
(
expr
.
operator
===
"+"
)
{
const
left
=
this
.
evaluateExpression
(
expr
.
left
);
if
(
!
left
)
return
;
const
right
=
this
.
evaluateExpression
(
expr
.
right
);
if
(
!
right
)
return
;
const
res
=
new
BasicEvaluatedExpression
();
if
(
left
.
isString
())
{
if
(
right
.
isString
())
{
res
.
setString
(
left
.
string
+
right
.
string
);
}
else
if
(
right
.
isNumber
())
{
res
.
setString
(
left
.
string
+
right
.
number
);
}
else
if
(
right
.
isWrapped
()
&&
right
.
prefix
&&
right
.
prefix
.
isString
()
)
{
// "left" + ("prefix" + inner + "postfix")
// => ("leftPrefix" + inner + "postfix")
res
.
setWrapped
(
new
BasicEvaluatedExpression
()
.
setString
(
left
.
string
+
right
.
prefix
.
string
)
.
setRange
(
joinRanges
(
left
.
range
,
right
.
prefix
.
range
)),
right
.
postfix
,
right
.
wrappedInnerExpressions
);
}
else
if
(
right
.
isWrapped
())
{
// "left" + ([null] + inner + "postfix")
// => ("left" + inner + "postfix")
res
.
setWrapped
(
left
,
right
.
postfix
,
right
.
wrappedInnerExpressions
);
}
else
{
// "left" + expr
// => ("left" + expr + "")
res
.
setWrapped
(
left
,
null
,
[
right
]);
}
}
else
if
(
left
.
isNumber
())
{
if
(
right
.
isString
())
{
res
.
setString
(
left
.
number
+
right
.
string
);
}
else
if
(
right
.
isNumber
())
{
res
.
setNumber
(
left
.
number
+
right
.
number
);
}
else
{
return
;
}
}
else
if
(
left
.
isBigInt
())
{
if
(
right
.
isBigInt
())
{
res
.
setBigInt
(
left
.
bigint
+
right
.
bigint
);
}
}
else
if
(
left
.
isWrapped
())
{
if
(
left
.
postfix
&&
left
.
postfix
.
isString
()
&&
right
.
isString
())
{
// ("prefix" + inner + "postfix") + "right"
// => ("prefix" + inner + "postfixRight")
res
.
setWrapped
(
left
.
prefix
,
new
BasicEvaluatedExpression
()
.
setString
(
left
.
postfix
.
string
+
right
.
string
)
.
setRange
(
joinRanges
(
left
.
postfix
.
range
,
right
.
range
)),
left
.
wrappedInnerExpressions
);
}
else
if
(
left
.
postfix
&&
left
.
postfix
.
isString
()
&&
right
.
isNumber
()
)
{
// ("prefix" + inner + "postfix") + 123
// => ("prefix" + inner + "postfix123")
res
.
setWrapped
(
left
.
prefix
,
new
BasicEvaluatedExpression
()
.
setString
(
left
.
postfix
.
string
+
right
.
number
)
.
setRange
(
joinRanges
(
left
.
postfix
.
range
,
right
.
range
)),
left
.
wrappedInnerExpressions
);
}
else
if
(
right
.
isString
())
{
// ("prefix" + inner + [null]) + "right"
// => ("prefix" + inner + "right")
res
.
setWrapped
(
left
.
prefix
,
right
,
left
.
wrappedInnerExpressions
);
}
else
if
(
right
.
isNumber
())
{
// ("prefix" + inner + [null]) + 123
// => ("prefix" + inner + "123")
res
.
setWrapped
(
left
.
prefix
,
new
BasicEvaluatedExpression
()
.
setString
(
right
.
number
+
""
)
.
setRange
(
right
.
range
),
left
.
wrappedInnerExpressions
);
}
else
if
(
right
.
isWrapped
())
{
// ("prefix1" + inner1 + "postfix1") + ("prefix2" + inner2 + "postfix2")
// ("prefix1" + inner1 + "postfix1" + "prefix2" + inner2 + "postfix2")
res
.
setWrapped
(
left
.
prefix
,
right
.
postfix
,
left
.
wrappedInnerExpressions
&&
right
.
wrappedInnerExpressions
&&
left
.
wrappedInnerExpressions
.
concat
(
left
.
postfix
?
[
left
.
postfix
]
:
[])
.
concat
(
right
.
prefix
?
[
right
.
prefix
]
:
[])
.
concat
(
right
.
wrappedInnerExpressions
)
);
}
else
{
// ("prefix" + inner + postfix) + expr
// => ("prefix" + inner + postfix + expr + [null])
res
.
setWrapped
(
left
.
prefix
,
null
,
left
.
wrappedInnerExpressions
&&
left
.
wrappedInnerExpressions
.
concat
(
left
.
postfix
?
[
left
.
postfix
,
right
]
:
[
right
]
)
);
}
}
else
{
if
(
right
.
isString
())
{
// left + "right"
// => ([null] + left + "right")
res
.
setWrapped
(
null
,
right
,
[
left
]);
}
else
if
(
right
.
isWrapped
())
{
// left + (prefix + inner + "postfix")
// => ([null] + left + prefix + inner + "postfix")
res
.
setWrapped
(
null
,
right
.
postfix
,
right
.
wrappedInnerExpressions
&&
(
right
.
prefix
?
[
left
,
right
.
prefix
]
:
[
left
]).
concat
(
right
.
wrappedInnerExpressions
)
);
}
else
{
return
;
}
}
if
(
left
.
couldHaveSideEffects
()
||
right
.
couldHaveSideEffects
())
res
.
setSideEffects
();
res
.
setRange
(
expr
.
range
);
return
res
;
}
else
if
(
expr
.
operator
===
"-"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
-
r
);
}
else
if
(
expr
.
operator
===
"*"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
*
r
);
}
else
if
(
expr
.
operator
===
"/"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
/
r
);
}
else
if
(
expr
.
operator
===
"**"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
**
r
);
}
else
if
(
expr
.
operator
===
"==="
)
{
return
handleStrictEqualityComparison
(
true
);
}
else
if
(
expr
.
operator
===
"=="
)
{
return
handleAbstractEqualityComparison
(
true
);
}
else
if
(
expr
.
operator
===
"!=="
)
{
return
handleStrictEqualityComparison
(
false
);
}
else
if
(
expr
.
operator
===
"!="
)
{
return
handleAbstractEqualityComparison
(
false
);
}
else
if
(
expr
.
operator
===
"&"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
&
r
);
}
else
if
(
expr
.
operator
===
"|"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
|
r
);
}
else
if
(
expr
.
operator
===
"^"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
^
r
);
}
else
if
(
expr
.
operator
===
">>>"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
>>>
r
);
}
else
if
(
expr
.
operator
===
">>"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
>>
r
);
}
else
if
(
expr
.
operator
===
"<<"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
<<
r
);
}
else
if
(
expr
.
operator
===
"<"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
<
r
);
}
else
if
(
expr
.
operator
===
">"
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
>
r
);
}
else
if
(
expr
.
operator
===
"<="
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
<=
r
);
}
else
if
(
expr
.
operator
===
">="
)
{
return
handleConstOperation
((
l
,
r
)
=>
l
>=
r
);
}
});
this
.
hooks
.
evaluate
.
for
(
"UnaryExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {UnaryExpressionNode} */
(
_expr
);
const
handleConstOperation
=
fn
=>
{
const
argument
=
this
.
evaluateExpression
(
expr
.
argument
);
if
(
!
argument
||
!
argument
.
isCompileTimeValue
())
return
;
const
result
=
fn
(
argument
.
asCompileTimeValue
());
return
valueAsExpression
(
result
,
expr
,
argument
.
couldHaveSideEffects
()
);
};
if
(
expr
.
operator
===
"typeof"
)
{
switch
(
expr
.
argument
.
type
)
{
case
"Identifier"
:
{
const
res
=
this
.
callHooksForName
(
this
.
hooks
.
evaluateTypeof
,
expr
.
argument
.
name
,
expr
);
if
(
res
!==
undefined
)
return
res
;
break
;
}
case
"MetaProperty"
:
{
const
res
=
this
.
callHooksForName
(
this
.
hooks
.
evaluateTypeof
,
"import.meta"
,
expr
);
if
(
res
!==
undefined
)
return
res
;
break
;
}
case
"MemberExpression"
:
{
const
res
=
this
.
callHooksForExpression
(
this
.
hooks
.
evaluateTypeof
,
expr
.
argument
,
expr
);
if
(
res
!==
undefined
)
return
res
;
break
;
}
case
"ChainExpression"
:
{
const
res
=
this
.
callHooksForExpression
(
this
.
hooks
.
evaluateTypeof
,
expr
.
argument
.
expression
,
expr
);
if
(
res
!==
undefined
)
return
res
;
break
;
}
case
"FunctionExpression"
:
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"function"
)
.
setRange
(
expr
.
range
);
}
}
const
arg
=
this
.
evaluateExpression
(
expr
.
argument
);
if
(
arg
.
isUnknown
())
return
;
if
(
arg
.
isString
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"string"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isWrapped
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"string"
)
.
setSideEffects
()
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isUndefined
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"undefined"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isNumber
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"number"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isBigInt
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"bigint"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isBoolean
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"boolean"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isConstArray
()
||
arg
.
isRegExp
()
||
arg
.
isNull
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"object"
)
.
setRange
(
expr
.
range
);
}
if
(
arg
.
isArray
())
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"object"
)
.
setSideEffects
(
arg
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
}
}
else
if
(
expr
.
operator
===
"!"
)
{
const
argument
=
this
.
evaluateExpression
(
expr
.
argument
);
if
(
!
argument
)
return
;
const
bool
=
argument
.
asBool
();
if
(
typeof
bool
!==
"boolean"
)
return
;
return
new
BasicEvaluatedExpression
()
.
setBoolean
(
!
bool
)
.
setSideEffects
(
argument
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
}
else
if
(
expr
.
operator
===
"~"
)
{
return
handleConstOperation
(
v
=>
~
v
);
}
else
if
(
expr
.
operator
===
"+"
)
{
return
handleConstOperation
(
v
=>
+
v
);
}
else
if
(
expr
.
operator
===
"-"
)
{
return
handleConstOperation
(
v
=>
-
v
);
}
});
this
.
hooks
.
evaluateTypeof
.
for
(
"undefined"
).
tap
(
"JavascriptParser"
,
expr
=>
{
return
new
BasicEvaluatedExpression
()
.
setString
(
"undefined"
)
.
setRange
(
expr
.
range
);
});
/**
* @param {string} exprType expression type name
* @param {function(ExpressionNode): GetInfoResult | undefined} getInfo get info
* @returns {void}
*/
const
tapEvaluateWithVariableInfo
=
(
exprType
,
getInfo
)
=>
{
/** @type {ExpressionNode | undefined} */
let
cachedExpression
=
undefined
;
/** @type {GetInfoResult | undefined} */
let
cachedInfo
=
undefined
;
this
.
hooks
.
evaluate
.
for
(
exprType
).
tap
(
"JavascriptParser"
,
expr
=>
{
const
expression
=
/** @type {MemberExpressionNode} */
(
expr
);
const
info
=
getInfo
(
expr
);
if
(
info
!==
undefined
)
{
return
this
.
callHooksForInfoWithFallback
(
this
.
hooks
.
evaluateIdentifier
,
info
.
name
,
name
=>
{
cachedExpression
=
expression
;
cachedInfo
=
info
;
},
name
=>
{
const
hook
=
this
.
hooks
.
evaluateDefinedIdentifier
.
get
(
name
);
if
(
hook
!==
undefined
)
{
return
hook
.
call
(
expression
);
}
},
expression
);
}
});
this
.
hooks
.
evaluate
.
for
(
exprType
)
.
tap
({
name
:
"JavascriptParser"
,
stage
:
100
},
expr
=>
{
const
info
=
cachedExpression
===
expr
?
cachedInfo
:
getInfo
(
expr
);
if
(
info
!==
undefined
)
{
return
new
BasicEvaluatedExpression
()
.
setIdentifier
(
info
.
name
,
info
.
rootInfo
,
info
.
getMembers
)
.
setRange
(
expr
.
range
);
}
});
};
tapEvaluateWithVariableInfo
(
"Identifier"
,
expr
=>
{
const
info
=
this
.
getVariableInfo
(
/** @type {IdentifierNode} */
(
expr
).
name
);
if
(
typeof
info
===
"string"
||
(
info
instanceof
VariableInfo
&&
typeof
info
.
freeName
===
"string"
)
)
{
return
{
name
:
info
,
rootInfo
:
info
,
getMembers
:
()
=>
[]
};
}
});
tapEvaluateWithVariableInfo
(
"ThisExpression"
,
expr
=>
{
const
info
=
this
.
getVariableInfo
(
"this"
);
if
(
typeof
info
===
"string"
||
(
info
instanceof
VariableInfo
&&
typeof
info
.
freeName
===
"string"
)
)
{
return
{
name
:
info
,
rootInfo
:
info
,
getMembers
:
()
=>
[]
};
}
});
this
.
hooks
.
evaluate
.
for
(
"MetaProperty"
).
tap
(
"JavascriptParser"
,
expr
=>
{
const
metaProperty
=
/** @type {MetaPropertyNode} */
(
expr
);
return
this
.
callHooksForName
(
this
.
hooks
.
evaluateIdentifier
,
getRootName
(
expr
),
metaProperty
);
});
tapEvaluateWithVariableInfo
(
"MemberExpression"
,
expr
=>
this
.
getMemberExpressionInfo
(
/** @type {MemberExpressionNode} */
(
expr
),
ALLOWED_MEMBER_TYPES_EXPRESSION
)
);
this
.
hooks
.
evaluate
.
for
(
"CallExpression"
).
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {CallExpressionNode} */
(
_expr
);
if
(
expr
.
callee
.
type
!==
"MemberExpression"
||
expr
.
callee
.
property
.
type
!==
(
expr
.
callee
.
computed
?
"Literal"
:
"Identifier"
)
)
{
return
;
}
// type Super also possible here
const
param
=
this
.
evaluateExpression
(
/** @type {ExpressionNode} */
(
expr
.
callee
.
object
)
);
if
(
!
param
)
return
;
const
property
=
expr
.
callee
.
property
.
type
===
"Literal"
?
`
$
{
expr
.
callee
.
property
.
value
}
`
:
expr
.
callee
.
property
.
name
;
const
hook
=
this
.
hooks
.
evaluateCallExpressionMember
.
get
(
property
);
if
(
hook
!==
undefined
)
{
return
hook
.
call
(
expr
,
param
);
}
});
this
.
hooks
.
evaluateCallExpressionMember
.
for
(
"indexOf"
)
.
tap
(
"JavascriptParser"
,
(
expr
,
param
)
=>
{
if
(
!
param
.
isString
())
return
;
if
(
expr
.
arguments
.
length
===
0
)
return
;
const
[
arg1
,
arg2
]
=
expr
.
arguments
;
if
(
arg1
.
type
===
"SpreadElement"
)
return
;
const
arg1Eval
=
this
.
evaluateExpression
(
arg1
);
if
(
!
arg1Eval
.
isString
())
return
;
const
arg1Value
=
arg1Eval
.
string
;
let
result
;
if
(
arg2
)
{
if
(
arg2
.
type
===
"SpreadElement"
)
return
;
const
arg2Eval
=
this
.
evaluateExpression
(
arg2
);
if
(
!
arg2Eval
.
isNumber
())
return
;
result
=
param
.
string
.
indexOf
(
arg1Value
,
arg2Eval
.
number
);
}
else
{
result
=
param
.
string
.
indexOf
(
arg1Value
);
}
return
new
BasicEvaluatedExpression
()
.
setNumber
(
result
)
.
setSideEffects
(
param
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
});
this
.
hooks
.
evaluateCallExpressionMember
.
for
(
"replace"
)
.
tap
(
"JavascriptParser"
,
(
expr
,
param
)
=>
{
if
(
!
param
.
isString
())
return
;
if
(
expr
.
arguments
.
length
!==
2
)
return
;
if
(
expr
.
arguments
[
0
].
type
===
"SpreadElement"
)
return
;
if
(
expr
.
arguments
[
1
].
type
===
"SpreadElement"
)
return
;
let
arg1
=
this
.
evaluateExpression
(
expr
.
arguments
[
0
]);
let
arg2
=
this
.
evaluateExpression
(
expr
.
arguments
[
1
]);
if
(
!
arg1
.
isString
()
&&
!
arg1
.
isRegExp
())
return
;
const
arg1Value
=
arg1
.
regExp
||
arg1
.
string
;
if
(
!
arg2
.
isString
())
return
;
const
arg2Value
=
arg2
.
string
;
return
new
BasicEvaluatedExpression
()
.
setString
(
param
.
string
.
replace
(
arg1Value
,
arg2Value
))
.
setSideEffects
(
param
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
});
[
"substr"
,
"substring"
,
"slice"
].
forEach
(
fn
=>
{
this
.
hooks
.
evaluateCallExpressionMember
.
for
(
fn
)
.
tap
(
"JavascriptParser"
,
(
expr
,
param
)
=>
{
if
(
!
param
.
isString
())
return
;
let
arg1
;
let
result
,
str
=
param
.
string
;
switch
(
expr
.
arguments
.
length
)
{
case
1
:
if
(
expr
.
arguments
[
0
].
type
===
"SpreadElement"
)
return
;
arg1
=
this
.
evaluateExpression
(
expr
.
arguments
[
0
]);
if
(
!
arg1
.
isNumber
())
return
;
result
=
str
[
fn
](
arg1
.
number
);
break
;
case
2
:
{
if
(
expr
.
arguments
[
0
].
type
===
"SpreadElement"
)
return
;
if
(
expr
.
arguments
[
1
].
type
===
"SpreadElement"
)
return
;
arg1
=
this
.
evaluateExpression
(
expr
.
arguments
[
0
]);
const
arg2
=
this
.
evaluateExpression
(
expr
.
arguments
[
1
]);
if
(
!
arg1
.
isNumber
())
return
;
if
(
!
arg2
.
isNumber
())
return
;
result
=
str
[
fn
](
arg1
.
number
,
arg2
.
number
);
break
;
}
default
:
return
;
}
return
new
BasicEvaluatedExpression
()
.
setString
(
result
)
.
setSideEffects
(
param
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
});
});
/**
* @param {"cooked" | "raw"} kind kind of values to get
* @param {TemplateLiteralNode} templateLiteralExpr TemplateLiteral expr
* @returns {{quasis: BasicEvaluatedExpression[], parts: BasicEvaluatedExpression[]}} Simplified template
*/
const
getSimplifiedTemplateResult
=
(
kind
,
templateLiteralExpr
)
=>
{
/** @type {BasicEvaluatedExpression[]} */
const
quasis
=
[];
/** @type {BasicEvaluatedExpression[]} */
const
parts
=
[];
for
(
let
i
=
0
;
i
<
templateLiteralExpr
.
quasis
.
length
;
i
++
)
{
const
quasiExpr
=
templateLiteralExpr
.
quasis
[
i
];
const
quasi
=
quasiExpr
.
value
[
kind
];
if
(
i
>
0
)
{
const
prevExpr
=
parts
[
parts
.
length
-
1
];
const
expr
=
this
.
evaluateExpression
(
templateLiteralExpr
.
expressions
[
i
-
1
]
);
const
exprAsString
=
expr
.
asString
();
if
(
typeof
exprAsString
===
"string"
&&
!
expr
.
couldHaveSideEffects
()
)
{
// We can merge quasi + expr + quasi when expr
// is a const string
prevExpr
.
setString
(
prevExpr
.
string
+
exprAsString
+
quasi
);
prevExpr
.
setRange
([
prevExpr
.
range
[
0
],
quasiExpr
.
range
[
1
]]);
// We unset the expression as it doesn't match to a single expression
prevExpr
.
setExpression
(
undefined
);
continue
;
}
parts
.
push
(
expr
);
}
const
part
=
new
BasicEvaluatedExpression
()
.
setString
(
quasi
)
.
setRange
(
quasiExpr
.
range
)
.
setExpression
(
quasiExpr
);
quasis
.
push
(
part
);
parts
.
push
(
part
);
}
return
{
quasis
,
parts
};
};
this
.
hooks
.
evaluate
.
for
(
"TemplateLiteral"
)
.
tap
(
"JavascriptParser"
,
_node
=>
{
const
node
=
/** @type {TemplateLiteralNode} */
(
_node
);
const
{
quasis
,
parts
}
=
getSimplifiedTemplateResult
(
"cooked"
,
node
);
if
(
parts
.
length
===
1
)
{
return
parts
[
0
].
setRange
(
node
.
range
);
}
return
new
BasicEvaluatedExpression
()
.
setTemplateString
(
quasis
,
parts
,
"cooked"
)
.
setRange
(
node
.
range
);
});
this
.
hooks
.
evaluate
.
for
(
"TaggedTemplateExpression"
)
.
tap
(
"JavascriptParser"
,
_node
=>
{
const
node
=
/** @type {TaggedTemplateExpressionNode} */
(
_node
);
const
tag
=
this
.
evaluateExpression
(
node
.
tag
);
if
(
tag
.
isIdentifier
()
&&
tag
.
identifier
!==
"String.raw"
)
return
;
const
{
quasis
,
parts
}
=
getSimplifiedTemplateResult
(
"raw"
,
node
.
quasi
);
return
new
BasicEvaluatedExpression
()
.
setTemplateString
(
quasis
,
parts
,
"raw"
)
.
setRange
(
node
.
range
);
});
this
.
hooks
.
evaluateCallExpressionMember
.
for
(
"concat"
)
.
tap
(
"JavascriptParser"
,
(
expr
,
param
)
=>
{
if
(
!
param
.
isString
()
&&
!
param
.
isWrapped
())
return
;
let
stringSuffix
=
null
;
let
hasUnknownParams
=
false
;
const
innerExpressions
=
[];
for
(
let
i
=
expr
.
arguments
.
length
-
1
;
i
>=
0
;
i
--
)
{
const
arg
=
expr
.
arguments
[
i
];
if
(
arg
.
type
===
"SpreadElement"
)
return
;
const
argExpr
=
this
.
evaluateExpression
(
arg
);
if
(
hasUnknownParams
||
(
!
argExpr
.
isString
()
&&
!
argExpr
.
isNumber
())
)
{
hasUnknownParams
=
true
;
innerExpressions
.
push
(
argExpr
);
continue
;
}
const
value
=
argExpr
.
isString
()
?
argExpr
.
string
:
""
+
argExpr
.
number
;
const
newString
=
value
+
(
stringSuffix
?
stringSuffix
.
string
:
""
);
const
newRange
=
[
argExpr
.
range
[
0
],
(
stringSuffix
||
argExpr
).
range
[
1
]
];
stringSuffix
=
new
BasicEvaluatedExpression
()
.
setString
(
newString
)
.
setSideEffects
(
(
stringSuffix
&&
stringSuffix
.
couldHaveSideEffects
())
||
argExpr
.
couldHaveSideEffects
()
)
.
setRange
(
newRange
);
}
if
(
hasUnknownParams
)
{
const
prefix
=
param
.
isString
()
?
param
:
param
.
prefix
;
const
inner
=
param
.
isWrapped
()
&&
param
.
wrappedInnerExpressions
?
param
.
wrappedInnerExpressions
.
concat
(
innerExpressions
.
reverse
())
:
innerExpressions
.
reverse
();
return
new
BasicEvaluatedExpression
()
.
setWrapped
(
prefix
,
stringSuffix
,
inner
)
.
setRange
(
expr
.
range
);
}
else
if
(
param
.
isWrapped
())
{
const
postfix
=
stringSuffix
||
param
.
postfix
;
const
inner
=
param
.
wrappedInnerExpressions
?
param
.
wrappedInnerExpressions
.
concat
(
innerExpressions
.
reverse
())
:
innerExpressions
.
reverse
();
return
new
BasicEvaluatedExpression
()
.
setWrapped
(
param
.
prefix
,
postfix
,
inner
)
.
setRange
(
expr
.
range
);
}
else
{
const
newString
=
param
.
string
+
(
stringSuffix
?
stringSuffix
.
string
:
""
);
return
new
BasicEvaluatedExpression
()
.
setString
(
newString
)
.
setSideEffects
(
(
stringSuffix
&&
stringSuffix
.
couldHaveSideEffects
())
||
param
.
couldHaveSideEffects
()
)
.
setRange
(
expr
.
range
);
}
});
this
.
hooks
.
evaluateCallExpressionMember
.
for
(
"split"
)
.
tap
(
"JavascriptParser"
,
(
expr
,
param
)
=>
{
if
(
!
param
.
isString
())
return
;
if
(
expr
.
arguments
.
length
!==
1
)
return
;
if
(
expr
.
arguments
[
0
].
type
===
"SpreadElement"
)
return
;
let
result
;
const
arg
=
this
.
evaluateExpression
(
expr
.
arguments
[
0
]);
if
(
arg
.
isString
())
{
result
=
param
.
string
.
split
(
arg
.
string
);
}
else
if
(
arg
.
isRegExp
())
{
result
=
param
.
string
.
split
(
arg
.
regExp
);
}
else
{
return
;
}
return
new
BasicEvaluatedExpression
()
.
setArray
(
result
)
.
setSideEffects
(
param
.
couldHaveSideEffects
())
.
setRange
(
expr
.
range
);
});
this
.
hooks
.
evaluate
.
for
(
"ConditionalExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {ConditionalExpressionNode} */
(
_expr
);
const
condition
=
this
.
evaluateExpression
(
expr
.
test
);
const
conditionValue
=
condition
.
asBool
();
let
res
;
if
(
conditionValue
===
undefined
)
{
const
consequent
=
this
.
evaluateExpression
(
expr
.
consequent
);
const
alternate
=
this
.
evaluateExpression
(
expr
.
alternate
);
if
(
!
consequent
||
!
alternate
)
return
;
res
=
new
BasicEvaluatedExpression
();
if
(
consequent
.
isConditional
())
{
res
.
setOptions
(
consequent
.
options
);
}
else
{
res
.
setOptions
([
consequent
]);
}
if
(
alternate
.
isConditional
())
{
res
.
addOptions
(
alternate
.
options
);
}
else
{
res
.
addOptions
([
alternate
]);
}
}
else
{
res
=
this
.
evaluateExpression
(
conditionValue
?
expr
.
consequent
:
expr
.
alternate
);
if
(
condition
.
couldHaveSideEffects
())
res
.
setSideEffects
();
}
res
.
setRange
(
expr
.
range
);
return
res
;
});
this
.
hooks
.
evaluate
.
for
(
"ArrayExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {ArrayExpressionNode} */
(
_expr
);
const
items
=
expr
.
elements
.
map
(
element
=>
{
return
(
element
!==
null
&&
element
.
type
!==
"SpreadElement"
&&
this
.
evaluateExpression
(
element
)
);
});
if
(
!
items
.
every
(
Boolean
))
return
;
return
new
BasicEvaluatedExpression
()
.
setItems
(
items
)
.
setRange
(
expr
.
range
);
});
this
.
hooks
.
evaluate
.
for
(
"ChainExpression"
)
.
tap
(
"JavascriptParser"
,
_expr
=>
{
const
expr
=
/** @type {ChainExpressionNode} */
(
_expr
);
/** @type {ExpressionNode[]} */
const
optionalExpressionsStack
=
[];
/** @type {ExpressionNode|SuperNode} */
let
next
=
expr
.
expression
;
while
(
next
.
type
===
"MemberExpression"
||
next
.
type
===
"CallExpression"
)
{
if
(
next
.
type
===
"MemberExpression"
)
{
if
(
next
.
optional
)
{
// SuperNode can not be optional
optionalExpressionsStack
.
push
(
/** @type {ExpressionNode} */
(
next
.
object
)
);
}
next
=
next
.
object
;
}
else
{
if
(
next
.
optional
)
{
// SuperNode can not be optional
optionalExpressionsStack
.
push
(
/** @type {ExpressionNode} */
(
next
.
callee
)
);
}
next
=
next
.
callee
;
}
}
while
(
optionalExpressionsStack
.
length
>
0
)
{
const
expression
=
optionalExpressionsStack
.
pop
();
const
evaluated
=
this
.
evaluateExpression
(
expression
);
if
(
evaluated
&&
evaluated
.
asNullish
())
{
return
evaluated
.
setRange
(
_expr
.
range
);
}
}
return
this
.
evaluateExpression
(
expr
.
expression
);
});
}
getRenameIdentifier
(
expr
)
{
const
result
=
this
.
evaluateExpression
(
expr
);
if
(
result
&&
result
.
isIdentifier
())
{
return
result
.
identifier
;
}
}
/**
* @param {ClassExpressionNode | ClassDeclarationNode} classy a class node
* @returns {void}
*/
walkClass
(
classy
)
{
if
(
classy
.
superClass
)
{
if
(
!
this
.
hooks
.
classExtendsExpression
.
call
(
classy
.
superClass
,
classy
))
{
this
.
walkExpression
(
classy
.
superClass
);
}
}
if
(
classy
.
body
&&
classy
.
body
.
type
===
"ClassBody"
)
{
const
wasTopLevel
=
this
.
scope
.
topLevelScope
;
for
(
const
classElement
of
classy
.
body
.
body
)
{
if
(
!
this
.
hooks
.
classBodyElement
.
call
(
classElement
,
classy
))
{
if
(
classElement
.
type
===
"MethodDefinition"
)
{
this
.
scope
.
topLevelScope
=
false
;
this
.
walkMethodDefinition
(
classElement
);
this
.
scope
.
topLevelScope
=
wasTopLevel
;
}
// TODO add support for ClassProperty here once acorn supports it
}
}
}
}
walkMethodDefinition
(
methodDefinition
)
{
if
(
methodDefinition
.
computed
&&
methodDefinition
.
key
)
{
this
.
walkExpression
(
methodDefinition
.
key
);
}
if
(
methodDefinition
.
value
)
{
this
.
walkExpression
(
methodDefinition
.
value
);
}
}
// Pre walking iterates the scope for variable declarations
preWalkStatements
(
statements
)
{
for
(
let
index
=
0
,
len
=
statements
.
length
;
index
<
len
;
index
++
)
{
const
statement
=
statements
[
index
];
this
.
preWalkStatement
(
statement
);
}
}
// Block pre walking iterates the scope for block variable declarations
blockPreWalkStatements
(
statements
)
{
for
(
let
index
=
0
,
len
=
statements
.
length
;
index
<
len
;
index
++
)
{
const
statement
=
statements
[
index
];
this
.
blockPreWalkStatement
(
statement
);
}
}
// Walking iterates the statements and expressions and processes them
walkStatements
(
statements
)
{
for
(
let
index
=
0
,
len
=
statements
.
length
;
index
<
len
;
index
++
)
{
const
statement
=
statements
[
index
];
this
.
walkStatement
(
statement
);
}
}
preWalkStatement
(
statement
)
{
this
.
statementPath
.
push
(
statement
);
if
(
this
.
hooks
.
preStatement
.
call
(
statement
))
{
this
.
prevStatement
=
this
.
statementPath
.
pop
();
return
;
}
switch
(
statement
.
type
)
{
case
"BlockStatement"
:
this
.
preWalkBlockStatement
(
statement
);
break
;
case
"DoWhileStatement"
:
this
.
preWalkDoWhileStatement
(
statement
);
break
;
case
"ForInStatement"
:
this
.
preWalkForInStatement
(
statement
);
break
;
case
"ForOfStatement"
:
this
.
preWalkForOfStatement
(
statement
);
break
;
case
"ForStatement"
:
this
.
preWalkForStatement
(
statement
);
break
;
case
"FunctionDeclaration"
:
this
.
preWalkFunctionDeclaration
(
statement
);
break
;
case
"IfStatement"
:
this
.
preWalkIfStatement
(
statement
);
break
;
case
"LabeledStatement"
:
this
.
preWalkLabeledStatement
(
statement
);
break
;
case
"SwitchStatement"
:
this
.
preWalkSwitchStatement
(
statement
);
break
;
case
"TryStatement"
:
this
.
preWalkTryStatement
(
statement
);
break
;
case
"VariableDeclaration"
:
this
.
preWalkVariableDeclaration
(
statement
);
break
;
case
"WhileStatement"
:
this
.
preWalkWhileStatement
(
statement
);
break
;
case
"WithStatement"
:
this
.
preWalkWithStatement
(
statement
);
break
;
}
this
.
prevStatement
=
this
.
statementPath
.
pop
();
}
blockPreWalkStatement
(
statement
)
{
this
.
statementPath
.
push
(
statement
);
if
(
this
.
hooks
.
blockPreStatement
.
call
(
statement
))
{
this
.
prevStatement
=
this
.
statementPath
.
pop
();
return
;
}
switch
(
statement
.
type
)
{
case
"ImportDeclaration"
:
this
.
blockPreWalkImportDeclaration
(
statement
);
break
;
case
"ExportAllDeclaration"
:
this
.
blockPreWalkExportAllDeclaration
(
statement
);
break
;
case
"ExportDefaultDeclaration"
:
this
.
blockPreWalkExportDefaultDeclaration
(
statement
);
break
;
case
"ExportNamedDeclaration"
:
this
.
blockPreWalkExportNamedDeclaration
(
statement
);
break
;
case
"VariableDeclaration"
:
this
.
blockPreWalkVariableDeclaration
(
statement
);
break
;
case
"ClassDeclaration"
:
this
.
blockPreWalkClassDeclaration
(
statement
);
break
;
}
this
.
prevStatement
=
this
.
statementPath
.
pop
();
}
walkStatement
(
statement
)
{
this
.
statementPath
.
push
(
statement
);
if
(
this
.
hooks
.
statement
.
call
(
statement
)
!==
undefined
)
{
this
.
prevStatement
=
this
.
statementPath
.
pop
();
return
;
}
switch
(
statement
.
type
)
{
case
"BlockStatement"
:
this
.
walkBlockStatement
(
statement
);
break
;
case
"ClassDeclaration"
:
this
.
walkClassDeclaration
(
statement
);
break
;
case
"DoWhileStatement"
:
this
.
walkDoWhileStatement
(
statement
);
break
;
case
"ExportDefaultDeclaration"
:
this
.
walkExportDefaultDeclaration
(
statement
);
break
;
case
"ExportNamedDeclaration"
:
this
.
walkExportNamedDeclaration
(
statement
);
break
;
case
"ExpressionStatement"
:
this
.
walkExpressionStatement
(
statement
);
break
;
case
"ForInStatement"
:
this
.
walkForInStatement
(
statement
);
break
;
case
"ForOfStatement"
:
this
.
walkForOfStatement
(
statement
);
break
;
case
"ForStatement"
:
this
.
walkForStatement
(
statement
);
break
;
case
"FunctionDeclaration"
:
this
.
walkFunctionDeclaration
(
statement
);
break
;
case
"IfStatement"
:
this
.
walkIfStatement
(
statement
);
break
;
case
"LabeledStatement"
:
this
.
walkLabeledStatement
(
statement
);
break
;
case
"ReturnStatement"
:
this
.
walkReturnStatement
(
statement
);
break
;
case
"SwitchStatement"
:
this
.
walkSwitchStatement
(
statement
);
break
;
case
"ThrowStatement"
:
this
.
walkThrowStatement
(
statement
);
break
;
case
"TryStatement"
:
this
.
walkTryStatement
(
statement
);
break
;
case
"VariableDeclaration"
:
this
.
walkVariableDeclaration
(
statement
);
break
;
case
"WhileStatement"
:
this
.
walkWhileStatement
(
statement
);
break
;
case
"WithStatement"
:
this
.
walkWithStatement
(
statement
);
break
;
}
this
.
prevStatement
=
this
.
statementPath
.
pop
();
}
/**
* Walks a statements that is nested within a parent statement
* and can potentially be a non-block statement.
* This enforces the nested statement to never be in ASI position.
* @param {StatementNode} statement the nested statement
* @returns {void}
*/
walkNestedStatement
(
statement
)
{
this
.
prevStatement
=
undefined
;
this
.
walkStatement
(
statement
);
}
// Real Statements
preWalkBlockStatement
(
statement
)
{
this
.
preWalkStatements
(
statement
.
body
);
}
walkBlockStatement
(
statement
)
{
this
.
inBlockScope
(()
=>
{
const
body
=
statement
.
body
;
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatements
(
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatements
(
body
);
});
}
walkExpressionStatement
(
statement
)
{
this
.
walkExpression
(
statement
.
expression
);
}
preWalkIfStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
consequent
);
if
(
statement
.
alternate
)
{
this
.
preWalkStatement
(
statement
.
alternate
);
}
}
walkIfStatement
(
statement
)
{
const
result
=
this
.
hooks
.
statementIf
.
call
(
statement
);
if
(
result
===
undefined
)
{
this
.
walkExpression
(
statement
.
test
);
this
.
walkNestedStatement
(
statement
.
consequent
);
if
(
statement
.
alternate
)
{
this
.
walkNestedStatement
(
statement
.
alternate
);
}
}
else
{
if
(
result
)
{
this
.
walkNestedStatement
(
statement
.
consequent
);
}
else
if
(
statement
.
alternate
)
{
this
.
walkNestedStatement
(
statement
.
alternate
);
}
}
}
preWalkLabeledStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
body
);
}
walkLabeledStatement
(
statement
)
{
const
hook
=
this
.
hooks
.
label
.
get
(
statement
.
label
.
name
);
if
(
hook
!==
undefined
)
{
const
result
=
hook
.
call
(
statement
);
if
(
result
===
true
)
return
;
}
this
.
walkNestedStatement
(
statement
.
body
);
}
preWalkWithStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
body
);
}
walkWithStatement
(
statement
)
{
this
.
walkExpression
(
statement
.
object
);
this
.
walkNestedStatement
(
statement
.
body
);
}
preWalkSwitchStatement
(
statement
)
{
this
.
preWalkSwitchCases
(
statement
.
cases
);
}
walkSwitchStatement
(
statement
)
{
this
.
walkExpression
(
statement
.
discriminant
);
this
.
walkSwitchCases
(
statement
.
cases
);
}
walkTerminatingStatement
(
statement
)
{
if
(
statement
.
argument
)
this
.
walkExpression
(
statement
.
argument
);
}
walkReturnStatement
(
statement
)
{
this
.
walkTerminatingStatement
(
statement
);
}
walkThrowStatement
(
statement
)
{
this
.
walkTerminatingStatement
(
statement
);
}
preWalkTryStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
block
);
if
(
statement
.
handler
)
this
.
preWalkCatchClause
(
statement
.
handler
);
if
(
statement
.
finializer
)
this
.
preWalkStatement
(
statement
.
finializer
);
}
walkTryStatement
(
statement
)
{
if
(
this
.
scope
.
inTry
)
{
this
.
walkStatement
(
statement
.
block
);
}
else
{
this
.
scope
.
inTry
=
true
;
this
.
walkStatement
(
statement
.
block
);
this
.
scope
.
inTry
=
false
;
}
if
(
statement
.
handler
)
this
.
walkCatchClause
(
statement
.
handler
);
if
(
statement
.
finalizer
)
this
.
walkStatement
(
statement
.
finalizer
);
}
preWalkWhileStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
body
);
}
walkWhileStatement
(
statement
)
{
this
.
walkExpression
(
statement
.
test
);
this
.
walkNestedStatement
(
statement
.
body
);
}
preWalkDoWhileStatement
(
statement
)
{
this
.
preWalkStatement
(
statement
.
body
);
}
walkDoWhileStatement
(
statement
)
{
this
.
walkNestedStatement
(
statement
.
body
);
this
.
walkExpression
(
statement
.
test
);
}
preWalkForStatement
(
statement
)
{
if
(
statement
.
init
)
{
if
(
statement
.
init
.
type
===
"VariableDeclaration"
)
{
this
.
preWalkStatement
(
statement
.
init
);
}
}
this
.
preWalkStatement
(
statement
.
body
);
}
walkForStatement
(
statement
)
{
this
.
inBlockScope
(()
=>
{
if
(
statement
.
init
)
{
if
(
statement
.
init
.
type
===
"VariableDeclaration"
)
{
this
.
blockPreWalkVariableDeclaration
(
statement
.
init
);
this
.
prevStatement
=
undefined
;
this
.
walkStatement
(
statement
.
init
);
}
else
{
this
.
walkExpression
(
statement
.
init
);
}
}
if
(
statement
.
test
)
{
this
.
walkExpression
(
statement
.
test
);
}
if
(
statement
.
update
)
{
this
.
walkExpression
(
statement
.
update
);
}
const
body
=
statement
.
body
;
if
(
body
.
type
===
"BlockStatement"
)
{
// no need to add additional scope
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatements
(
body
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatements
(
body
.
body
);
}
else
{
this
.
walkNestedStatement
(
body
);
}
});
}
preWalkForInStatement
(
statement
)
{
if
(
statement
.
left
.
type
===
"VariableDeclaration"
)
{
this
.
preWalkVariableDeclaration
(
statement
.
left
);
}
this
.
preWalkStatement
(
statement
.
body
);
}
walkForInStatement
(
statement
)
{
this
.
inBlockScope
(()
=>
{
if
(
statement
.
left
.
type
===
"VariableDeclaration"
)
{
this
.
blockPreWalkVariableDeclaration
(
statement
.
left
);
this
.
walkVariableDeclaration
(
statement
.
left
);
}
else
{
this
.
walkPattern
(
statement
.
left
);
}
this
.
walkExpression
(
statement
.
right
);
const
body
=
statement
.
body
;
if
(
body
.
type
===
"BlockStatement"
)
{
// no need to add additional scope
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatements
(
body
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatements
(
body
.
body
);
}
else
{
this
.
walkNestedStatement
(
body
);
}
});
}
preWalkForOfStatement
(
statement
)
{
if
(
statement
.
await
&&
this
.
scope
.
topLevelScope
===
true
)
{
this
.
hooks
.
topLevelAwait
.
call
(
statement
);
}
if
(
statement
.
left
.
type
===
"VariableDeclaration"
)
{
this
.
preWalkVariableDeclaration
(
statement
.
left
);
}
this
.
preWalkStatement
(
statement
.
body
);
}
walkForOfStatement
(
statement
)
{
this
.
inBlockScope
(()
=>
{
if
(
statement
.
left
.
type
===
"VariableDeclaration"
)
{
this
.
blockPreWalkVariableDeclaration
(
statement
.
left
);
this
.
walkVariableDeclaration
(
statement
.
left
);
}
else
{
this
.
walkPattern
(
statement
.
left
);
}
this
.
walkExpression
(
statement
.
right
);
const
body
=
statement
.
body
;
if
(
body
.
type
===
"BlockStatement"
)
{
// no need to add additional scope
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatements
(
body
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatements
(
body
.
body
);
}
else
{
this
.
walkNestedStatement
(
body
);
}
});
}
// Declarations
preWalkFunctionDeclaration
(
statement
)
{
if
(
statement
.
id
)
{
this
.
defineVariable
(
statement
.
id
.
name
);
}
}
walkFunctionDeclaration
(
statement
)
{
const
wasTopLevel
=
this
.
scope
.
topLevelScope
;
this
.
scope
.
topLevelScope
=
false
;
this
.
inFunctionScope
(
true
,
statement
.
params
,
()
=>
{
for
(
const
param
of
statement
.
params
)
{
this
.
walkPattern
(
param
);
}
if
(
statement
.
body
.
type
===
"BlockStatement"
)
{
this
.
detectMode
(
statement
.
body
.
body
);
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
statement
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatement
(
statement
.
body
);
}
else
{
this
.
walkExpression
(
statement
.
body
);
}
});
this
.
scope
.
topLevelScope
=
wasTopLevel
;
}
blockPreWalkImportDeclaration
(
statement
)
{
const
source
=
statement
.
source
.
value
;
this
.
hooks
.
import
.
call
(
statement
,
source
);
for
(
const
specifier
of
statement
.
specifiers
)
{
const
name
=
specifier
.
local
.
name
;
switch
(
specifier
.
type
)
{
case
"ImportDefaultSpecifier"
:
if
(
!
this
.
hooks
.
importSpecifier
.
call
(
statement
,
source
,
"default"
,
name
)
)
{
this
.
defineVariable
(
name
);
}
break
;
case
"ImportSpecifier"
:
if
(
!
this
.
hooks
.
importSpecifier
.
call
(
statement
,
source
,
specifier
.
imported
.
name
,
name
)
)
{
this
.
defineVariable
(
name
);
}
break
;
case
"ImportNamespaceSpecifier"
:
if
(
!
this
.
hooks
.
importSpecifier
.
call
(
statement
,
source
,
null
,
name
))
{
this
.
defineVariable
(
name
);
}
break
;
default
:
this
.
defineVariable
(
name
);
}
}
}
enterDeclaration
(
declaration
,
onIdent
)
{
switch
(
declaration
.
type
)
{
case
"VariableDeclaration"
:
for
(
const
declarator
of
declaration
.
declarations
)
{
switch
(
declarator
.
type
)
{
case
"VariableDeclarator"
:
{
this
.
enterPattern
(
declarator
.
id
,
onIdent
);
break
;
}
}
}
break
;
case
"FunctionDeclaration"
:
this
.
enterPattern
(
declaration
.
id
,
onIdent
);
break
;
case
"ClassDeclaration"
:
this
.
enterPattern
(
declaration
.
id
,
onIdent
);
break
;
}
}
blockPreWalkExportNamedDeclaration
(
statement
)
{
let
source
;
if
(
statement
.
source
)
{
source
=
statement
.
source
.
value
;
this
.
hooks
.
exportImport
.
call
(
statement
,
source
);
}
else
{
this
.
hooks
.
export
.
call
(
statement
);
}
if
(
statement
.
declaration
)
{
if
(
!
this
.
hooks
.
exportDeclaration
.
call
(
statement
,
statement
.
declaration
)
)
{
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
statement
.
declaration
);
this
.
prevStatement
=
prev
;
this
.
blockPreWalkStatement
(
statement
.
declaration
);
let
index
=
0
;
this
.
enterDeclaration
(
statement
.
declaration
,
def
=>
{
this
.
hooks
.
exportSpecifier
.
call
(
statement
,
def
,
def
,
index
++
);
});
}
}
if
(
statement
.
specifiers
)
{
for
(
let
specifierIndex
=
0
;
specifierIndex
<
statement
.
specifiers
.
length
;
specifierIndex
++
)
{
const
specifier
=
statement
.
specifiers
[
specifierIndex
];
switch
(
specifier
.
type
)
{
case
"ExportSpecifier"
:
{
const
name
=
specifier
.
exported
.
name
;
if
(
source
)
{
this
.
hooks
.
exportImportSpecifier
.
call
(
statement
,
source
,
specifier
.
local
.
name
,
name
,
specifierIndex
);
}
else
{
this
.
hooks
.
exportSpecifier
.
call
(
statement
,
specifier
.
local
.
name
,
name
,
specifierIndex
);
}
break
;
}
}
}
}
}
walkExportNamedDeclaration
(
statement
)
{
if
(
statement
.
declaration
)
{
this
.
walkStatement
(
statement
.
declaration
);
}
}
blockPreWalkExportDefaultDeclaration
(
statement
)
{
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
statement
.
declaration
);
this
.
prevStatement
=
prev
;
this
.
blockPreWalkStatement
(
statement
.
declaration
);
if
(
statement
.
declaration
.
id
&&
statement
.
declaration
.
type
!==
"FunctionExpression"
&&
statement
.
declaration
.
type
!==
"ClassExpression"
)
{
this
.
hooks
.
exportSpecifier
.
call
(
statement
,
statement
.
declaration
.
id
.
name
,
"default"
,
undefined
);
}
}
walkExportDefaultDeclaration
(
statement
)
{
this
.
hooks
.
export
.
call
(
statement
);
if
(
statement
.
declaration
.
id
&&
statement
.
declaration
.
type
!==
"FunctionExpression"
&&
statement
.
declaration
.
type
!==
"ClassExpression"
)
{
if
(
!
this
.
hooks
.
exportDeclaration
.
call
(
statement
,
statement
.
declaration
)
)
{
this
.
walkStatement
(
statement
.
declaration
);
}
}
else
{
// Acorn parses `export default function() {}` as `FunctionDeclaration` and
// `export default class {}` as `ClassDeclaration`, both with `id = null`.
// These nodes must be treated as expressions.
if
(
statement
.
declaration
.
type
===
"FunctionDeclaration"
||
statement
.
declaration
.
type
===
"ClassDeclaration"
)
{
this
.
walkStatement
(
statement
.
declaration
);
}
else
{
this
.
walkExpression
(
statement
.
declaration
);
}
if
(
!
this
.
hooks
.
exportExpression
.
call
(
statement
,
statement
.
declaration
))
{
this
.
hooks
.
exportSpecifier
.
call
(
statement
,
statement
.
declaration
,
"default"
,
undefined
);
}
}
}
blockPreWalkExportAllDeclaration
(
statement
)
{
const
source
=
statement
.
source
.
value
;
const
name
=
statement
.
exported
?
statement
.
exported
.
name
:
null
;
this
.
hooks
.
exportImport
.
call
(
statement
,
source
);
this
.
hooks
.
exportImportSpecifier
.
call
(
statement
,
source
,
null
,
name
,
0
);
}
preWalkVariableDeclaration
(
statement
)
{
if
(
statement
.
kind
!==
"var"
)
return
;
this
.
_preWalkVariableDeclaration
(
statement
,
this
.
hooks
.
varDeclarationVar
);
}
blockPreWalkVariableDeclaration
(
statement
)
{
if
(
statement
.
kind
===
"var"
)
return
;
const
hookMap
=
statement
.
kind
===
"const"
?
this
.
hooks
.
varDeclarationConst
:
this
.
hooks
.
varDeclarationLet
;
this
.
_preWalkVariableDeclaration
(
statement
,
hookMap
);
}
_preWalkVariableDeclaration
(
statement
,
hookMap
)
{
for
(
const
declarator
of
statement
.
declarations
)
{
switch
(
declarator
.
type
)
{
case
"VariableDeclarator"
:
{
if
(
!
this
.
hooks
.
preDeclarator
.
call
(
declarator
,
statement
))
{
this
.
enterPattern
(
declarator
.
id
,
(
name
,
decl
)
=>
{
let
hook
=
hookMap
.
get
(
name
);
if
(
hook
===
undefined
||
!
hook
.
call
(
decl
))
{
hook
=
this
.
hooks
.
varDeclaration
.
get
(
name
);
if
(
hook
===
undefined
||
!
hook
.
call
(
decl
))
{
this
.
defineVariable
(
name
);
}
}
});
}
break
;
}
}
}
}
walkVariableDeclaration
(
statement
)
{
for
(
const
declarator
of
statement
.
declarations
)
{
switch
(
declarator
.
type
)
{
case
"VariableDeclarator"
:
{
const
renameIdentifier
=
declarator
.
init
&&
this
.
getRenameIdentifier
(
declarator
.
init
);
if
(
renameIdentifier
&&
declarator
.
id
.
type
===
"Identifier"
)
{
const
hook
=
this
.
hooks
.
canRename
.
get
(
renameIdentifier
);
if
(
hook
!==
undefined
&&
hook
.
call
(
declarator
.
init
))
{
// renaming with "var a = b;"
const
hook
=
this
.
hooks
.
rename
.
get
(
renameIdentifier
);
if
(
hook
===
undefined
||
!
hook
.
call
(
declarator
.
init
))
{
this
.
setVariable
(
declarator
.
id
.
name
,
renameIdentifier
);
}
break
;
}
}
if
(
!
this
.
hooks
.
declarator
.
call
(
declarator
,
statement
))
{
this
.
walkPattern
(
declarator
.
id
);
if
(
declarator
.
init
)
this
.
walkExpression
(
declarator
.
init
);
}
break
;
}
}
}
}
blockPreWalkClassDeclaration
(
statement
)
{
if
(
statement
.
id
)
{
this
.
defineVariable
(
statement
.
id
.
name
);
}
}
walkClassDeclaration
(
statement
)
{
this
.
walkClass
(
statement
);
}
preWalkSwitchCases
(
switchCases
)
{
for
(
let
index
=
0
,
len
=
switchCases
.
length
;
index
<
len
;
index
++
)
{
const
switchCase
=
switchCases
[
index
];
this
.
preWalkStatements
(
switchCase
.
consequent
);
}
}
walkSwitchCases
(
switchCases
)
{
this
.
inBlockScope
(()
=>
{
const
len
=
switchCases
.
length
;
// we need to pre walk all statements first since we can have invalid code
// import A from "module";
// switch(1) {
// case 1:
// console.log(A); // should fail at runtime
// case 2:
// const A = 1;
// }
for
(
let
index
=
0
;
index
<
len
;
index
++
)
{
const
switchCase
=
switchCases
[
index
];
if
(
switchCase
.
consequent
.
length
>
0
)
{
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatements
(
switchCase
.
consequent
);
this
.
prevStatement
=
prev
;
}
}
for
(
let
index
=
0
;
index
<
len
;
index
++
)
{
const
switchCase
=
switchCases
[
index
];
if
(
switchCase
.
test
)
{
this
.
walkExpression
(
switchCase
.
test
);
}
if
(
switchCase
.
consequent
.
length
>
0
)
{
this
.
walkStatements
(
switchCase
.
consequent
);
}
}
});
}
preWalkCatchClause
(
catchClause
)
{
this
.
preWalkStatement
(
catchClause
.
body
);
}
walkCatchClause
(
catchClause
)
{
this
.
inBlockScope
(()
=>
{
// Error binding is optional in catch clause since ECMAScript 2019
if
(
catchClause
.
param
!==
null
)
{
this
.
enterPattern
(
catchClause
.
param
,
ident
=>
{
this
.
defineVariable
(
ident
);
});
this
.
walkPattern
(
catchClause
.
param
);
}
const
prev
=
this
.
prevStatement
;
this
.
blockPreWalkStatement
(
catchClause
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatement
(
catchClause
.
body
);
});
}
walkPattern
(
pattern
)
{
switch
(
pattern
.
type
)
{
case
"ArrayPattern"
:
this
.
walkArrayPattern
(
pattern
);
break
;
case
"AssignmentPattern"
:
this
.
walkAssignmentPattern
(
pattern
);
break
;
case
"MemberExpression"
:
this
.
walkMemberExpression
(
pattern
);
break
;
case
"ObjectPattern"
:
this
.
walkObjectPattern
(
pattern
);
break
;
case
"RestElement"
:
this
.
walkRestElement
(
pattern
);
break
;
}
}
walkAssignmentPattern
(
pattern
)
{
this
.
walkExpression
(
pattern
.
right
);
this
.
walkPattern
(
pattern
.
left
);
}
walkObjectPattern
(
pattern
)
{
for
(
let
i
=
0
,
len
=
pattern
.
properties
.
length
;
i
<
len
;
i
++
)
{
const
prop
=
pattern
.
properties
[
i
];
if
(
prop
)
{
if
(
prop
.
computed
)
this
.
walkExpression
(
prop
.
key
);
if
(
prop
.
value
)
this
.
walkPattern
(
prop
.
value
);
}
}
}
walkArrayPattern
(
pattern
)
{
for
(
let
i
=
0
,
len
=
pattern
.
elements
.
length
;
i
<
len
;
i
++
)
{
const
element
=
pattern
.
elements
[
i
];
if
(
element
)
this
.
walkPattern
(
element
);
}
}
walkRestElement
(
pattern
)
{
this
.
walkPattern
(
pattern
.
argument
);
}
walkExpressions
(
expressions
)
{
for
(
const
expression
of
expressions
)
{
if
(
expression
)
{
this
.
walkExpression
(
expression
);
}
}
}
walkExpression
(
expression
)
{
switch
(
expression
.
type
)
{
case
"ArrayExpression"
:
this
.
walkArrayExpression
(
expression
);
break
;
case
"ArrowFunctionExpression"
:
this
.
walkArrowFunctionExpression
(
expression
);
break
;
case
"AssignmentExpression"
:
this
.
walkAssignmentExpression
(
expression
);
break
;
case
"AwaitExpression"
:
this
.
walkAwaitExpression
(
expression
);
break
;
case
"BinaryExpression"
:
this
.
walkBinaryExpression
(
expression
);
break
;
case
"CallExpression"
:
this
.
walkCallExpression
(
expression
);
break
;
case
"ChainExpression"
:
this
.
walkChainExpression
(
expression
);
break
;
case
"ClassExpression"
:
this
.
walkClassExpression
(
expression
);
break
;
case
"ConditionalExpression"
:
this
.
walkConditionalExpression
(
expression
);
break
;
case
"FunctionExpression"
:
this
.
walkFunctionExpression
(
expression
);
break
;
case
"Identifier"
:
this
.
walkIdentifier
(
expression
);
break
;
case
"ImportExpression"
:
this
.
walkImportExpression
(
expression
);
break
;
case
"LogicalExpression"
:
this
.
walkLogicalExpression
(
expression
);
break
;
case
"MetaProperty"
:
this
.
walkMetaProperty
(
expression
);
break
;
case
"MemberExpression"
:
this
.
walkMemberExpression
(
expression
);
break
;
case
"NewExpression"
:
this
.
walkNewExpression
(
expression
);
break
;
case
"ObjectExpression"
:
this
.
walkObjectExpression
(
expression
);
break
;
case
"SequenceExpression"
:
this
.
walkSequenceExpression
(
expression
);
break
;
case
"SpreadElement"
:
this
.
walkSpreadElement
(
expression
);
break
;
case
"TaggedTemplateExpression"
:
this
.
walkTaggedTemplateExpression
(
expression
);
break
;
case
"TemplateLiteral"
:
this
.
walkTemplateLiteral
(
expression
);
break
;
case
"ThisExpression"
:
this
.
walkThisExpression
(
expression
);
break
;
case
"UnaryExpression"
:
this
.
walkUnaryExpression
(
expression
);
break
;
case
"UpdateExpression"
:
this
.
walkUpdateExpression
(
expression
);
break
;
case
"YieldExpression"
:
this
.
walkYieldExpression
(
expression
);
break
;
}
}
walkAwaitExpression
(
expression
)
{
if
(
this
.
scope
.
topLevelScope
===
true
)
this
.
hooks
.
topLevelAwait
.
call
(
expression
);
this
.
walkExpression
(
expression
.
argument
);
}
walkArrayExpression
(
expression
)
{
if
(
expression
.
elements
)
{
this
.
walkExpressions
(
expression
.
elements
);
}
}
walkSpreadElement
(
expression
)
{
if
(
expression
.
argument
)
{
this
.
walkExpression
(
expression
.
argument
);
}
}
walkObjectExpression
(
expression
)
{
for
(
let
propIndex
=
0
,
len
=
expression
.
properties
.
length
;
propIndex
<
len
;
propIndex
++
)
{
const
prop
=
expression
.
properties
[
propIndex
];
if
(
prop
.
type
===
"SpreadElement"
)
{
this
.
walkExpression
(
prop
.
argument
);
continue
;
}
if
(
prop
.
computed
)
{
this
.
walkExpression
(
prop
.
key
);
}
if
(
prop
.
shorthand
&&
prop
.
value
&&
prop
.
value
.
type
===
"Identifier"
)
{
this
.
scope
.
inShorthand
=
prop
.
value
.
name
;
this
.
walkIdentifier
(
prop
.
value
);
this
.
scope
.
inShorthand
=
false
;
}
else
{
this
.
walkExpression
(
prop
.
value
);
}
}
}
walkFunctionExpression
(
expression
)
{
const
wasTopLevel
=
this
.
scope
.
topLevelScope
;
this
.
scope
.
topLevelScope
=
false
;
const
scopeParams
=
expression
.
params
;
// Add function name in scope for recursive calls
if
(
expression
.
id
)
{
scopeParams
.
push
(
expression
.
id
.
name
);
}
this
.
inFunctionScope
(
true
,
scopeParams
,
()
=>
{
for
(
const
param
of
expression
.
params
)
{
this
.
walkPattern
(
param
);
}
if
(
expression
.
body
.
type
===
"BlockStatement"
)
{
this
.
detectMode
(
expression
.
body
.
body
);
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
expression
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatement
(
expression
.
body
);
}
else
{
this
.
walkExpression
(
expression
.
body
);
}
});
this
.
scope
.
topLevelScope
=
wasTopLevel
;
}
walkArrowFunctionExpression
(
expression
)
{
const
wasTopLevel
=
this
.
scope
.
topLevelScope
;
this
.
scope
.
topLevelScope
=
wasTopLevel
?
"arrow"
:
false
;
this
.
inFunctionScope
(
false
,
expression
.
params
,
()
=>
{
for
(
const
param
of
expression
.
params
)
{
this
.
walkPattern
(
param
);
}
if
(
expression
.
body
.
type
===
"BlockStatement"
)
{
this
.
detectMode
(
expression
.
body
.
body
);
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
expression
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatement
(
expression
.
body
);
}
else
{
this
.
walkExpression
(
expression
.
body
);
}
});
this
.
scope
.
topLevelScope
=
wasTopLevel
;
}
/**
* @param {SequenceExpressionNode} expression the sequence
*/
walkSequenceExpression
(
expression
)
{
if
(
!
expression
.
expressions
)
return
;
// We treat sequence expressions like statements when they are one statement level
// This has some benefits for optimizations that only work on statement level
const
currentStatement
=
this
.
statementPath
[
this
.
statementPath
.
length
-
1
];
if
(
currentStatement
===
expression
||
(
currentStatement
.
type
===
"ExpressionStatement"
&&
currentStatement
.
expression
===
expression
)
)
{
const
old
=
this
.
statementPath
.
pop
();
for
(
const
expr
of
expression
.
expressions
)
{
this
.
statementPath
.
push
(
expr
);
this
.
walkExpression
(
expr
);
this
.
statementPath
.
pop
();
}
this
.
statementPath
.
push
(
old
);
}
else
{
this
.
walkExpressions
(
expression
.
expressions
);
}
}
walkUpdateExpression
(
expression
)
{
this
.
walkExpression
(
expression
.
argument
);
}
walkUnaryExpression
(
expression
)
{
if
(
expression
.
operator
===
"typeof"
)
{
const
result
=
this
.
callHooksForExpression
(
this
.
hooks
.
typeof
,
expression
.
argument
,
expression
);
if
(
result
===
true
)
return
;
if
(
expression
.
argument
.
type
===
"ChainExpression"
)
{
const
result
=
this
.
callHooksForExpression
(
this
.
hooks
.
typeof
,
expression
.
argument
.
expression
,
expression
);
if
(
result
===
true
)
return
;
}
}
this
.
walkExpression
(
expression
.
argument
);
}
walkLeftRightExpression
(
expression
)
{
this
.
walkExpression
(
expression
.
left
);
this
.
walkExpression
(
expression
.
right
);
}
walkBinaryExpression
(
expression
)
{
this
.
walkLeftRightExpression
(
expression
);
}
walkLogicalExpression
(
expression
)
{
const
result
=
this
.
hooks
.
expressionLogicalOperator
.
call
(
expression
);
if
(
result
===
undefined
)
{
this
.
walkLeftRightExpression
(
expression
);
}
else
{
if
(
result
)
{
this
.
walkExpression
(
expression
.
right
);
}
}
}
walkAssignmentExpression
(
expression
)
{
if
(
expression
.
left
.
type
===
"Identifier"
)
{
const
renameIdentifier
=
this
.
getRenameIdentifier
(
expression
.
right
);
if
(
renameIdentifier
)
{
if
(
this
.
callHooksForInfo
(
this
.
hooks
.
canRename
,
renameIdentifier
,
expression
.
right
)
)
{
// renaming "a = b;"
if
(
!
this
.
callHooksForInfo
(
this
.
hooks
.
rename
,
renameIdentifier
,
expression
.
right
)
)
{
this
.
setVariable
(
expression
.
left
.
name
,
this
.
getVariableInfo
(
renameIdentifier
)
);
}
return
;
}
}
this
.
walkExpression
(
expression
.
right
);
this
.
enterPattern
(
expression
.
left
,
(
name
,
decl
)
=>
{
if
(
!
this
.
callHooksForName
(
this
.
hooks
.
assign
,
name
,
expression
))
{
this
.
walkExpression
(
expression
.
left
);
}
});
return
;
}
if
(
expression
.
left
.
type
.
endsWith
(
"Pattern"
))
{
this
.
walkExpression
(
expression
.
right
);
this
.
enterPattern
(
expression
.
left
,
(
name
,
decl
)
=>
{
if
(
!
this
.
callHooksForName
(
this
.
hooks
.
assign
,
name
,
expression
))
{
this
.
defineVariable
(
name
);
}
});
this
.
walkPattern
(
expression
.
left
);
}
else
if
(
expression
.
left
.
type
===
"MemberExpression"
)
{
const
exprName
=
this
.
getMemberExpressionInfo
(
expression
.
left
,
ALLOWED_MEMBER_TYPES_EXPRESSION
);
if
(
exprName
)
{
if
(
this
.
callHooksForInfo
(
this
.
hooks
.
assignMemberChain
,
exprName
.
rootInfo
,
expression
,
exprName
.
getMembers
()
)
)
{
return
;
}
}
this
.
walkExpression
(
expression
.
right
);
this
.
walkExpression
(
expression
.
left
);
}
else
{
this
.
walkExpression
(
expression
.
right
);
this
.
walkExpression
(
expression
.
left
);
}
}
walkConditionalExpression
(
expression
)
{
const
result
=
this
.
hooks
.
expressionConditionalOperator
.
call
(
expression
);
if
(
result
===
undefined
)
{
this
.
walkExpression
(
expression
.
test
);
this
.
walkExpression
(
expression
.
consequent
);
if
(
expression
.
alternate
)
{
this
.
walkExpression
(
expression
.
alternate
);
}
}
else
{
if
(
result
)
{
this
.
walkExpression
(
expression
.
consequent
);
}
else
if
(
expression
.
alternate
)
{
this
.
walkExpression
(
expression
.
alternate
);
}
}
}
walkNewExpression
(
expression
)
{
const
result
=
this
.
callHooksForExpression
(
this
.
hooks
.
new
,
expression
.
callee
,
expression
);
if
(
result
===
true
)
return
;
this
.
walkExpression
(
expression
.
callee
);
if
(
expression
.
arguments
)
{
this
.
walkExpressions
(
expression
.
arguments
);
}
}
walkYieldExpression
(
expression
)
{
if
(
expression
.
argument
)
{
this
.
walkExpression
(
expression
.
argument
);
}
}
walkTemplateLiteral
(
expression
)
{
if
(
expression
.
expressions
)
{
this
.
walkExpressions
(
expression
.
expressions
);
}
}
walkTaggedTemplateExpression
(
expression
)
{
if
(
expression
.
tag
)
{
this
.
walkExpression
(
expression
.
tag
);
}
if
(
expression
.
quasi
&&
expression
.
quasi
.
expressions
)
{
this
.
walkExpressions
(
expression
.
quasi
.
expressions
);
}
}
walkClassExpression
(
expression
)
{
this
.
walkClass
(
expression
);
}
/**
* @param {ChainExpressionNode} expression expression
*/
walkChainExpression
(
expression
)
{
const
result
=
this
.
hooks
.
optionalChaining
.
call
(
expression
);
if
(
result
===
undefined
)
{
if
(
expression
.
expression
.
type
===
"CallExpression"
)
{
this
.
walkCallExpression
(
expression
.
expression
);
}
else
{
this
.
walkMemberExpression
(
expression
.
expression
);
}
}
}
_walkIIFE
(
functionExpression
,
options
,
currentThis
)
{
const
getVarInfo
=
argOrThis
=>
{
const
renameIdentifier
=
this
.
getRenameIdentifier
(
argOrThis
);
if
(
renameIdentifier
)
{
if
(
this
.
callHooksForInfo
(
this
.
hooks
.
canRename
,
renameIdentifier
,
argOrThis
)
)
{
if
(
!
this
.
callHooksForInfo
(
this
.
hooks
.
rename
,
renameIdentifier
,
argOrThis
)
)
{
return
this
.
getVariableInfo
(
renameIdentifier
);
}
}
}
this
.
walkExpression
(
argOrThis
);
};
const
{
params
,
type
}
=
functionExpression
;
const
arrow
=
type
===
"ArrowFunctionExpression"
;
const
renameThis
=
currentThis
?
getVarInfo
(
currentThis
)
:
null
;
const
varInfoForArgs
=
options
.
map
(
getVarInfo
);
const
wasTopLevel
=
this
.
scope
.
topLevelScope
;
this
.
scope
.
topLevelScope
=
wasTopLevel
&&
arrow
?
"arrow"
:
false
;
const
scopeParams
=
params
.
filter
(
(
identifier
,
idx
)
=>
!
varInfoForArgs
[
idx
]
);
// Add function name in scope for recursive calls
if
(
functionExpression
.
id
)
{
scopeParams
.
push
(
functionExpression
.
id
.
name
);
}
this
.
inFunctionScope
(
true
,
scopeParams
,
()
=>
{
if
(
renameThis
&&
!
arrow
)
{
this
.
setVariable
(
"this"
,
renameThis
);
}
for
(
let
i
=
0
;
i
<
varInfoForArgs
.
length
;
i
++
)
{
const
varInfo
=
varInfoForArgs
[
i
];
if
(
!
varInfo
)
continue
;
if
(
!
params
[
i
]
||
params
[
i
].
type
!==
"Identifier"
)
continue
;
this
.
setVariable
(
params
[
i
].
name
,
varInfo
);
}
if
(
functionExpression
.
body
.
type
===
"BlockStatement"
)
{
this
.
detectMode
(
functionExpression
.
body
.
body
);
const
prev
=
this
.
prevStatement
;
this
.
preWalkStatement
(
functionExpression
.
body
);
this
.
prevStatement
=
prev
;
this
.
walkStatement
(
functionExpression
.
body
);
}
else
{
this
.
walkExpression
(
functionExpression
.
body
);
}
});
this
.
scope
.
topLevelScope
=
wasTopLevel
;
}
walkImportExpression
(
expression
)
{
let
result
=
this
.
hooks
.
importCall
.
call
(
expression
);
if
(
result
===
true
)
return
;
this
.
walkExpression
(
expression
.
source
);
}
walkCallExpression
(
expression
)
{
if
(
expression
.
callee
.
type
===
"MemberExpression"
&&
expression
.
callee
.
object
.
type
.
endsWith
(
"FunctionExpression"
)
&&
!
expression
.
callee
.
computed
&&
(
expression
.
callee
.
property
.
name
===
"call"
||
expression
.
callee
.
property
.
name
===
"bind"
)
&&
expression
.
arguments
.
length
>
0
)
{
// (function(…) { }.call/bind(?, …))
this
.
_walkIIFE
(
expression
.
callee
.
object
,
expression
.
arguments
.
slice
(
1
),
expression
.
arguments
[
0
]
);
}
else
if
(
expression
.
callee
.
type
.
endsWith
(
"FunctionExpression"
))
{
// (function(…) { }(…))
this
.
_walkIIFE
(
expression
.
callee
,
expression
.
arguments
,
null
);
}
else
{
if
(
expression
.
callee
.
type
===
"MemberExpression"
)
{
const
exprInfo
=
this
.
getMemberExpressionInfo
(
expression
.
callee
,
ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
);
if
(
exprInfo
&&
exprInfo
.
type
===
"call"
)
{
const
result
=
this
.
callHooksForInfo
(
this
.
hooks
.
callMemberChainOfCallMemberChain
,
exprInfo
.
rootInfo
,
expression
,
exprInfo
.
getCalleeMembers
(),
exprInfo
.
call
,
exprInfo
.
getMembers
()
);
if
(
result
===
true
)
return
;
}
}
const
callee
=
this
.
evaluateExpression
(
expression
.
callee
);
if
(
callee
.
isIdentifier
())
{
const
result1
=
this
.
callHooksForInfo
(
this
.
hooks
.
callMemberChain
,
callee
.
rootInfo
,
expression
,
callee
.
getMembers
()
);
if
(
result1
===
true
)
return
;
const
result2
=
this
.
callHooksForInfo
(
this
.
hooks
.
call
,
callee
.
identifier
,
expression
);
if
(
result2
===
true
)
return
;
}
if
(
expression
.
callee
)
{
if
(
expression
.
callee
.
type
===
"MemberExpression"
)
{
// because of call context we need to walk the call context as expression
this
.
walkExpression
(
expression
.
callee
.
object
);
if
(
expression
.
callee
.
computed
===
true
)
this
.
walkExpression
(
expression
.
callee
.
property
);
}
else
{
this
.
walkExpression
(
expression
.
callee
);
}
}
if
(
expression
.
arguments
)
this
.
walkExpressions
(
expression
.
arguments
);
}
}
walkMemberExpression
(
expression
)
{
const
exprInfo
=
this
.
getMemberExpressionInfo
(
expression
,
ALLOWED_MEMBER_TYPES_ALL
);
if
(
exprInfo
)
{
switch
(
exprInfo
.
type
)
{
case
"expression"
:
{
const
result1
=
this
.
callHooksForInfo
(
this
.
hooks
.
expression
,
exprInfo
.
name
,
expression
);
if
(
result1
===
true
)
return
;
const
members
=
exprInfo
.
getMembers
();
const
result2
=
this
.
callHooksForInfo
(
this
.
hooks
.
expressionMemberChain
,
exprInfo
.
rootInfo
,
expression
,
members
);
if
(
result2
===
true
)
return
;
this
.
walkMemberExpressionWithExpressionName
(
expression
,
exprInfo
.
name
,
exprInfo
.
rootInfo
,
members
.
slice
(),
()
=>
this
.
callHooksForInfo
(
this
.
hooks
.
unhandledExpressionMemberChain
,
exprInfo
.
rootInfo
,
expression
,
members
)
);
return
;
}
case
"call"
:
{
const
result
=
this
.
callHooksForInfo
(
this
.
hooks
.
memberChainOfCallMemberChain
,
exprInfo
.
rootInfo
,
expression
,
exprInfo
.
getCalleeMembers
(),
exprInfo
.
call
,
exprInfo
.
getMembers
()
);
if
(
result
===
true
)
return
;
// Fast skip over the member chain as we already called memberChainOfCallMemberChain
// and call computed property are literals anyway
this
.
walkExpression
(
exprInfo
.
call
);
return
;
}
}
}
this
.
walkExpression
(
expression
.
object
);
if
(
expression
.
computed
===
true
)
this
.
walkExpression
(
expression
.
property
);
}
walkMemberExpressionWithExpressionName
(
expression
,
name
,
rootInfo
,
members
,
onUnhandled
)
{
if
(
expression
.
object
.
type
===
"MemberExpression"
)
{
// optimize the case where expression.object is a MemberExpression too.
// we can keep info here when calling walkMemberExpression directly
const
property
=
expression
.
property
.
name
||
`
$
{
expression
.
property
.
value
}
`
;
name
=
name
.
slice
(
0
,
-
property
.
length
-
1
);
members
.
pop
();
const
result
=
this
.
callHooksForInfo
(
this
.
hooks
.
expression
,
name
,
expression
.
object
);
if
(
result
===
true
)
return
;
this
.
walkMemberExpressionWithExpressionName
(
expression
.
object
,
name
,
rootInfo
,
members
,
onUnhandled
);
}
else
if
(
!
onUnhandled
||
!
onUnhandled
())
{
this
.
walkExpression
(
expression
.
object
);
}
if
(
expression
.
computed
===
true
)
this
.
walkExpression
(
expression
.
property
);
}
walkThisExpression
(
expression
)
{
this
.
callHooksForName
(
this
.
hooks
.
expression
,
"this"
,
expression
);
}
walkIdentifier
(
expression
)
{
this
.
callHooksForName
(
this
.
hooks
.
expression
,
expression
.
name
,
expression
);
}
/**
* @param {MetaPropertyNode} metaProperty meta property
*/
walkMetaProperty
(
metaProperty
)
{
this
.
hooks
.
expression
.
for
(
getRootName
(
metaProperty
)).
call
(
metaProperty
);
}
callHooksForExpression
(
hookMap
,
expr
,
...
args
)
{
return
this
.
callHooksForExpressionWithFallback
(
hookMap
,
expr
,
undefined
,
undefined
,
...
args
);
}
/**
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
* @param {MemberExpressionNode} expr expression info
* @param {function(string, string | ScopeInfo | VariableInfo, function(): string[]): any} fallback callback when variable in not handled by hooks
* @param {function(string): any} defined callback when variable is defined
* @param {AsArray<T>} args args for the hook
* @returns {R} result of hook
*/
callHooksForExpressionWithFallback
(
hookMap
,
expr
,
fallback
,
defined
,
...
args
)
{
const
exprName
=
this
.
getMemberExpressionInfo
(
expr
,
ALLOWED_MEMBER_TYPES_EXPRESSION
);
if
(
exprName
!==
undefined
)
{
const
members
=
exprName
.
getMembers
();
return
this
.
callHooksForInfoWithFallback
(
hookMap
,
members
.
length
===
0
?
exprName
.
rootInfo
:
exprName
.
name
,
fallback
&&
(
name
=>
fallback
(
name
,
exprName
.
rootInfo
,
exprName
.
getMembers
)),
defined
&&
(()
=>
defined
(
exprName
.
name
)),
...
args
);
}
}
/**
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
* @param {string} name key in map
* @param {AsArray<T>} args args for the hook
* @returns {R} result of hook
*/
callHooksForName
(
hookMap
,
name
,
...
args
)
{
return
this
.
callHooksForNameWithFallback
(
hookMap
,
name
,
undefined
,
undefined
,
...
args
);
}
/**
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap hooks that should be called
* @param {ExportedVariableInfo} info variable info
* @param {AsArray<T>} args args for the hook
* @returns {R} result of hook
*/
callHooksForInfo
(
hookMap
,
info
,
...
args
)
{
return
this
.
callHooksForInfoWithFallback
(
hookMap
,
info
,
undefined
,
undefined
,
...
args
);
}
/**
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
* @param {ExportedVariableInfo} info variable info
* @param {function(string): any} fallback callback when variable in not handled by hooks
* @param {function(): any} defined callback when variable is defined
* @param {AsArray<T>} args args for the hook
* @returns {R} result of hook
*/
callHooksForInfoWithFallback
(
hookMap
,
info
,
fallback
,
defined
,
...
args
)
{
let
name
;
if
(
typeof
info
===
"string"
)
{
name
=
info
;
}
else
{
if
(
!
(
info
instanceof
VariableInfo
))
{
if
(
defined
!==
undefined
)
{
return
defined
();
}
return
;
}
let
tagInfo
=
info
.
tagInfo
;
while
(
tagInfo
!==
undefined
)
{
const
hook
=
hookMap
.
get
(
tagInfo
.
tag
);
if
(
hook
!==
undefined
)
{
this
.
currentTagData
=
tagInfo
.
data
;
const
result
=
hook
.
call
(...
args
);
this
.
currentTagData
=
undefined
;
if
(
result
!==
undefined
)
return
result
;
}
tagInfo
=
tagInfo
.
next
;
}
if
(
info
.
freeName
===
true
)
{
if
(
defined
!==
undefined
)
{
return
defined
();
}
return
;
}
name
=
info
.
freeName
;
}
const
hook
=
hookMap
.
get
(
name
);
if
(
hook
!==
undefined
)
{
const
result
=
hook
.
call
(...
args
);
if
(
result
!==
undefined
)
return
result
;
}
if
(
fallback
!==
undefined
)
{
return
fallback
(
name
);
}
}
/**
* @template T
* @template R
* @param {HookMap<SyncBailHook<T, R>>} hookMap hooks the should be called
* @param {string} name key in map
* @param {function(string): any} fallback callback when variable in not handled by hooks
* @param {function(): any} defined callback when variable is defined
* @param {AsArray<T>} args args for the hook
* @returns {R} result of hook
*/
callHooksForNameWithFallback
(
hookMap
,
name
,
fallback
,
defined
,
...
args
)
{
return
this
.
callHooksForInfoWithFallback
(
hookMap
,
this
.
getVariableInfo
(
name
),
fallback
,
defined
,
...
args
);
}
/**
* @deprecated
* @param {any} params scope params
* @param {function(): void} fn inner function
* @returns {void}
*/
inScope
(
params
,
fn
)
{
const
oldScope
=
this
.
scope
;
this
.
scope
=
{
topLevelScope
:
oldScope
.
topLevelScope
,
inTry
:
false
,
inShorthand
:
false
,
isStrict
:
oldScope
.
isStrict
,
isAsmJs
:
oldScope
.
isAsmJs
,
definitions
:
oldScope
.
definitions
.
createChild
()
};
this
.
undefineVariable
(
"this"
);
this
.
enterPatterns
(
params
,
(
ident
,
pattern
)
=>
{
this
.
defineVariable
(
ident
);
});
fn
();
this
.
scope
=
oldScope
;
}
inFunctionScope
(
hasThis
,
params
,
fn
)
{
const
oldScope
=
this
.
scope
;
this
.
scope
=
{
topLevelScope
:
oldScope
.
topLevelScope
,
inTry
:
false
,
inShorthand
:
false
,
isStrict
:
oldScope
.
isStrict
,
isAsmJs
:
oldScope
.
isAsmJs
,
definitions
:
oldScope
.
definitions
.
createChild
()
};
if
(
hasThis
)
{
this
.
undefineVariable
(
"this"
);
}
this
.
enterPatterns
(
params
,
(
ident
,
pattern
)
=>
{
this
.
defineVariable
(
ident
);
});
fn
();
this
.
scope
=
oldScope
;
}
inBlockScope
(
fn
)
{
const
oldScope
=
this
.
scope
;
this
.
scope
=
{
topLevelScope
:
oldScope
.
topLevelScope
,
inTry
:
oldScope
.
inTry
,
inShorthand
:
false
,
isStrict
:
oldScope
.
isStrict
,
isAsmJs
:
oldScope
.
isAsmJs
,
definitions
:
oldScope
.
definitions
.
createChild
()
};
fn
();
this
.
scope
=
oldScope
;
}
detectMode
(
statements
)
{
const
isLiteral
=
statements
.
length
>=
1
&&
statements
[
0
].
type
===
"ExpressionStatement"
&&
statements
[
0
].
expression
.
type
===
"Literal"
;
if
(
isLiteral
&&
statements
[
0
].
expression
.
value
===
"use strict"
)
{
this
.
scope
.
isStrict
=
true
;
}
if
(
isLiteral
&&
statements
[
0
].
expression
.
value
===
"use asm"
)
{
this
.
scope
.
isAsmJs
=
true
;
}
}
enterPatterns
(
patterns
,
onIdent
)
{
for
(
const
pattern
of
patterns
)
{
if
(
typeof
pattern
!==
"string"
)
{
this
.
enterPattern
(
pattern
,
onIdent
);
}
else
if
(
pattern
)
{
onIdent
(
pattern
);
}
}
}
enterPattern
(
pattern
,
onIdent
)
{
if
(
!
pattern
)
return
;
switch
(
pattern
.
type
)
{
case
"ArrayPattern"
:
this
.
enterArrayPattern
(
pattern
,
onIdent
);
break
;
case
"AssignmentPattern"
:
this
.
enterAssignmentPattern
(
pattern
,
onIdent
);
break
;
case
"Identifier"
:
this
.
enterIdentifier
(
pattern
,
onIdent
);
break
;
case
"ObjectPattern"
:
this
.
enterObjectPattern
(
pattern
,
onIdent
);
break
;
case
"RestElement"
:
this
.
enterRestElement
(
pattern
,
onIdent
);
break
;
case
"Property"
:
if
(
pattern
.
shorthand
&&
pattern
.
value
.
type
===
"Identifier"
)
{
this
.
scope
.
inShorthand
=
pattern
.
value
.
name
;
this
.
enterIdentifier
(
pattern
.
value
,
onIdent
);
this
.
scope
.
inShorthand
=
false
;
}
else
{
this
.
enterPattern
(
pattern
.
value
,
onIdent
);
}
break
;
}
}
enterIdentifier
(
pattern
,
onIdent
)
{
if
(
!
this
.
callHooksForName
(
this
.
hooks
.
pattern
,
pattern
.
name
,
pattern
))
{
onIdent
(
pattern
.
name
,
pattern
);
}
}
enterObjectPattern
(
pattern
,
onIdent
)
{
for
(
let
propIndex
=
0
,
len
=
pattern
.
properties
.
length
;
propIndex
<
len
;
propIndex
++
)
{
const
prop
=
pattern
.
properties
[
propIndex
];
this
.
enterPattern
(
prop
,
onIdent
);
}
}
enterArrayPattern
(
pattern
,
onIdent
)
{
for
(
let
elementIndex
=
0
,
len
=
pattern
.
elements
.
length
;
elementIndex
<
len
;
elementIndex
++
)
{
const
element
=
pattern
.
elements
[
elementIndex
];
this
.
enterPattern
(
element
,
onIdent
);
}
}
enterRestElement
(
pattern
,
onIdent
)
{
this
.
enterPattern
(
pattern
.
argument
,
onIdent
);
}
enterAssignmentPattern
(
pattern
,
onIdent
)
{
this
.
enterPattern
(
pattern
.
left
,
onIdent
);
}
/**
* @param {ExpressionNode} expression expression node
* @returns {BasicEvaluatedExpression | undefined} evaluation result
*/
evaluateExpression
(
expression
)
{
try
{
const
hook
=
this
.
hooks
.
evaluate
.
get
(
expression
.
type
);
if
(
hook
!==
undefined
)
{
const
result
=
hook
.
call
(
expression
);
if
(
result
!==
undefined
)
{
if
(
result
)
{
result
.
setExpression
(
expression
);
}
return
result
;
}
}
}
catch
(
e
)
{
console
.
warn
(
e
);
// ignore error
}
return
new
BasicEvaluatedExpression
()
.
setRange
(
expression
.
range
)
.
setExpression
(
expression
);
}
parseString
(
expression
)
{
switch
(
expression
.
type
)
{
case
"BinaryExpression"
:
if
(
expression
.
operator
===
"+"
)
{
return
(
this
.
parseString
(
expression
.
left
)
+
this
.
parseString
(
expression
.
right
)
);
}
break
;
case
"Literal"
:
return
expression
.
value
+
""
;
}
throw
new
Error
(
expression
.
type
+
" is not supported as parameter for require"
);
}
parseCalculatedString
(
expression
)
{
switch
(
expression
.
type
)
{
case
"BinaryExpression"
:
if
(
expression
.
operator
===
"+"
)
{
const
left
=
this
.
parseCalculatedString
(
expression
.
left
);
const
right
=
this
.
parseCalculatedString
(
expression
.
right
);
if
(
left
.
code
)
{
return
{
range
:
left
.
range
,
value
:
left
.
value
,
code
:
true
,
conditional
:
false
};
}
else
if
(
right
.
code
)
{
return
{
range
:
[
left
.
range
[
0
],
right
.
range
?
right
.
range
[
1
]
:
left
.
range
[
1
]
],
value
:
left
.
value
+
right
.
value
,
code
:
true
,
conditional
:
false
};
}
else
{
return
{
range
:
[
left
.
range
[
0
],
right
.
range
[
1
]],
value
:
left
.
value
+
right
.
value
,
code
:
false
,
conditional
:
false
};
}
}
break
;
case
"ConditionalExpression"
:
{
const
consequent
=
this
.
parseCalculatedString
(
expression
.
consequent
);
const
alternate
=
this
.
parseCalculatedString
(
expression
.
alternate
);
const
items
=
[];
if
(
consequent
.
conditional
)
{
items
.
push
(...
consequent
.
conditional
);
}
else
if
(
!
consequent
.
code
)
{
items
.
push
(
consequent
);
}
else
{
break
;
}
if
(
alternate
.
conditional
)
{
items
.
push
(...
alternate
.
conditional
);
}
else
if
(
!
alternate
.
code
)
{
items
.
push
(
alternate
);
}
else
{
break
;
}
return
{
range
:
undefined
,
value
:
""
,
code
:
true
,
conditional
:
items
};
}
case
"Literal"
:
return
{
range
:
expression
.
range
,
value
:
expression
.
value
+
""
,
code
:
false
,
conditional
:
false
};
}
return
{
range
:
undefined
,
value
:
""
,
code
:
true
,
conditional
:
false
};
}
/**
* @param {string | Buffer | PreparsedAst} source the source to parse
* @param {ParserState} state the parser state
* @returns {ParserState} the parser state
*/
parse
(
source
,
state
)
{
let
ast
;
let
comments
;
const
semicolons
=
new
Set
();
if
(
source
===
null
)
{
throw
new
Error
(
"source must not be null"
);
}
if
(
Buffer
.
isBuffer
(
source
))
{
source
=
source
.
toString
(
"utf-8"
);
}
if
(
typeof
source
===
"object"
)
{
ast
=
/** @type {ProgramNode} */
(
source
);
comments
=
source
.
comments
;
}
else
{
comments
=
[];
ast
=
JavascriptParser
.
_parse
(
source
,
{
sourceType
:
this
.
sourceType
,
onComment
:
comments
,
onInsertedSemicolon
:
pos
=>
semicolons
.
add
(
pos
)
});
}
const
oldScope
=
this
.
scope
;
const
oldState
=
this
.
state
;
const
oldComments
=
this
.
comments
;
const
oldSemicolons
=
this
.
semicolons
;
const
oldStatementPath
=
this
.
statementPath
;
const
oldPrevStatement
=
this
.
prevStatement
;
this
.
scope
=
{
topLevelScope
:
true
,
inTry
:
false
,
inShorthand
:
false
,
isStrict
:
false
,
isAsmJs
:
false
,
definitions
:
new
StackedMap
()
};
/** @type {ParserState} */
this
.
state
=
state
;
this
.
comments
=
comments
;
this
.
semicolons
=
semicolons
;
this
.
statementPath
=
[];
this
.
prevStatement
=
undefined
;
if
(
this
.
hooks
.
program
.
call
(
ast
,
comments
)
===
undefined
)
{
this
.
detectMode
(
ast
.
body
);
this
.
preWalkStatements
(
ast
.
body
);
this
.
prevStatement
=
undefined
;
this
.
blockPreWalkStatements
(
ast
.
body
);
this
.
prevStatement
=
undefined
;
this
.
walkStatements
(
ast
.
body
);
}
this
.
hooks
.
finish
.
call
(
ast
,
comments
);
this
.
scope
=
oldScope
;
/** @type {ParserState} */
this
.
state
=
oldState
;
this
.
comments
=
oldComments
;
this
.
semicolons
=
oldSemicolons
;
this
.
statementPath
=
oldStatementPath
;
this
.
prevStatement
=
oldPrevStatement
;
return
state
;
}
evaluate
(
source
)
{
const
ast
=
JavascriptParser
.
_parse
(
"("
+
source
+
")"
,
{
sourceType
:
this
.
sourceType
,
locations
:
false
});
if
(
ast
.
body
.
length
!==
1
||
ast
.
body
[
0
].
type
!==
"ExpressionStatement"
)
{
throw
new
Error
(
"evaluate: Source is not a expression"
);
}
return
this
.
evaluateExpression
(
ast
.
body
[
0
].
expression
);
}
/**
* @param {ExpressionNode | DeclarationNode | null | undefined} expr an expression
* @param {number} commentsStartPos source position from which annotation comments are checked
* @returns {boolean} true, when the expression is pure
*/
isPure
(
expr
,
commentsStartPos
)
{
if
(
!
expr
)
return
true
;
const
result
=
this
.
hooks
.
isPure
.
for
(
expr
.
type
)
.
call
(
expr
,
commentsStartPos
);
if
(
typeof
result
===
"boolean"
)
return
result
;
switch
(
expr
.
type
)
{
case
"ClassDeclaration"
:
case
"ClassExpression"
:
if
(
expr
.
body
.
type
!==
"ClassBody"
)
return
false
;
if
(
expr
.
superClass
&&
!
this
.
isPure
(
expr
.
superClass
,
expr
.
range
[
0
]))
{
return
false
;
}
return
expr
.
body
.
body
.
every
(
item
=>
{
switch
(
item
.
type
)
{
// @ts-expect-error not yet supported by acorn
case
"ClassProperty"
:
// TODO add test case once acorn supports it
// Currently this is not parsable
if
(
item
.
static
)
return
this
.
isPure
(
item
.
value
,
item
.
range
[
0
]);
break
;
}
return
true
;
});
case
"FunctionDeclaration"
:
case
"FunctionExpression"
:
case
"ArrowFunctionExpression"
:
case
"Literal"
:
return
true
;
case
"VariableDeclaration"
:
return
expr
.
declarations
.
every
(
decl
=>
this
.
isPure
(
decl
.
init
,
decl
.
range
[
0
])
);
case
"ConditionalExpression"
:
return
(
this
.
isPure
(
expr
.
test
,
commentsStartPos
)
&&
this
.
isPure
(
expr
.
consequent
,
expr
.
test
.
range
[
1
])
&&
this
.
isPure
(
expr
.
alternate
,
expr
.
consequent
.
range
[
1
])
);
case
"SequenceExpression"
:
return
expr
.
expressions
.
every
(
expr
=>
{
const
pureFlag
=
this
.
isPure
(
expr
,
commentsStartPos
);
commentsStartPos
=
expr
.
range
[
1
];
return
pureFlag
;
});
case
"CallExpression"
:
{
const
pureFlag
=
expr
.
range
[
0
]
-
commentsStartPos
>
12
&&
this
.
getComments
([
commentsStartPos
,
expr
.
range
[
0
]]).
some
(
comment
=>
comment
.
type
===
"Block"
&&
/^\s*(#|@)__PURE__\s*$/
.
test
(
comment
.
value
)
);
if
(
!
pureFlag
)
return
false
;
commentsStartPos
=
expr
.
callee
.
range
[
1
];
return
expr
.
arguments
.
every
(
arg
=>
{
if
(
arg
.
type
===
"SpreadElement"
)
return
false
;
const
pureFlag
=
this
.
isPure
(
arg
,
commentsStartPos
);
commentsStartPos
=
arg
.
range
[
1
];
return
pureFlag
;
});
}
}
const
evaluated
=
this
.
evaluateExpression
(
expr
);
return
!
evaluated
.
couldHaveSideEffects
();
}
getComments
(
range
)
{
return
this
.
comments
.
filter
(
comment
=>
comment
.
range
[
0
]
>=
range
[
0
]
&&
comment
.
range
[
1
]
<=
range
[
1
]
);
}
/**
* @param {number} pos source code position
* @returns {boolean} true when a semicolon has been inserted before this position, false if not
*/
isAsiPosition
(
pos
)
{
const
currentStatement
=
this
.
statementPath
[
this
.
statementPath
.
length
-
1
];
if
(
currentStatement
===
undefined
)
throw
new
Error
(
"Not in statement"
);
return
(
// Either asking directly for the end position of the current statement
(
currentStatement
.
range
[
1
]
===
pos
&&
this
.
semicolons
.
has
(
pos
))
||
// Or asking for the start position of the current statement,
// here we have to check multiple things
(
currentStatement
.
range
[
0
]
===
pos
&&
// is there a previous statement which might be relevant?
this
.
prevStatement
!==
undefined
&&
// is the end position of the previous statement an ASI position?
this
.
semicolons
.
has
(
this
.
prevStatement
.
range
[
1
]))
);
}
/**
* @param {number} pos source code position
* @returns {void}
*/
unsetAsiPosition
(
pos
)
{
this
.
semicolons
.
delete
(
pos
);
}
isStatementLevelExpression
(
expr
)
{
const
currentStatement
=
this
.
statementPath
[
this
.
statementPath
.
length
-
1
];
return
(
expr
===
currentStatement
||
(
currentStatement
.
type
===
"ExpressionStatement"
&&
currentStatement
.
expression
===
expr
)
);
}
getTagData
(
name
,
tag
)
{
const
info
=
this
.
scope
.
definitions
.
get
(
name
);
if
(
info
instanceof
VariableInfo
)
{
let
tagInfo
=
info
.
tagInfo
;
while
(
tagInfo
!==
undefined
)
{
if
(
tagInfo
.
tag
===
tag
)
return
tagInfo
.
data
;
tagInfo
=
tagInfo
.
next
;
}
}
}
tagVariable
(
name
,
tag
,
data
)
{
const
oldInfo
=
this
.
scope
.
definitions
.
get
(
name
);
/** @type {VariableInfo} */
let
newInfo
;
if
(
oldInfo
===
undefined
)
{
newInfo
=
new
VariableInfo
(
this
.
scope
,
name
,
{
tag
,
data
,
next
:
undefined
});
}
else
if
(
oldInfo
instanceof
VariableInfo
)
{
newInfo
=
new
VariableInfo
(
oldInfo
.
declaredScope
,
oldInfo
.
freeName
,
{
tag
,
data
,
next
:
oldInfo
.
tagInfo
});
}
else
{
newInfo
=
new
VariableInfo
(
oldInfo
,
true
,
{
tag
,
data
,
next
:
undefined
});
}
this
.
scope
.
definitions
.
set
(
name
,
newInfo
);
}
defineVariable
(
name
)
{
const
oldInfo
=
this
.
scope
.
definitions
.
get
(
name
);
// Don't redefine variable in same scope to keep existing tags
if
(
oldInfo
instanceof
VariableInfo
&&
oldInfo
.
declaredScope
===
this
.
scope
)
return
;
this
.
scope
.
definitions
.
set
(
name
,
this
.
scope
);
}
undefineVariable
(
name
)
{
this
.
scope
.
definitions
.
delete
(
name
);
}
isVariableDefined
(
name
)
{
const
info
=
this
.
scope
.
definitions
.
get
(
name
);
if
(
info
===
undefined
)
return
false
;
if
(
info
instanceof
VariableInfo
)
{
return
info
.
freeName
===
true
;
}
return
true
;
}
/**
* @param {string} name variable name
* @returns {ExportedVariableInfo} info for this variable
*/
getVariableInfo
(
name
)
{
const
value
=
this
.
scope
.
definitions
.
get
(
name
);
if
(
value
===
undefined
)
{
return
name
;
}
else
{
return
value
;
}
}
/**
* @param {string} name variable name
* @param {ExportedVariableInfo} variableInfo new info for this variable
* @returns {void}
*/
setVariable
(
name
,
variableInfo
)
{
if
(
typeof
variableInfo
===
"string"
)
{
if
(
variableInfo
===
name
)
{
this
.
scope
.
definitions
.
delete
(
name
);
}
else
{
this
.
scope
.
definitions
.
set
(
name
,
new
VariableInfo
(
this
.
scope
,
variableInfo
,
undefined
)
);
}
}
else
{
this
.
scope
.
definitions
.
set
(
name
,
variableInfo
);
}
}
parseCommentOptions
(
range
)
{
const
comments
=
this
.
getComments
(
range
);
if
(
comments
.
length
===
0
)
{
return
EMPTY_COMMENT_OPTIONS
;
}
let
options
=
{};
let
errors
=
[];
for
(
const
comment
of
comments
)
{
const
{
value
}
=
comment
;
if
(
value
&&
webpackCommentRegExp
.
test
(
value
))
{
// try compile only if webpack options comment is present
try
{
const
val
=
vm
.
runInNewContext
(
`
(
function
(){
return
{
$
{
value
}};})()
`
);
Object
.
assign
(
options
,
val
);
}
catch
(
e
)
{
e
.
comment
=
comment
;
errors
.
push
(
e
);
}
}
}
return
{
options
,
errors
};
}
/**
* @param {MemberExpressionNode} expression a member expression
* @returns {{ members: string[], object: ExpressionNode | SuperNode }} member names (reverse order) and remaining object
*/
extractMemberExpressionChain
(
expression
)
{
/** @type {AnyNode} */
let
expr
=
expression
;
const
members
=
[];
while
(
expr
.
type
===
"MemberExpression"
)
{
if
(
expr
.
computed
)
{
if
(
expr
.
property
.
type
!==
"Literal"
)
break
;
members
.
push
(
`
$
{
expr
.
property
.
value
}
`
);
}
else
{
if
(
expr
.
property
.
type
!==
"Identifier"
)
break
;
members
.
push
(
expr
.
property
.
name
);
}
expr
=
expr
.
object
;
}
return
{
members
,
object
:
expr
};
}
/**
* @param {string} varName variable name
* @returns {{name: string, info: VariableInfo | string}} name of the free variable and variable info for that
*/
getFreeInfoFromVariable
(
varName
)
{
const
info
=
this
.
getVariableInfo
(
varName
);
let
name
;
if
(
info
instanceof
VariableInfo
)
{
name
=
info
.
freeName
;
if
(
typeof
name
!==
"string"
)
return
undefined
;
}
else
if
(
typeof
info
!==
"string"
)
{
return
undefined
;
}
else
{
name
=
info
;
}
return
{
info
,
name
};
}
/** @typedef {{ type: "call", call: CallExpressionNode, calleeName: string, rootInfo: string | VariableInfo, getCalleeMembers: () => string[], name: string, getMembers: () => string[]}} CallExpressionInfo */
/** @typedef {{ type: "expression", rootInfo: string | VariableInfo, name: string, getMembers: () => string[]}} ExpressionExpressionInfo */
/**
* @param {MemberExpressionNode} expression a member expression
* @param {number} allowedTypes which types should be returned, presented in bit mask
* @returns {CallExpressionInfo | ExpressionExpressionInfo | undefined} expression info
*/
getMemberExpressionInfo
(
expression
,
allowedTypes
)
{
const
{
object
,
members
}
=
this
.
extractMemberExpressionChain
(
expression
);
switch
(
object
.
type
)
{
case
"CallExpression"
:
{
if
((
allowedTypes
&
ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
)
===
0
)
return
undefined
;
let
callee
=
object
.
callee
;
let
rootMembers
=
EMPTY_ARRAY
;
if
(
callee
.
type
===
"MemberExpression"
)
{
({
object
:
callee
,
members
:
rootMembers
}
=
this
.
extractMemberExpressionChain
(
callee
));
}
const
rootName
=
getRootName
(
callee
);
if
(
!
rootName
)
return
undefined
;
const
result
=
this
.
getFreeInfoFromVariable
(
rootName
);
if
(
!
result
)
return
undefined
;
const
{
info
:
rootInfo
,
name
:
resolvedRoot
}
=
result
;
const
calleeName
=
objectAndMembersToName
(
resolvedRoot
,
rootMembers
);
return
{
type
:
"call"
,
call
:
object
,
calleeName
,
rootInfo
,
getCalleeMembers
:
memorize
(()
=>
rootMembers
.
reverse
()),
name
:
objectAndMembersToName
(
`
$
{
calleeName
}()
`
,
members
),
getMembers
:
memorize
(()
=>
members
.
reverse
())
};
}
case
"Identifier"
:
case
"MetaProperty"
:
case
"ThisExpression"
:
{
if
((
allowedTypes
&
ALLOWED_MEMBER_TYPES_EXPRESSION
)
===
0
)
return
undefined
;
const
rootName
=
getRootName
(
object
);
if
(
!
rootName
)
return
undefined
;
const
result
=
this
.
getFreeInfoFromVariable
(
rootName
);
if
(
!
result
)
return
undefined
;
const
{
info
:
rootInfo
,
name
:
resolvedRoot
}
=
result
;
return
{
type
:
"expression"
,
name
:
objectAndMembersToName
(
resolvedRoot
,
members
),
rootInfo
,
getMembers
:
memorize
(()
=>
members
.
reverse
())
};
}
}
}
/**
* @param {MemberExpressionNode} expression an expression
* @returns {{ name: string, rootInfo: ExportedVariableInfo, getMembers: () => string[]}} name info
*/
getNameForExpression
(
expression
)
{
return
this
.
getMemberExpressionInfo
(
expression
,
ALLOWED_MEMBER_TYPES_EXPRESSION
);
}
/**
* @param {string} code source code
* @param {ParseOptions} options parsing options
* @returns {ProgramNode} parsed ast
*/
static
_parse
(
code
,
options
)
{
const
type
=
options
?
options
.
sourceType
:
"module"
;
/** @type {AcornOptions} */
const
parserOptions
=
{
...
defaultParserOptions
,
allowReturnOutsideFunction
:
type
===
"script"
,
...
options
,
sourceType
:
type
===
"auto"
?
"module"
:
type
};
/** @type {AnyNode} */
let
ast
;
let
error
;
let
threw
=
false
;
try
{
ast
=
/** @type {AnyNode} */
(
parser
.
parse
(
code
,
parserOptions
));
}
catch
(
e
)
{
error
=
e
;
threw
=
true
;
}
if
(
threw
&&
type
===
"auto"
)
{
parserOptions
.
sourceType
=
"script"
;
if
(
!
(
"allowReturnOutsideFunction"
in
options
))
{
parserOptions
.
allowReturnOutsideFunction
=
true
;
}
if
(
Array
.
isArray
(
parserOptions
.
onComment
))
{
parserOptions
.
onComment
.
length
=
0
;
}
try
{
ast
=
/** @type {AnyNode} */
(
parser
.
parse
(
code
,
parserOptions
));
threw
=
false
;
}
catch
(
e
)
{
// we use the error from first parse try
// so nothing to do here
}
}
if
(
threw
)
{
throw
error
;
}
return
/** @type {ProgramNode} */
(
ast
);
}
}
module
.
exports
=
JavascriptParser
;
module
.
exports
.
ALLOWED_MEMBER_TYPES_ALL
=
ALLOWED_MEMBER_TYPES_ALL
;
module
.
exports
.
ALLOWED_MEMBER_TYPES_EXPRESSION
=
ALLOWED_MEMBER_TYPES_EXPRESSION
;
module
.
exports
.
ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
=
ALLOWED_MEMBER_TYPES_CALL_EXPRESSION
;
Event Timeline
Log In to Comment