123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- "use strict";
- const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
- const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
- function parseOptions(options) {
- let functions = true;
- let classes = true;
- let variables = true;
- let allowNamedExports = false;
- 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;
- allowNamedExports = !!options.allowNamedExports;
- }
- return { functions, classes, variables, allowNamedExports };
- }
- function isInRange(node, location) {
- return node && node.range[0] <= location && location <= node.range[1];
- }
- function isInClassStaticInitializerRange(node, location) {
- return node.body.some(classMember => (
- (
- classMember.type === "StaticBlock" &&
- isInRange(classMember, location)
- ) ||
- (
- classMember.type === "PropertyDefinition" &&
- classMember.static &&
- classMember.value &&
- isInRange(classMember.value, location)
- )
- ));
- }
- function isClassStaticInitializerScope(scope) {
- if (scope.type === "class-static-block") {
- return true;
- }
- if (scope.type === "class-field-initializer") {
-
- const propertyDefinition = scope.block.parent;
- return propertyDefinition.static;
- }
- return false;
- }
- function isFromSeparateExecutionContext(reference) {
- const variable = reference.resolved;
- let scope = reference.from;
-
- while (variable.scope.variableScope !== scope.variableScope) {
- if (isClassStaticInitializerScope(scope.variableScope)) {
- scope = scope.variableScope.upper;
- } else {
- return true;
- }
- }
- return false;
- }
- function isEvaluatedDuringInitialization(reference) {
- if (isFromSeparateExecutionContext(reference)) {
-
- return false;
- }
- const location = reference.identifier.range[1];
- const definition = reference.resolved.defs[0];
- if (definition.type === "ClassName") {
-
- const classDefinition = definition.node;
- return (
- isInRange(classDefinition, location) &&
-
- !isInClassStaticInitializerRange(classDefinition.body, location)
- );
- }
- let node = definition.name.parent;
- 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;
- }
- module.exports = {
- meta: {
- type: "problem",
- docs: {
- description: "Disallow the use of variables before they are defined",
- recommended: false,
- url: "https://eslint.org/docs/latest/rules/no-use-before-define"
- },
- schema: [
- {
- oneOf: [
- {
- enum: ["nofunc"]
- },
- {
- type: "object",
- properties: {
- functions: { type: "boolean" },
- classes: { type: "boolean" },
- variables: { type: "boolean" },
- allowNamedExports: { type: "boolean" }
- },
- additionalProperties: false
- }
- ]
- }
- ],
- messages: {
- usedBeforeDefined: "'{{name}}' was used before it was defined."
- }
- },
- create(context) {
- const options = parseOptions(context.options[0]);
- const sourceCode = context.sourceCode;
-
- function shouldCheck(reference) {
- if (reference.init) {
- return false;
- }
- const { identifier } = reference;
- if (
- options.allowNamedExports &&
- identifier.parent.type === "ExportSpecifier" &&
- identifier.parent.local === identifier
- ) {
- return false;
- }
- const variable = reference.resolved;
- if (!variable || variable.defs.length === 0) {
- return false;
- }
- const definitionType = variable.defs[0].type;
- if (!options.functions && definitionType === "FunctionName") {
- return false;
- }
- if (
- (
- !options.variables && definitionType === "Variable" ||
- !options.classes && definitionType === "ClassName"
- ) &&
-
- isFromSeparateExecutionContext(reference)
- ) {
- return false;
- }
- return true;
- }
-
- function checkReferencesInScope(scope) {
- scope.references.filter(shouldCheck).forEach(reference => {
- const variable = reference.resolved;
- const definitionIdentifier = variable.defs[0].name;
- if (
- reference.identifier.range[1] < definitionIdentifier.range[1] ||
- isEvaluatedDuringInitialization(reference)
- ) {
- context.report({
- node: reference.identifier,
- messageId: "usedBeforeDefined",
- data: reference.identifier
- });
- }
- });
- scope.childScopes.forEach(checkReferencesInScope);
- }
- return {
- Program(node) {
- checkReferencesInScope(sourceCode.getScope(node));
- }
- };
- }
- };
|