"use strict";
|
/**
|
* A note on `__proto__`:
|
*
|
* This file uses ordinary objects to track identifiers that are observed in
|
* the input source code. It creates these objects using `Object.create` so
|
* that the tracking objects have no prototype, allowing the `__proto__`
|
* property to be used to store a value *without* triggering the invocation of
|
* the built-in `Object.prototype.__proto__` accessor method. Some environments
|
* (e.g. PhantomJS) do not implement the correct semantics for property
|
* enumeration. In those environments, methods like `Object.keys` and Lodash's
|
* `values` do not include the property name. This file includes a number of
|
* branches which ensure that JSHint behaves consistently in those
|
* environments. The branches must be ignored by the test coverage verification
|
* system because the workaround is not necessary in the environment where
|
* coverage is verified (i.e. Node.js).
|
*/
|
|
var _ = require("lodash");
|
var events = require("events");
|
|
// Used to denote membership in lookup tables (a primitive value such as `true`
|
// would be silently rejected for the property name "__proto__" in some
|
// environments)
|
var marker = {};
|
|
/**
|
* A factory function for creating scope managers. A scope manager tracks
|
* bindings, detecting when variables are referenced (through "usages").
|
*
|
* @param {object} state - the global state object (see `state.js`)
|
* @param {Array} predefined - a set of binding names for built-in bindings
|
* provided by the environment
|
* @param {object} exported - a hash for binding names that are intended to be
|
* referenced in contexts beyond the current program
|
* code
|
* @param {object} declared - a hash for binding names that were defined as
|
* global bindings via linting configuration
|
*
|
* @returns {object} - a scope manager
|
*/
|
var scopeManager = function(state, predefined, exported, declared) {
|
|
var _current;
|
var _scopeStack = [];
|
|
function _newScope(type) {
|
_current = {
|
"(bindings)": Object.create(null),
|
"(usages)": Object.create(null),
|
"(labels)": Object.create(null),
|
"(parent)": _current,
|
"(type)": type,
|
"(params)": (type === "functionparams" || type === "catchparams") ? [] : null
|
};
|
_scopeStack.push(_current);
|
}
|
|
_newScope("global");
|
_current["(predefined)"] = predefined;
|
|
var _currentFunctBody = _current; // this is the block after the params = function
|
|
var usedPredefinedAndGlobals = Object.create(null);
|
var impliedGlobals = Object.create(null);
|
var unuseds = [];
|
var esModuleExports = [];
|
var emitter = new events.EventEmitter();
|
|
function warning(code, token) {
|
emitter.emit("warning", {
|
code: code,
|
token: token,
|
data: _.slice(arguments, 2)
|
});
|
}
|
|
function error(code, token) {
|
emitter.emit("warning", {
|
code: code,
|
token: token,
|
data: _.slice(arguments, 2)
|
});
|
}
|
|
function _setupUsages(bindingName) {
|
if (!_current["(usages)"][bindingName]) {
|
_current["(usages)"][bindingName] = {
|
"(modified)": [],
|
"(reassigned)": [],
|
"(tokens)": []
|
};
|
}
|
}
|
|
var _getUnusedOption = function(unused_opt) {
|
if (unused_opt === undefined) {
|
unused_opt = state.option.unused;
|
}
|
|
if (unused_opt === true) {
|
unused_opt = "last-param";
|
}
|
|
return unused_opt;
|
};
|
|
var _warnUnused = function(name, tkn, type, unused_opt) {
|
var line = tkn.line;
|
var chr = tkn.from;
|
var raw_name = tkn.raw_text || name;
|
|
unused_opt = _getUnusedOption(unused_opt);
|
|
var warnable_types = {
|
"vars": ["var"],
|
"last-param": ["var", "param"],
|
"strict": ["var", "param", "last-param"]
|
};
|
|
if (unused_opt) {
|
if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) {
|
warning("W098", { line: line, from: chr }, raw_name);
|
}
|
}
|
|
// inconsistent - see gh-1894
|
if (unused_opt || type === "var") {
|
unuseds.push({
|
name: name,
|
line: line,
|
character: chr
|
});
|
}
|
};
|
|
/**
|
* Check the current scope for unused identifiers
|
*/
|
function _checkForUnused() {
|
if (_current["(type)"] !== "functionparams") {
|
var currentBindings = _current["(bindings)"];
|
for (var bindingName in currentBindings) {
|
if (currentBindings[bindingName]["(type)"] !== "exception" &&
|
currentBindings[bindingName]["(unused)"]) {
|
_warnUnused(bindingName, currentBindings[bindingName]["(token)"], "var");
|
}
|
}
|
return;
|
}
|
|
// Check the current scope for unused parameters and issue warnings as
|
// necessary.
|
var params = _current["(params)"];
|
|
var param = params.pop();
|
var unused_opt;
|
|
while (param) {
|
var binding = _current["(bindings)"][param];
|
|
unused_opt = _getUnusedOption(state.funct["(unusedOption)"]);
|
|
// 'undefined' is a special case for the common pattern where `undefined`
|
// is used as a formal parameter name to defend against global
|
// re-assignment, e.g.
|
//
|
// (function(window, undefined) {
|
// })();
|
if (param === "undefined")
|
return;
|
|
if (binding["(unused)"]) {
|
_warnUnused(param, binding["(token)"], "param", state.funct["(unusedOption)"]);
|
} else if (unused_opt === "last-param") {
|
return;
|
}
|
|
param = params.pop();
|
}
|
}
|
|
/**
|
* Find the relevant binding's scope. The owning scope is located by first
|
* inspecting the current scope and then moving "downward" through the stack
|
* of scopes.
|
*
|
* @param {string} bindingName - the value of the identifier
|
*
|
* @returns {Object} - the scope in which the binding was found
|
*/
|
function _getBinding(bindingName) {
|
for (var i = _scopeStack.length - 1 ; i >= 0; --i) {
|
var scopeBindings = _scopeStack[i]["(bindings)"];
|
if (scopeBindings[bindingName]) {
|
return scopeBindings;
|
}
|
}
|
}
|
|
/**
|
* Determine if a given binding name has been referenced within the current
|
* function or any function defined within.
|
*
|
* @param {string} bindingName - the value of the identifier
|
*
|
* @returns {boolean}
|
*/
|
function usedSoFarInCurrentFunction(bindingName) {
|
for (var i = _scopeStack.length - 1; i >= 0; i--) {
|
var current = _scopeStack[i];
|
if (current["(usages)"][bindingName]) {
|
return current["(usages)"][bindingName];
|
}
|
if (current === _currentFunctBody) {
|
break;
|
}
|
}
|
return false;
|
}
|
|
function _checkOuterShadow(bindingName, token) {
|
|
// only check if shadow is outer
|
if (state.option.shadow !== "outer") {
|
return;
|
}
|
|
var isGlobal = _currentFunctBody["(type)"] === "global",
|
isNewFunction = _current["(type)"] === "functionparams";
|
|
var outsideCurrentFunction = !isGlobal;
|
for (var i = 0; i < _scopeStack.length; i++) {
|
var stackItem = _scopeStack[i];
|
|
if (!isNewFunction && _scopeStack[i + 1] === _currentFunctBody) {
|
outsideCurrentFunction = false;
|
}
|
if (outsideCurrentFunction && stackItem["(bindings)"][bindingName]) {
|
warning("W123", token, bindingName);
|
}
|
if (stackItem["(labels)"][bindingName]) {
|
warning("W123", token, bindingName);
|
}
|
}
|
}
|
|
function _latedefWarning(type, bindingName, token) {
|
var isFunction;
|
|
if (state.option.latedef) {
|
isFunction = type === "function" || type === "generator function" ||
|
type === "async function";
|
|
// if either latedef is strict and this is a function
|
// or this is not a function
|
if ((state.option.latedef === true && isFunction) || !isFunction) {
|
warning("W003", token, bindingName);
|
}
|
}
|
}
|
|
var scopeManagerInst = {
|
|
on: function(names, listener) {
|
names.split(" ").forEach(function(name) {
|
emitter.on(name, listener);
|
});
|
},
|
|
isPredefined: function(bindingName) {
|
return !this.has(bindingName) && _.has(_scopeStack[0]["(predefined)"], bindingName);
|
},
|
|
/**
|
* Create a new scope within the current scope. As the topmost value, the
|
* new scope will be interpreted as the current scope until it is
|
* exited--see the `unstack` method.
|
*
|
* @param {string} [type] - The type of the scope. Valid values are
|
* "functionparams", "catchparams" and
|
* "functionouter"
|
*/
|
stack: function(type) {
|
var previousScope = _current;
|
_newScope(type);
|
|
if (!type && previousScope["(type)"] === "functionparams") {
|
|
_current["(isFuncBody)"] = true;
|
_currentFunctBody = _current;
|
}
|
},
|
|
/**
|
* Valldate all binding references and declarations in the current scope
|
* and set the next scope on the stack as the active scope.
|
*/
|
unstack: function() {
|
// jshint proto: true
|
var subScope = _scopeStack.length > 1 ? _scopeStack[_scopeStack.length - 2] : null;
|
var isUnstackingFunctionBody = _current === _currentFunctBody,
|
isUnstackingFunctionParams = _current["(type)"] === "functionparams",
|
isUnstackingFunctionOuter = _current["(type)"] === "functionouter";
|
|
var i, j, isImmutable, isFunction;
|
var currentUsages = _current["(usages)"];
|
var currentBindings = _current["(bindings)"];
|
var usedBindingNameList = Object.keys(currentUsages);
|
|
// See comment, "A note on `__proto__`"
|
/* istanbul ignore if */
|
if (currentUsages.__proto__ && usedBindingNameList.indexOf("__proto__") === -1) {
|
usedBindingNameList.push("__proto__");
|
}
|
|
for (i = 0; i < usedBindingNameList.length; i++) {
|
var usedBindingName = usedBindingNameList[i];
|
|
var usage = currentUsages[usedBindingName];
|
var usedBinding = currentBindings[usedBindingName];
|
if (usedBinding) {
|
var usedBindingType = usedBinding["(type)"];
|
isImmutable = usedBindingType === "const" || usedBindingType === "import";
|
|
if (usedBinding["(useOutsideOfScope)"] && !state.option.funcscope) {
|
var usedTokens = usage["(tokens)"];
|
for (j = 0; j < usedTokens.length; j++) {
|
// Keep the consistency of https://github.com/jshint/jshint/issues/2409
|
if (usedBinding["(function)"] === usedTokens[j]["(function)"]) {
|
error("W038", usedTokens[j], usedBindingName);
|
}
|
}
|
}
|
|
// mark the binding used
|
_current["(bindings)"][usedBindingName]["(unused)"] = false;
|
|
// check for modifying a const
|
if (isImmutable && usage["(modified)"]) {
|
for (j = 0; j < usage["(modified)"].length; j++) {
|
error("E013", usage["(modified)"][j], usedBindingName);
|
}
|
}
|
|
isFunction = usedBindingType === "function" ||
|
usedBindingType === "generator function" ||
|
usedBindingType === "async function";
|
|
// check for re-assigning a function declaration
|
if ((isFunction || usedBindingType === "class") && usage["(reassigned)"]) {
|
for (j = 0; j < usage["(reassigned)"].length; j++) {
|
if (!usage["(reassigned)"][j].ignoreW021) {
|
warning("W021", usage["(reassigned)"][j], usedBindingName, usedBindingType);
|
}
|
}
|
}
|
continue;
|
}
|
|
if (subScope) {
|
var bindingType = this.bindingtype(usedBindingName);
|
isImmutable = bindingType === "const" ||
|
(bindingType === null && _scopeStack[0]["(predefined)"][usedBindingName] === false);
|
if (isUnstackingFunctionOuter && !isImmutable) {
|
if (!state.funct["(outerMutables)"]) {
|
state.funct["(outerMutables)"] = [];
|
}
|
state.funct["(outerMutables)"].push(usedBindingName);
|
}
|
|
// not exiting the global scope, so copy the usage down in case its an out of scope usage
|
if (!subScope["(usages)"][usedBindingName]) {
|
subScope["(usages)"][usedBindingName] = usage;
|
if (isUnstackingFunctionBody) {
|
subScope["(usages)"][usedBindingName]["(onlyUsedSubFunction)"] = true;
|
}
|
} else {
|
var subScopeUsage = subScope["(usages)"][usedBindingName];
|
subScopeUsage["(modified)"] = subScopeUsage["(modified)"].concat(usage["(modified)"]);
|
subScopeUsage["(tokens)"] = subScopeUsage["(tokens)"].concat(usage["(tokens)"]);
|
subScopeUsage["(reassigned)"] =
|
subScopeUsage["(reassigned)"].concat(usage["(reassigned)"]);
|
}
|
} else {
|
// this is exiting global scope, so we finalise everything here - we are at the end of the file
|
if (typeof _current["(predefined)"][usedBindingName] === "boolean") {
|
|
// remove the declared token, so we know it is used
|
delete declared[usedBindingName];
|
|
// note it as used so it can be reported
|
usedPredefinedAndGlobals[usedBindingName] = marker;
|
|
// check for re-assigning a read-only (set to false) predefined
|
if (_current["(predefined)"][usedBindingName] === false && usage["(reassigned)"]) {
|
for (j = 0; j < usage["(reassigned)"].length; j++) {
|
if (!usage["(reassigned)"][j].ignoreW020) {
|
warning("W020", usage["(reassigned)"][j]);
|
}
|
}
|
}
|
}
|
else {
|
// binding usage is not predefined and we have not found a declaration
|
// so report as undeclared
|
for (j = 0; j < usage["(tokens)"].length; j++) {
|
var undefinedToken = usage["(tokens)"][j];
|
// if its not a forgiven undefined (e.g. typof x)
|
if (!undefinedToken.forgiveUndef) {
|
// if undef is on and undef was on when the token was defined
|
if (state.option.undef && !undefinedToken.ignoreUndef) {
|
warning("W117", undefinedToken, usedBindingName);
|
}
|
if (impliedGlobals[usedBindingName]) {
|
impliedGlobals[usedBindingName].line.push(undefinedToken.line);
|
} else {
|
impliedGlobals[usedBindingName] = {
|
name: usedBindingName,
|
line: [undefinedToken.line]
|
};
|
}
|
}
|
}
|
}
|
}
|
}
|
|
// if exiting the global scope, we can warn about declared globals that haven't been used yet
|
if (!subScope) {
|
Object.keys(declared)
|
.forEach(function(bindingNotUsed) {
|
_warnUnused(bindingNotUsed, declared[bindingNotUsed], "var");
|
});
|
}
|
|
// If this is not a function boundary, transfer function-scoped bindings to
|
// the parent block (a rough simulation of variable hoisting). Previously
|
// existing bindings in the parent block should take precedence so that
|
// prior usages are not discarded.
|
if (subScope && !isUnstackingFunctionBody &&
|
!isUnstackingFunctionParams && !isUnstackingFunctionOuter) {
|
var bindingNames = Object.keys(currentBindings);
|
for (i = 0; i < bindingNames.length; i++) {
|
|
var defBindingName = bindingNames[i];
|
var defBinding = currentBindings[defBindingName];
|
|
if (!defBinding["(blockscoped)"] && defBinding["(type)"] !== "exception") {
|
var shadowed = subScope["(bindings)"][defBindingName];
|
|
// Do not overwrite a binding if it exists in the parent scope
|
// because it is shared by adjacent blocks. Copy the `unused`
|
// property so that any references found within the current block
|
// are counted toward that higher-level declaration.
|
if (shadowed) {
|
shadowed["(unused)"] &= defBinding["(unused)"];
|
|
// "Hoist" the variable to the parent block, decorating the binding
|
// so that future references, though technically valid, can be
|
// reported as "out-of-scope" in the absence of the `funcscope`
|
// option.
|
} else {
|
defBinding["(useOutsideOfScope)"] =
|
// Do not warn about out-of-scope usages in the global scope
|
_currentFunctBody["(type)"] !== "global" &&
|
// When a higher scope contains a binding for the binding, the
|
// binding is a re-declaration and should not prompt "used
|
// out-of-scope" warnings.
|
!this.funct.has(defBindingName, { excludeCurrent: true });
|
|
subScope["(bindings)"][defBindingName] = defBinding;
|
}
|
|
delete currentBindings[defBindingName];
|
}
|
}
|
}
|
|
_checkForUnused();
|
|
_scopeStack.pop();
|
if (isUnstackingFunctionBody) {
|
_currentFunctBody = _scopeStack[_.findLastIndex(_scopeStack, function(scope) {
|
// if function or if global (which is at the bottom so it will only return true if we call back)
|
return scope["(isFuncBody)"] || scope["(type)"] === "global";
|
})];
|
}
|
|
_current = subScope;
|
},
|
|
/**
|
* Add a function parameter to the current scope.
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {Token} token
|
* @param {string} [type] - binding type; defaults to "param"
|
*/
|
addParam: function(bindingName, token, type) {
|
type = type || "param";
|
|
if (type === "exception") {
|
// if defined in the current function
|
var previouslyDefinedBindingType = this.funct.bindingtype(bindingName);
|
if (previouslyDefinedBindingType && previouslyDefinedBindingType !== "exception") {
|
// and has not been used yet in the current function scope
|
if (!state.option.node) {
|
warning("W002", state.tokens.next, bindingName);
|
}
|
}
|
|
if (state.isStrict() && (bindingName === "arguments" || bindingName === "eval")) {
|
warning("E008", token);
|
}
|
}
|
|
// The variable was declared in the current scope
|
if (_.has(_current["(bindings)"], bindingName)) {
|
_current["(bindings)"][bindingName].duplicated = true;
|
|
// The variable was declared in an outer scope
|
} else {
|
// if this scope has the variable defined, it's a re-definition error
|
_checkOuterShadow(bindingName, token);
|
|
_current["(bindings)"][bindingName] = {
|
"(type)" : type,
|
"(token)": token,
|
"(unused)": true };
|
|
_current["(params)"].push(bindingName);
|
}
|
|
if (_.has(_current["(usages)"], bindingName)) {
|
var usage = _current["(usages)"][bindingName];
|
// if its in a sub function it is not necessarily an error, just latedef
|
if (usage["(onlyUsedSubFunction)"]) {
|
_latedefWarning(type, bindingName, token);
|
} else {
|
// this is a clear illegal usage, but not a syntax error, so emit a
|
// warning and not an error
|
warning("W003", token, bindingName);
|
}
|
}
|
},
|
|
validateParams: function(isArrow) {
|
var isStrict = state.isStrict();
|
var currentFunctParamScope = _currentFunctBody["(parent)"];
|
// From ECMAScript 2017:
|
//
|
// > 14.1.2Static Semantics: Early Errors
|
// >
|
// > [...]
|
// > - It is a Syntax Error if IsSimpleParameterList of
|
// > FormalParameterList is false and BoundNames of FormalParameterList
|
// > contains any duplicate elements.
|
var isSimple = state.funct['(hasSimpleParams)'];
|
// Method definitions are defined in terms of UniqueFormalParameters, so
|
// they cannot support duplicate parameter names regardless of strict
|
// mode.
|
var isMethod = state.funct["(method)"];
|
|
if (!currentFunctParamScope["(params)"]) {
|
/* istanbul ignore next */
|
return;
|
}
|
|
currentFunctParamScope["(params)"].forEach(function(bindingName) {
|
var binding = currentFunctParamScope["(bindings)"][bindingName];
|
|
if (binding.duplicated) {
|
if (isStrict || isArrow || isMethod || !isSimple) {
|
warning("E011", binding["(token)"], bindingName);
|
} else if (state.option.shadow !== true) {
|
warning("W004", binding["(token)"], bindingName);
|
}
|
}
|
|
if (isStrict && (bindingName === "arguments" || bindingName === "eval")) {
|
warning("E008", binding["(token)"]);
|
}
|
});
|
},
|
|
getUsedOrDefinedGlobals: function() {
|
// jshint proto: true
|
var list = Object.keys(usedPredefinedAndGlobals);
|
|
// See comment, "A note on `__proto__`"
|
/* istanbul ignore if */
|
if (usedPredefinedAndGlobals.__proto__ === marker &&
|
list.indexOf("__proto__") === -1) {
|
list.push("__proto__");
|
}
|
|
return list;
|
},
|
|
/**
|
* Get an array of implied globals
|
*
|
* @returns {Array.<{ name: string, line: Array.<number>}>}
|
*/
|
getImpliedGlobals: function() {
|
// jshint proto: true
|
var values = _.values(impliedGlobals);
|
var hasProto = false;
|
|
// See comment, "A note on `__proto__`"
|
if (impliedGlobals.__proto__) {
|
hasProto = values.some(function(value) {
|
return value.name === "__proto__";
|
});
|
|
/* istanbul ignore if */
|
if (!hasProto) {
|
values.push(impliedGlobals.__proto__);
|
}
|
}
|
|
return values;
|
},
|
|
/**
|
* Get an array of objects describing unused bindings.
|
*
|
* @returns {Array<Object>}
|
*/
|
getUnuseds: function() {
|
return unuseds;
|
},
|
|
/**
|
* Determine if a given name has been defined in the current scope or any
|
* lower scope.
|
*
|
* @param {string} bindingName - the value of the identifier
|
*
|
* @return {boolean}
|
*/
|
has: function(bindingName) {
|
return Boolean(_getBinding(bindingName));
|
},
|
|
/**
|
* Retrieve binding described by `bindingName` or null
|
*
|
* @param {string} bindingName - the value of the identifier
|
*
|
* @returns {string|null} - the type of the binding or `null` if no such
|
* binding exists
|
*/
|
bindingtype: function(bindingName) {
|
var scopeBindings = _getBinding(bindingName);
|
if (scopeBindings) {
|
return scopeBindings[bindingName]["(type)"];
|
}
|
return null;
|
},
|
|
/**
|
* For the exported options, indicating a variable is used outside the file
|
*
|
* @param {string} bindingName - the value of the identifier
|
*/
|
addExported: function(bindingName) {
|
var globalBindings = _scopeStack[0]["(bindings)"];
|
if (_.has(declared, bindingName)) {
|
// remove the declared token, so we know it is used
|
delete declared[bindingName];
|
} else if (_.has(globalBindings, bindingName)) {
|
globalBindings[bindingName]["(unused)"] = false;
|
} else {
|
for (var i = 1; i < _scopeStack.length; i++) {
|
var scope = _scopeStack[i];
|
// if `scope.(type)` is not defined, it is a block scope
|
if (!scope["(type)"]) {
|
if (_.has(scope["(bindings)"], bindingName) &&
|
!scope["(bindings)"][bindingName]["(blockscoped)"]) {
|
scope["(bindings)"][bindingName]["(unused)"] = false;
|
return;
|
}
|
} else {
|
break;
|
}
|
}
|
exported[bindingName] = true;
|
}
|
},
|
|
/**
|
* Mark a binding as "exported" by an ES2015 module
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {object} token
|
*/
|
setExported: function(localName, exportName) {
|
if (exportName) {
|
if (esModuleExports.indexOf(exportName.value) > -1) {
|
error("E069", exportName, exportName.value);
|
}
|
|
esModuleExports.push(exportName.value);
|
}
|
|
if (localName) {
|
this.block.use(localName.value, localName);
|
}
|
},
|
|
/**
|
* Mark a binding as "initialized." This is necessary to enforce the
|
* "temporal dead zone" (TDZ) of block-scoped bindings which are not
|
* hoisted.
|
*
|
* @param {string} bindingName - the value of the identifier
|
*/
|
initialize: function(bindingName) {
|
if (_current["(bindings)"][bindingName]) {
|
_current["(bindings)"][bindingName]["(initialized)"] = true;
|
}
|
},
|
|
/**
|
* Create a new binding and add it to the current scope. Delegates to the
|
* internal `block.add` or `func.add` methods depending on the type.
|
* Produces warnings and errors as necessary.
|
*
|
* @param {string} bindingName
|
* @param {Object} opts
|
* @param {String} opts.type - the type of the binding e.g. "param", "var",
|
* "let, "const", "import", "function",
|
* "generator function", "async function",
|
* "async generator function"
|
* @param {object} opts.token - the token pointing at the declaration
|
* @param {boolean} opts.initialized - whether the binding should be
|
* created in an "initialized" state.
|
*/
|
addbinding: function(bindingName, opts) {
|
|
var type = opts.type;
|
var token = opts.token;
|
var isblockscoped = type === "let" || type === "const" ||
|
type === "class" || type === "import" || type === "generator function" ||
|
type === "async function" || type === "async generator function";
|
var ishoisted = type === "function" || type === "generator function" ||
|
type === "async function" || type === "import";
|
var isexported = (isblockscoped ? _current : _currentFunctBody)["(type)"] === "global" &&
|
_.has(exported, bindingName);
|
|
// outer shadow check (inner is only on non-block scoped)
|
_checkOuterShadow(bindingName, token);
|
|
if (state.isStrict() && (bindingName === "arguments" || bindingName === "eval")) {
|
warning("E008", token);
|
}
|
|
if (isblockscoped) {
|
|
var declaredInCurrentScope = _current["(bindings)"][bindingName];
|
// for block scoped variables, params are seen in the current scope as the root function
|
// scope, so check these too.
|
if (!declaredInCurrentScope && _current === _currentFunctBody &&
|
_current["(type)"] !== "global") {
|
declaredInCurrentScope = !!_currentFunctBody["(parent)"]["(bindings)"][bindingName];
|
}
|
|
// if its not already defined (which is an error, so ignore) and is used in TDZ
|
if (!declaredInCurrentScope && _current["(usages)"][bindingName]) {
|
var usage = _current["(usages)"][bindingName];
|
// if its in a sub function it is not necessarily an error, just latedef
|
if (usage["(onlyUsedSubFunction)"] || ishoisted) {
|
_latedefWarning(type, bindingName, token);
|
} else if (!ishoisted) {
|
// this is a clear illegal usage for block scoped variables
|
warning("E056", token, bindingName, type);
|
}
|
}
|
|
// If this scope has already declared a binding with the same name,
|
// then this represents a redeclaration error if:
|
//
|
// 1. it is a "hoisted" block-scoped binding within a block. For
|
// instance: generator functions may be redeclared in the global
|
// scope but not within block statements
|
// 2. this is not a "hoisted" block-scoped binding
|
if (declaredInCurrentScope &&
|
(!ishoisted || (_current["(type)"] !== "global" || type === "import"))) {
|
warning("E011", token, bindingName);
|
}
|
else if (state.option.shadow === "outer") {
|
|
// if shadow is outer, for block scope we want to detect any shadowing within this function
|
if (scopeManagerInst.funct.has(bindingName)) {
|
warning("W004", token, bindingName);
|
}
|
}
|
|
scopeManagerInst.block.add(
|
bindingName, type, token, !isexported, opts.initialized
|
);
|
|
} else {
|
|
var declaredInCurrentFunctionScope = scopeManagerInst.funct.has(bindingName);
|
|
// check for late definition, ignore if already declared
|
if (!declaredInCurrentFunctionScope && usedSoFarInCurrentFunction(bindingName)) {
|
_latedefWarning(type, bindingName, token);
|
}
|
|
// defining with a var or a function when a block scope variable of the same name
|
// is in scope is an error
|
if (scopeManagerInst.funct.has(bindingName, { onlyBlockscoped: true })) {
|
warning("E011", token, bindingName);
|
} else if (state.option.shadow !== true) {
|
// now since we didn't get any block scope variables, test for var/function
|
// shadowing
|
if (declaredInCurrentFunctionScope && bindingName !== "__proto__") {
|
|
// see https://github.com/jshint/jshint/issues/2400
|
if (_currentFunctBody["(type)"] !== "global") {
|
warning("W004", token, bindingName);
|
}
|
}
|
}
|
|
scopeManagerInst.funct.add(bindingName, type, token, !isexported);
|
|
if (_currentFunctBody["(type)"] === "global" && !state.impliedClosure()) {
|
usedPredefinedAndGlobals[bindingName] = marker;
|
}
|
}
|
},
|
|
funct: {
|
/**
|
* Return the type of the provided binding given certain options
|
*
|
* @param {string} bindingName
|
* @param {Object=} [options]
|
* @param {boolean} [options.onlyBlockscoped] - only include block scoped
|
* bindings
|
* @param {boolean} [options.excludeParams] - exclude the param scope
|
* @param {boolean} [options.excludeCurrent] - exclude the current scope
|
*
|
* @returns {String}
|
*/
|
bindingtype: function(bindingName, options) {
|
var onlyBlockscoped = options && options.onlyBlockscoped;
|
var excludeParams = options && options.excludeParams;
|
var currentScopeIndex = _scopeStack.length - (options && options.excludeCurrent ? 2 : 1);
|
for (var i = currentScopeIndex; i >= 0; i--) {
|
var current = _scopeStack[i];
|
if (current["(bindings)"][bindingName] &&
|
(!onlyBlockscoped || current["(bindings)"][bindingName]["(blockscoped)"])) {
|
return current["(bindings)"][bindingName]["(type)"];
|
}
|
var scopeCheck = excludeParams ? _scopeStack[ i - 1 ] : current;
|
if (scopeCheck && scopeCheck["(type)"] === "functionparams") {
|
return null;
|
}
|
}
|
return null;
|
},
|
|
/**
|
* Determine whether a `break` statement label exists in the function
|
* scope.
|
*
|
* @param {string} labelName - the value of the identifier
|
*
|
* @returns {boolean}
|
*/
|
hasLabel: function(labelName) {
|
for (var i = _scopeStack.length - 1; i >= 0; i--) {
|
var current = _scopeStack[i];
|
|
if (current["(labels)"][labelName]) {
|
return true;
|
}
|
if (current["(type)"] === "functionparams") {
|
return false;
|
}
|
}
|
return false;
|
},
|
|
/**
|
* Determine if a given name has been defined in the current function
|
* scope.
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {object} options - options as supported by the
|
* `funct.bindingtype` method
|
*
|
* @return {boolean}
|
*/
|
has: function(bindingName, options) {
|
return Boolean(this.bindingtype(bindingName, options));
|
},
|
|
/**
|
* Create a new function-scoped binding and add it to the current scope.
|
* See the `block.add` method for coresponding logic to create
|
* block-scoped bindings.
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {string} type - the type of the binding; either "function" or
|
* "var"
|
* @param {object} tok - the token that triggered the definition
|
* @param {boolean} unused - `true` if the binding has not been
|
* referenced
|
*/
|
add: function(bindingName, type, tok, unused) {
|
_current["(bindings)"][bindingName] = {
|
"(type)" : type,
|
"(token)": tok,
|
"(blockscoped)": false,
|
"(function)": _currentFunctBody,
|
"(unused)": unused };
|
}
|
},
|
|
block: {
|
|
/**
|
* Determine whether the current block scope is the global scope.
|
*
|
* @returns Boolean
|
*/
|
isGlobal: function() {
|
return _current["(type)"] === "global";
|
},
|
|
/**
|
* Resolve a reference to a binding and mark the corresponding binding as
|
* "used."
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {object} token - the token value that triggered the reference
|
*/
|
use: function(bindingName, token) {
|
// If the name resolves to a parameter of the current function, then do
|
// not store usage. This is because in cases such as the following:
|
//
|
// function(a) {
|
// var a;
|
// a = a;
|
// }
|
//
|
// the usage of `a` will resolve to the parameter, not to the unset
|
// variable binding.
|
var paramScope = _currentFunctBody["(parent)"];
|
if (paramScope && paramScope["(bindings)"][bindingName] &&
|
paramScope["(bindings)"][bindingName]["(type)"] === "param") {
|
|
// then check its not declared by a block scope variable
|
if (!scopeManagerInst.funct.has(bindingName,
|
{ excludeParams: true, onlyBlockscoped: true })) {
|
paramScope["(bindings)"][bindingName]["(unused)"] = false;
|
}
|
}
|
|
if (token && (state.ignored.W117 || state.option.undef === false)) {
|
token.ignoreUndef = true;
|
}
|
|
_setupUsages(bindingName);
|
|
_current["(usages)"][bindingName]["(onlyUsedSubFunction)"] = false;
|
|
if (token) {
|
token["(function)"] = _currentFunctBody;
|
_current["(usages)"][bindingName]["(tokens)"].push(token);
|
}
|
|
// Block-scoped bindings can't be used within their initializer due to
|
// "temporal dead zone" (TDZ) restrictions.
|
var binding = _current["(bindings)"][bindingName];
|
if (binding && binding["(blockscoped)"] && !binding["(initialized)"]) {
|
error("E056", token, bindingName, binding["(type)"]);
|
}
|
},
|
|
reassign: function(bindingName, token) {
|
token.ignoreW020 = state.ignored.W020;
|
token.ignoreW021 = state.ignored.W021;
|
|
this.modify(bindingName, token);
|
|
_current["(usages)"][bindingName]["(reassigned)"].push(token);
|
},
|
|
modify: function(bindingName, token) {
|
|
_setupUsages(bindingName);
|
|
_current["(usages)"][bindingName]["(onlyUsedSubFunction)"] = false;
|
_current["(usages)"][bindingName]["(modified)"].push(token);
|
},
|
|
/**
|
* Create a new block-scoped binding and add it to the current scope. See
|
* the `funct.add` method for coresponding logic to create
|
* function-scoped bindings.
|
*
|
* @param {string} bindingName - the value of the identifier
|
* @param {string} type - the type of the binding; one of "class",
|
* "const", "function", "import", or "let"
|
* @param {object} tok - the token that triggered the definition
|
* @param {boolean} unused - `true` if the binding has not been
|
* referenced
|
* @param {boolean} initialized - `true` if the binding has been
|
* initialized (as is the case with
|
* bindings created via `import`
|
* declarations)
|
*/
|
add: function(bindingName, type, tok, unused, initialized) {
|
_current["(bindings)"][bindingName] = {
|
"(type)" : type,
|
"(token)": tok,
|
"(initialized)": !!initialized,
|
"(blockscoped)": true,
|
"(unused)": unused };
|
},
|
|
addLabel: function(labelName, opts) {
|
var token = opts.token;
|
if (scopeManagerInst.funct.hasLabel(labelName)) {
|
warning("E011", token, labelName);
|
}
|
else if (state.option.shadow === "outer") {
|
if (scopeManagerInst.funct.has(labelName)) {
|
warning("W004", token, labelName);
|
} else {
|
_checkOuterShadow(labelName, token);
|
}
|
}
|
_current["(labels)"][labelName] = token;
|
}
|
}
|
};
|
return scopeManagerInst;
|
};
|
|
module.exports = scopeManager;
|