mirror of https://github.com/lukechilds/docs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
895 lines
30 KiB
895 lines
30 KiB
"use strict";
|
|
|
|
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 = {};
|
|
|
|
/**
|
|
* Creates a scope manager that handles variables and labels, storing usages
|
|
* and resolving when variables are used and undefined
|
|
*/
|
|
var scopeManager = function(state, predefined, exported, declared) {
|
|
|
|
var _current;
|
|
var _scopeStack = [];
|
|
|
|
function _newScope(type) {
|
|
_current = {
|
|
"(labels)": Object.create(null),
|
|
"(usages)": Object.create(null),
|
|
"(breakLabels)": 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 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(labelName) {
|
|
if (!_current["(usages)"][labelName]) {
|
|
_current["(usages)"][labelName] = {
|
|
"(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
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Checks the current scope for unused identifiers
|
|
*/
|
|
function _checkForUnused() {
|
|
// function params are handled specially
|
|
// assume that parameters are the only thing declared in the param scope
|
|
if (_current["(type)"] === "functionparams") {
|
|
_checkParams();
|
|
return;
|
|
}
|
|
var curentLabels = _current["(labels)"];
|
|
for (var labelName in curentLabels) {
|
|
if (curentLabels[labelName]["(type)"] !== "exception" &&
|
|
curentLabels[labelName]["(unused)"]) {
|
|
_warnUnused(labelName, curentLabels[labelName]["(token)"], "var");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks the current scope for unused parameters
|
|
* Must be called in a function parameter scope
|
|
*/
|
|
function _checkParams() {
|
|
var params = _current["(params)"];
|
|
|
|
if (!params) {
|
|
return;
|
|
}
|
|
|
|
var param = params.pop();
|
|
var unused_opt;
|
|
|
|
while (param) {
|
|
var label = _current["(labels)"][param];
|
|
|
|
unused_opt = _getUnusedOption(state.funct["(unusedOption)"]);
|
|
|
|
// 'undefined' is a special case for (function(window, undefined) { ... })();
|
|
// patterns.
|
|
if (param === "undefined")
|
|
return;
|
|
|
|
if (label["(unused)"]) {
|
|
_warnUnused(param, label["(token)"], "param", state.funct["(unusedOption)"]);
|
|
} else if (unused_opt === "last-param") {
|
|
return;
|
|
}
|
|
|
|
param = params.pop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the relevant label's scope, searching from nearest outwards
|
|
* @returns {Object} the scope the label was found in
|
|
*/
|
|
function _getLabel(labelName) {
|
|
for (var i = _scopeStack.length - 1 ; i >= 0; --i) {
|
|
var scopeLabels = _scopeStack[i]["(labels)"];
|
|
if (scopeLabels[labelName]) {
|
|
return scopeLabels;
|
|
}
|
|
}
|
|
}
|
|
|
|
function usedSoFarInCurrentFunction(labelName) {
|
|
// used so far in this whole function and any sub functions
|
|
for (var i = _scopeStack.length - 1; i >= 0; i--) {
|
|
var current = _scopeStack[i];
|
|
if (current["(usages)"][labelName]) {
|
|
return current["(usages)"][labelName];
|
|
}
|
|
if (current === _currentFunctBody) {
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function _checkOuterShadow(labelName, 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["(labels)"][labelName]) {
|
|
warning("W123", token, labelName);
|
|
}
|
|
if (stackItem["(breakLabels)"][labelName]) {
|
|
warning("W123", token, labelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
function _latedefWarning(type, labelName, token) {
|
|
var isFunction;
|
|
|
|
if (state.option.latedef) {
|
|
isFunction = type === "function" || type === "generator 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, labelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
var scopeManagerInst = {
|
|
|
|
on: function(names, listener) {
|
|
names.split(" ").forEach(function(name) {
|
|
emitter.on(name, listener);
|
|
});
|
|
},
|
|
|
|
isPredefined: function(labelName) {
|
|
return !this.has(labelName) && _.has(_scopeStack[0]["(predefined)"], labelName);
|
|
},
|
|
|
|
/**
|
|
* Tell the manager we are entering a new block of code
|
|
* @param {string} [type] - The type of the block. 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;
|
|
}
|
|
},
|
|
|
|
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;
|
|
var currentUsages = _current["(usages)"];
|
|
var currentLabels = _current["(labels)"];
|
|
var usedLabelNameList = Object.keys(currentUsages);
|
|
|
|
/* istanbul ignore if */
|
|
if (currentUsages.__proto__ && usedLabelNameList.indexOf("__proto__") === -1) {
|
|
usedLabelNameList.push("__proto__");
|
|
}
|
|
|
|
for (i = 0; i < usedLabelNameList.length; i++) {
|
|
var usedLabelName = usedLabelNameList[i];
|
|
|
|
var usage = currentUsages[usedLabelName];
|
|
var usedLabel = currentLabels[usedLabelName];
|
|
if (usedLabel) {
|
|
var usedLabelType = usedLabel["(type)"];
|
|
isImmutable = usedLabelType === "const" || usedLabelType === "import";
|
|
|
|
if (usedLabel["(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 (usedLabel["(function)"] === usedTokens[j]["(function)"]) {
|
|
error("W038", usedTokens[j], usedLabelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
// mark the label used
|
|
_current["(labels)"][usedLabelName]["(unused)"] = false;
|
|
|
|
// check for modifying a const
|
|
if (isImmutable && usage["(modified)"]) {
|
|
for (j = 0; j < usage["(modified)"].length; j++) {
|
|
error("E013", usage["(modified)"][j], usedLabelName);
|
|
}
|
|
}
|
|
|
|
// check for re-assigning a function declaration
|
|
if ((usedLabelType === "function" || usedLabelType === "generator function" ||
|
|
usedLabelType === "class") && usage["(reassigned)"]) {
|
|
for (j = 0; j < usage["(reassigned)"].length; j++) {
|
|
if (!usage["(reassigned)"][j].ignoreW021) {
|
|
warning("W021", usage["(reassigned)"][j], usedLabelName, usedLabelType);
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (subScope) {
|
|
var labelType = this.labeltype(usedLabelName);
|
|
isImmutable = labelType === "const" ||
|
|
(labelType === null && _scopeStack[0]["(predefined)"][usedLabelName] === false);
|
|
if (isUnstackingFunctionOuter && !isImmutable) {
|
|
if (!state.funct["(outerMutables)"]) {
|
|
state.funct["(outerMutables)"] = [];
|
|
}
|
|
state.funct["(outerMutables)"].push(usedLabelName);
|
|
}
|
|
|
|
// not exiting the global scope, so copy the usage down in case its an out of scope usage
|
|
if (!subScope["(usages)"][usedLabelName]) {
|
|
subScope["(usages)"][usedLabelName] = usage;
|
|
if (isUnstackingFunctionBody) {
|
|
subScope["(usages)"][usedLabelName]["(onlyUsedSubFunction)"] = true;
|
|
}
|
|
} else {
|
|
var subScopeUsage = subScope["(usages)"][usedLabelName];
|
|
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)"][usedLabelName] === "boolean") {
|
|
|
|
// remove the declared token, so we know it is used
|
|
delete declared[usedLabelName];
|
|
|
|
// note it as used so it can be reported
|
|
usedPredefinedAndGlobals[usedLabelName] = marker;
|
|
|
|
// check for re-assigning a read-only (set to false) predefined
|
|
if (_current["(predefined)"][usedLabelName] === false && usage["(reassigned)"]) {
|
|
for (j = 0; j < usage["(reassigned)"].length; j++) {
|
|
if (!usage["(reassigned)"][j].ignoreW020) {
|
|
warning("W020", usage["(reassigned)"][j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// label 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, usedLabelName);
|
|
}
|
|
if (impliedGlobals[usedLabelName]) {
|
|
impliedGlobals[usedLabelName].line.push(undefinedToken.line);
|
|
} else {
|
|
impliedGlobals[usedLabelName] = {
|
|
name: usedLabelName,
|
|
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(labelNotUsed) {
|
|
_warnUnused(labelNotUsed, declared[labelNotUsed], "var");
|
|
});
|
|
}
|
|
|
|
// If this is not a function boundary, transfer function-scoped labels to
|
|
// the parent block (a rough simulation of variable hoisting). Previously
|
|
// existing labels in the parent block should take precedence so that things and stuff.
|
|
if (subScope && !isUnstackingFunctionBody &&
|
|
!isUnstackingFunctionParams && !isUnstackingFunctionOuter) {
|
|
var labelNames = Object.keys(currentLabels);
|
|
for (i = 0; i < labelNames.length; i++) {
|
|
|
|
var defLabelName = labelNames[i];
|
|
var defLabel = currentLabels[defLabelName];
|
|
|
|
if (!defLabel["(blockscoped)"] && defLabel["(type)"] !== "exception") {
|
|
var shadowed = subScope["(labels)"][defLabelName];
|
|
|
|
// Do not overwrite a label 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)"] &= defLabel["(unused)"];
|
|
|
|
// "Hoist" the variable to the parent block, decorating the label
|
|
// so that future references, though technically valid, can be
|
|
// reported as "out-of-scope" in the absence of the `funcscope`
|
|
// option.
|
|
} else {
|
|
defLabel["(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 label, the
|
|
// label is a re-declaration and should not prompt "used
|
|
// out-of-scope" warnings.
|
|
!this.funct.has(defLabelName, { excludeCurrent: true });
|
|
|
|
subScope["(labels)"][defLabelName] = defLabel;
|
|
}
|
|
|
|
delete currentLabels[defLabelName];
|
|
}
|
|
}
|
|
}
|
|
|
|
_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 param to the current scope
|
|
* @param {string} labelName
|
|
* @param {Token} token
|
|
* @param {string} [type="param"] param type
|
|
*/
|
|
addParam: function(labelName, token, type) {
|
|
type = type || "param";
|
|
|
|
if (type === "exception") {
|
|
// if defined in the current function
|
|
var previouslyDefinedLabelType = this.funct.labeltype(labelName);
|
|
if (previouslyDefinedLabelType && previouslyDefinedLabelType !== "exception") {
|
|
// and has not been used yet in the current function scope
|
|
if (!state.option.node) {
|
|
warning("W002", state.tokens.next, labelName);
|
|
}
|
|
}
|
|
|
|
if (state.isStrict() && (labelName === "arguments" || labelName === "eval")) {
|
|
warning("E008", token);
|
|
}
|
|
}
|
|
|
|
// The variable was declared in the current scope
|
|
if (_.has(_current["(labels)"], labelName)) {
|
|
_current["(labels)"][labelName].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(labelName, token);
|
|
|
|
_current["(labels)"][labelName] = {
|
|
"(type)" : type,
|
|
"(token)": token,
|
|
"(unused)": true };
|
|
|
|
_current["(params)"].push(labelName);
|
|
}
|
|
|
|
if (_.has(_current["(usages)"], labelName)) {
|
|
var usage = _current["(usages)"][labelName];
|
|
// if its in a sub function it is not necessarily an error, just latedef
|
|
if (usage["(onlyUsedSubFunction)"]) {
|
|
_latedefWarning(type, labelName, token);
|
|
} else {
|
|
// this is a clear illegal usage for block scoped variables
|
|
warning("E056", token, labelName, type);
|
|
}
|
|
}
|
|
},
|
|
|
|
validateParams: function(isArrow) {
|
|
var isStrict = state.isStrict();
|
|
var currentFunctParamScope = _currentFunctBody["(parent)"];
|
|
|
|
if (!currentFunctParamScope["(params)"]) {
|
|
return;
|
|
}
|
|
|
|
currentFunctParamScope["(params)"].forEach(function(labelName) {
|
|
var label = currentFunctParamScope["(labels)"][labelName];
|
|
|
|
if (label.duplicated) {
|
|
if (isStrict || isArrow) {
|
|
warning("E011", label["(token)"], labelName);
|
|
} else if (state.option.shadow !== true) {
|
|
warning("W004", label["(token)"], labelName);
|
|
}
|
|
}
|
|
|
|
if (isStrict && (labelName === "arguments" || labelName === "eval")) {
|
|
warning("E008", label["(token)"]);
|
|
}
|
|
});
|
|
},
|
|
|
|
getUsedOrDefinedGlobals: function() {
|
|
// jshint proto: true
|
|
var list = Object.keys(usedPredefinedAndGlobals);
|
|
|
|
// If `__proto__` is used as a global variable name, its entry in the
|
|
// lookup table may not be enumerated by `Object.keys` (depending on the
|
|
// environment).
|
|
/* istanbul ignore if */
|
|
if (usedPredefinedAndGlobals.__proto__ === marker &&
|
|
list.indexOf("__proto__") === -1) {
|
|
list.push("__proto__");
|
|
}
|
|
|
|
return list;
|
|
},
|
|
|
|
/**
|
|
* Gets an array of implied globals
|
|
* @returns {Array.<{ name: string, line: Array.<number>}>}
|
|
*/
|
|
getImpliedGlobals: function() {
|
|
// jshint proto: true
|
|
var values = _.values(impliedGlobals);
|
|
var hasProto = false;
|
|
|
|
// If `__proto__` is an implied global variable, its entry in the lookup
|
|
// table may not be enumerated by `_.values` (depending on the
|
|
// environment).
|
|
if (impliedGlobals.__proto__) {
|
|
hasProto = values.some(function(value) {
|
|
return value.name === "__proto__";
|
|
});
|
|
|
|
/* istanbul ignore if */
|
|
if (!hasProto) {
|
|
values.push(impliedGlobals.__proto__);
|
|
}
|
|
}
|
|
|
|
return values;
|
|
},
|
|
|
|
/**
|
|
* Returns a list of unused variables
|
|
* @returns {Array}
|
|
*/
|
|
getUnuseds: function() {
|
|
return unuseds;
|
|
},
|
|
|
|
has: function(labelName) {
|
|
return Boolean(_getLabel(labelName));
|
|
},
|
|
|
|
labeltype: function(labelName) {
|
|
// returns a labels type or null if not present
|
|
var scopeLabels = _getLabel(labelName);
|
|
if (scopeLabels) {
|
|
return scopeLabels[labelName]["(type)"];
|
|
}
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* for the exported options, indicating a variable is used outside the file
|
|
*/
|
|
addExported: function(labelName) {
|
|
var globalLabels = _scopeStack[0]["(labels)"];
|
|
if (_.has(declared, labelName)) {
|
|
// remove the declared token, so we know it is used
|
|
delete declared[labelName];
|
|
} else if (_.has(globalLabels, labelName)) {
|
|
globalLabels[labelName]["(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["(labels)"], labelName) &&
|
|
!scope["(labels)"][labelName]["(blockscoped)"]) {
|
|
scope["(labels)"][labelName]["(unused)"] = false;
|
|
return;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
exported[labelName] = true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Mark an indentifier as es6 module exported
|
|
*/
|
|
setExported: function(labelName, token) {
|
|
this.block.use(labelName, token);
|
|
},
|
|
|
|
initialize: function(labelName) {
|
|
if (_current["(labels)"][labelName]) {
|
|
_current["(labels)"][labelName]["(initialized)"] = true;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* adds an indentifier to the relevant current scope and creates warnings/errors as necessary
|
|
* @param {string} labelName
|
|
* @param {Object} opts
|
|
* @param {String} opts.type - the type of the label e.g. "param", "var", "let, "const", "import", "function", "generator function"
|
|
* @param {Token} opts.token - the token pointing at the declaration
|
|
* @param {boolean} opts.initialized - whether the binding should be created in an "initialized" state.
|
|
*/
|
|
addlabel: function(labelName, opts) {
|
|
|
|
var type = opts.type;
|
|
var token = opts.token;
|
|
var isblockscoped = type === "let" || type === "const" ||
|
|
type === "class" || type === "import" || type === "generator function";
|
|
var ishoisted = type === "function" || type === "generator function" ||
|
|
type === "import";
|
|
var isexported = (isblockscoped ? _current : _currentFunctBody)["(type)"] === "global" &&
|
|
_.has(exported, labelName);
|
|
|
|
// outer shadow check (inner is only on non-block scoped)
|
|
_checkOuterShadow(labelName, token);
|
|
|
|
if (state.isStrict() && (labelName === "arguments" || labelName === "eval")) {
|
|
warning("E008", token);
|
|
}
|
|
|
|
if (isblockscoped) {
|
|
|
|
var declaredInCurrentScope = _current["(labels)"][labelName];
|
|
// 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)"]["(labels)"][labelName];
|
|
}
|
|
|
|
// if its not already defined (which is an error, so ignore) and is used in TDZ
|
|
if (!declaredInCurrentScope && _current["(usages)"][labelName]) {
|
|
var usage = _current["(usages)"][labelName];
|
|
// if its in a sub function it is not necessarily an error, just latedef
|
|
if (usage["(onlyUsedSubFunction)"] || ishoisted) {
|
|
_latedefWarning(type, labelName, token);
|
|
} else if (!ishoisted) {
|
|
// this is a clear illegal usage for block scoped variables
|
|
warning("E056", token, labelName, 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, labelName);
|
|
}
|
|
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(labelName)) {
|
|
warning("W004", token, labelName);
|
|
}
|
|
}
|
|
|
|
scopeManagerInst.block.add(
|
|
labelName, type, token, !isexported, opts.initialized
|
|
);
|
|
|
|
} else {
|
|
|
|
var declaredInCurrentFunctionScope = scopeManagerInst.funct.has(labelName);
|
|
|
|
// check for late definition, ignore if already declared
|
|
if (!declaredInCurrentFunctionScope && usedSoFarInCurrentFunction(labelName)) {
|
|
_latedefWarning(type, labelName, 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(labelName, { onlyBlockscoped: true })) {
|
|
warning("E011", token, labelName);
|
|
} else if (state.option.shadow !== true) {
|
|
// now since we didn't get any block scope variables, test for var/function
|
|
// shadowing
|
|
if (declaredInCurrentFunctionScope && labelName !== "__proto__") {
|
|
|
|
// see https://github.com/jshint/jshint/issues/2400
|
|
if (_currentFunctBody["(type)"] !== "global") {
|
|
warning("W004", token, labelName);
|
|
}
|
|
}
|
|
}
|
|
|
|
scopeManagerInst.funct.add(labelName, type, token, !isexported);
|
|
|
|
if (_currentFunctBody["(type)"] === "global" && !state.impliedClosure()) {
|
|
usedPredefinedAndGlobals[labelName] = marker;
|
|
}
|
|
}
|
|
},
|
|
|
|
funct: {
|
|
/**
|
|
* Returns the label type given certain options
|
|
* @param labelName
|
|
* @param {Object=} options
|
|
* @param {Boolean=} options.onlyBlockscoped - only include block scoped labels
|
|
* @param {Boolean=} options.excludeParams - exclude the param scope
|
|
* @param {Boolean=} options.excludeCurrent - exclude the current scope
|
|
* @returns {String}
|
|
*/
|
|
labeltype: function(labelName, 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["(labels)"][labelName] &&
|
|
(!onlyBlockscoped || current["(labels)"][labelName]["(blockscoped)"])) {
|
|
return current["(labels)"][labelName]["(type)"];
|
|
}
|
|
var scopeCheck = excludeParams ? _scopeStack[ i - 1 ] : current;
|
|
if (scopeCheck && scopeCheck["(type)"] === "functionparams") {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
/**
|
|
* Returns if a break label exists in the function scope
|
|
* @param {string} labelName
|
|
* @returns {boolean}
|
|
*/
|
|
hasBreakLabel: function(labelName) {
|
|
for (var i = _scopeStack.length - 1; i >= 0; i--) {
|
|
var current = _scopeStack[i];
|
|
|
|
if (current["(breakLabels)"][labelName]) {
|
|
return true;
|
|
}
|
|
if (current["(type)"] === "functionparams") {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
/**
|
|
* Returns if the label is in the current function scope
|
|
* See scopeManager.funct.labelType for options
|
|
*/
|
|
has: function(labelName, options) {
|
|
return Boolean(this.labeltype(labelName, options));
|
|
},
|
|
|
|
/**
|
|
* Adds a new function scoped variable
|
|
* see block.add for block scoped
|
|
*/
|
|
add: function(labelName, type, tok, unused) {
|
|
_current["(labels)"][labelName] = {
|
|
"(type)" : type,
|
|
"(token)": tok,
|
|
"(blockscoped)": false,
|
|
"(function)": _currentFunctBody,
|
|
"(unused)": unused };
|
|
}
|
|
},
|
|
|
|
block: {
|
|
|
|
/**
|
|
* is the current block global?
|
|
* @returns Boolean
|
|
*/
|
|
isGlobal: function() {
|
|
return _current["(type)"] === "global";
|
|
},
|
|
|
|
use: function(labelName, token) {
|
|
|
|
// if resolves to current function params, then do not store usage just resolve
|
|
// this is because function(a) { var a; a = a; } will resolve to the param, not
|
|
// to the unset var
|
|
// first check the param is used
|
|
var paramScope = _currentFunctBody["(parent)"];
|
|
if (paramScope && paramScope["(labels)"][labelName] &&
|
|
paramScope["(labels)"][labelName]["(type)"] === "param") {
|
|
|
|
// then check its not declared by a block scope variable
|
|
if (!scopeManagerInst.funct.has(labelName,
|
|
{ excludeParams: true, onlyBlockscoped: true })) {
|
|
paramScope["(labels)"][labelName]["(unused)"] = false;
|
|
}
|
|
}
|
|
|
|
if (token && (state.ignored.W117 || state.option.undef === false)) {
|
|
token.ignoreUndef = true;
|
|
}
|
|
|
|
_setupUsages(labelName);
|
|
|
|
_current["(usages)"][labelName]["(onlyUsedSubFunction)"] = false;
|
|
|
|
if (token) {
|
|
token["(function)"] = _currentFunctBody;
|
|
_current["(usages)"][labelName]["(tokens)"].push(token);
|
|
}
|
|
|
|
// blockscoped vars can't be used within their initializer (TDZ)
|
|
var label = _current["(labels)"][labelName];
|
|
if (label && label["(blockscoped)"] && !label["(initialized)"]) {
|
|
error("E056", token, labelName, label["(type)"]);
|
|
}
|
|
},
|
|
|
|
reassign: function(labelName, token) {
|
|
token.ignoreW020 = state.ignored.W020;
|
|
token.ignoreW021 = state.ignored.W021;
|
|
|
|
this.modify(labelName, token);
|
|
|
|
_current["(usages)"][labelName]["(reassigned)"].push(token);
|
|
},
|
|
|
|
modify: function(labelName, token) {
|
|
|
|
_setupUsages(labelName);
|
|
|
|
_current["(usages)"][labelName]["(onlyUsedSubFunction)"] = false;
|
|
_current["(usages)"][labelName]["(modified)"].push(token);
|
|
},
|
|
|
|
/**
|
|
* Adds a new variable
|
|
*/
|
|
add: function(labelName, type, tok, unused, initialized) {
|
|
_current["(labels)"][labelName] = {
|
|
"(type)" : type,
|
|
"(token)": tok,
|
|
"(initialized)": !!initialized,
|
|
"(blockscoped)": true,
|
|
"(unused)": unused };
|
|
},
|
|
|
|
addBreakLabel: function(labelName, opts) {
|
|
var token = opts.token;
|
|
if (scopeManagerInst.funct.hasBreakLabel(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["(breakLabels)"][labelName] = token;
|
|
}
|
|
}
|
|
};
|
|
return scopeManagerInst;
|
|
};
|
|
|
|
module.exports = scopeManager;
|
|
|