summaryrefslogtreecommitdiff
path: root/tools/node_modules/eslint/lib/rules/no-use-before-define.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/node_modules/eslint/lib/rules/no-use-before-define.js')
-rw-r--r--tools/node_modules/eslint/lib/rules/no-use-before-define.js266
1 files changed, 266 insertions, 0 deletions
diff --git a/tools/node_modules/eslint/lib/rules/no-use-before-define.js b/tools/node_modules/eslint/lib/rules/no-use-before-define.js
new file mode 100644
index 0000000000..ada01815d9
--- /dev/null
+++ b/tools/node_modules/eslint/lib/rules/no-use-before-define.js
@@ -0,0 +1,266 @@
+/**
+ * @fileoverview Rule to flag use of variables before they are defined
+ * @author Ilya Volodin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/;
+const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/;
+
+/**
+ * Parses a given value as options.
+ *
+ * @param {any} options - A value to parse.
+ * @returns {Object} The parsed options.
+ */
+function parseOptions(options) {
+ let functions = true;
+ let classes = true;
+ let variables = true;
+
+ if (typeof options === "string") {
+ functions = (options !== "nofunc");
+ } else if (typeof options === "object" && options !== null) {
+ functions = options.functions !== false;
+ classes = options.classes !== false;
+ variables = options.variables !== false;
+ }
+
+ return { functions, classes, variables };
+}
+
+/**
+ * Checks whether or not a given variable is a function declaration.
+ *
+ * @param {eslint-scope.Variable} variable - A variable to check.
+ * @returns {boolean} `true` if the variable is a function declaration.
+ */
+function isFunction(variable) {
+ return variable.defs[0].type === "FunctionName";
+}
+
+/**
+ * Checks whether or not a given variable is a class declaration in an upper function scope.
+ *
+ * @param {eslint-scope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
+ * @returns {boolean} `true` if the variable is a class declaration.
+ */
+function isOuterClass(variable, reference) {
+ return (
+ variable.defs[0].type === "ClassName" &&
+ variable.scope.variableScope !== reference.from.variableScope
+ );
+}
+
+/**
+ * Checks whether or not a given variable is a variable declaration in an upper function scope.
+ * @param {eslint-scope.Variable} variable - A variable to check.
+ * @param {eslint-scope.Reference} reference - A reference to check.
+ * @returns {boolean} `true` if the variable is a variable declaration.
+ */
+function isOuterVariable(variable, reference) {
+ return (
+ variable.defs[0].type === "Variable" &&
+ variable.scope.variableScope !== reference.from.variableScope
+ );
+}
+
+/**
+ * Checks whether or not a given location is inside of the range of a given node.
+ *
+ * @param {ASTNode} node - An node to check.
+ * @param {number} location - A location to check.
+ * @returns {boolean} `true` if the location is inside of the range of the node.
+ */
+function isInRange(node, location) {
+ return node && node.range[0] <= location && location <= node.range[1];
+}
+
+/**
+ * Checks whether or not a given reference is inside of the initializers of a given variable.
+ *
+ * This returns `true` in the following cases:
+ *
+ * var a = a
+ * var [a = a] = list
+ * var {a = a} = obj
+ * for (var a in a) {}
+ * for (var a of a) {}
+ *
+ * @param {Variable} variable - A variable to check.
+ * @param {Reference} reference - A reference to check.
+ * @returns {boolean} `true` if the reference is inside of the initializers.
+ */
+function isInInitializer(variable, reference) {
+ if (variable.scope !== reference.from) {
+ return false;
+ }
+
+ let node = variable.identifiers[0].parent;
+ const location = reference.identifier.range[1];
+
+ while (node) {
+ if (node.type === "VariableDeclarator") {
+ if (isInRange(node.init, location)) {
+ return true;
+ }
+ if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
+ isInRange(node.parent.parent.right, location)
+ ) {
+ return true;
+ }
+ break;
+ } else if (node.type === "AssignmentPattern") {
+ if (isInRange(node.right, location)) {
+ return true;
+ }
+ } else if (SENTINEL_TYPE.test(node.type)) {
+ break;
+ }
+
+ node = node.parent;
+ }
+
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "disallow the use of variables before they are defined",
+ category: "Variables",
+ recommended: false
+ },
+
+ schema: [
+ {
+ oneOf: [
+ {
+ enum: ["nofunc"]
+ },
+ {
+ type: "object",
+ properties: {
+ functions: { type: "boolean" },
+ classes: { type: "boolean" },
+ variables: { type: "boolean" }
+ },
+ additionalProperties: false
+ }
+ ]
+ }
+ ]
+ },
+
+ create(context) {
+ const options = parseOptions(context.options[0]);
+
+ /**
+ * Determines whether a given use-before-define case should be reported according to the options.
+ * @param {eslint-scope.Variable} variable The variable that gets used before being defined
+ * @param {eslint-scope.Reference} reference The reference to the variable
+ * @returns {boolean} `true` if the usage should be reported
+ */
+ function isForbidden(variable, reference) {
+ if (isFunction(variable)) {
+ return options.functions;
+ }
+ if (isOuterClass(variable, reference)) {
+ return options.classes;
+ }
+ if (isOuterVariable(variable, reference)) {
+ return options.variables;
+ }
+ return true;
+ }
+
+ /**
+ * Finds and validates all variables in a given scope.
+ * @param {Scope} scope The scope object.
+ * @returns {void}
+ * @private
+ */
+ function findVariablesInScope(scope) {
+ scope.references.forEach(reference => {
+ const variable = reference.resolved;
+
+ /*
+ * Skips when the reference is:
+ * - initialization's.
+ * - referring to an undefined variable.
+ * - referring to a global environment variable (there're no identifiers).
+ * - located preceded by the variable (except in initializers).
+ * - allowed by options.
+ */
+ if (reference.init ||
+ !variable ||
+ variable.identifiers.length === 0 ||
+ (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
+ !isForbidden(variable, reference)
+ ) {
+ return;
+ }
+
+ // Reports.
+ context.report({
+ node: reference.identifier,
+ message: "'{{name}}' was used before it was defined.",
+ data: reference.identifier
+ });
+ });
+ }
+
+ /**
+ * Validates variables inside of a node's scope.
+ * @param {ASTNode} node The node to check.
+ * @returns {void}
+ * @private
+ */
+ function findVariables() {
+ const scope = context.getScope();
+
+ findVariablesInScope(scope);
+ }
+
+ const ruleDefinition = {
+ "Program:exit"(node) {
+ const scope = context.getScope(),
+ ecmaFeatures = context.parserOptions.ecmaFeatures || {};
+
+ findVariablesInScope(scope);
+
+ // both Node.js and Modules have an extra scope
+ if (ecmaFeatures.globalReturn || node.sourceType === "module") {
+ findVariablesInScope(scope.childScopes[0]);
+ }
+ }
+ };
+
+ if (context.parserOptions.ecmaVersion >= 6) {
+ ruleDefinition["BlockStatement:exit"] =
+ ruleDefinition["SwitchStatement:exit"] = findVariables;
+
+ ruleDefinition["ArrowFunctionExpression:exit"] = function(node) {
+ if (node.body.type !== "BlockStatement") {
+ findVariables();
+ }
+ };
+ } else {
+ ruleDefinition["FunctionExpression:exit"] =
+ ruleDefinition["FunctionDeclaration:exit"] =
+ ruleDefinition["ArrowFunctionExpression:exit"] = findVariables;
+ }
+
+ return ruleDefinition;
+ }
+};