@@ -11,6 +11,21 @@
const astUtils = require ( "./utils/ast-utils" ) ;
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
/**
* Determines if the given code path is a code path with lexical `this` binding.
* That is, if `this` within the code path refers to `this` of surrounding code path.
* @param {CodePath } codePath Code path.
* @param {ASTNode } node Node that started the code path.
* @returns {boolean } `true` if it is a code path with lexical `this` binding.
*/
function isCodePathWithLexicalThis ( codePath , node ) {
return codePath . origin === "function" && node . type === "ArrowFunctionExpression" ;
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -72,71 +87,53 @@ module.exports = {
return current ;
} ;
/**
* Pushs new checking context into the stack.
*
* The checking context is not initialized yet.
* Because most functions don't have `this` keyword.
* When `this` keyword was found, the checking context is initialized.
* @param {ASTNode } node A function node that was entered.
* @returns {void }
*/
function enterFunction ( node ) {
// `this` can be invalid only under strict mode.
stack . push ( {
init : ! context . getScope ( ) . isStrict ,
node,
valid : true
} ) ;
}
return {
/**
* Pops the current checking context from the stack.
* @returns {void }
*/
function exitFunction ( ) {
stack . pop ( ) ;
}
onCodePathStart ( codePath , node ) {
if ( isCodePathWithLexicalThis ( codePath , node ) ) {
return ;
}
return {
if ( codePath . origin === "program" ) {
const scope = context . getScope ( ) ;
const features = context . parserOptions . ecmaFeatures || { } ;
stack . push ( {
init : true ,
node,
valid : ! (
scope . isStrict ||
node . sourceType === "module" ||
( features . globalReturn && scope . childScopes [ 0 ] . isStrict )
)
} ) ;
/*
* `this` is invalid only under strict mode.
* Modules is always strict mode.
*/
Program ( node ) {
const scope = context . getScope ( ) ,
features = context . parserOptions . ecmaFeatures || { } ;
return ;
}
/*
* `init: false` means that `valid` isn't determined yet.
* Most functions don't use `this`, and the calculation for `valid`
* is relatively costly, so we'll calculate it lazily when the first
* `this` within the function is traversed. A special case are non-strict
* functions, because `this` refers to the global object and therefore is
* always valid, so we can set `init: true` right away.
*/
stack . push ( {
init : true ,
init : ! context . getScope ( ) . isStrict ,
node,
valid : ! (
scope . isStrict ||
node . sourceType === "module" ||
( features . globalReturn && scope . childScopes [ 0 ] . isStrict )
)
valid : true
} ) ;
} ,
"Program:exit" ( ) {
onCodePathEnd ( codePath , node ) {
if ( isCodePathWithLexicalThis ( codePath , node ) ) {
return ;
}
stack . pop ( ) ;
} ,
FunctionDeclaration : enterFunction ,
"FunctionDeclaration:exit" : exitFunction ,
FunctionExpression : enterFunction ,
"FunctionExpression:exit" : exitFunction ,
// Field initializers are implicit functions.
"PropertyDefinition > *.value" : enterFunction ,
"PropertyDefinition > *.value:exit" : exitFunction ,
// Class static blocks are implicit functions.
StaticBlock : enterFunction ,
"StaticBlock:exit" : exitFunction ,
// Reports if `this` of the current context is invalid.
ThisExpression ( node ) {
const current = stack . getCurrent ( ) ;