blob: 0f25f4912c8a1dbef8c8c0a34c74e206dca393d6 [file] [log] [blame]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
const eslint_visitor_keys_1 = require("eslint-visitor-keys");
const scope_manager_1 = require("./scope/scope-manager");
const typescript_estree_1 = require("@typescript-eslint/typescript-estree");
/**
* Define the override function of `Scope#__define` for global augmentation.
* @param {Function} define The original Scope#__define method.
* @returns {Function} The override function.
*/
function overrideDefine(define) {
return function (node, definition) {
define.call(this, node, definition);
// Set `variable.eslintUsed` to tell ESLint that the variable is exported.
const variable = 'name' in node &&
typeof node.name === 'string' &&
this.set.get(node.name);
if (variable) {
variable.eslintUsed = true;
}
};
}
class PatternVisitor extends experimental_utils_1.TSESLintScope.PatternVisitor {
constructor(options, rootPattern, callback) {
super(options, rootPattern, callback);
}
Identifier(node) {
super.Identifier(node);
if (node.decorators) {
this.rightHandNodes.push(...node.decorators);
}
if (node.typeAnnotation) {
this.rightHandNodes.push(node.typeAnnotation);
}
}
ArrayPattern(node) {
node.elements.forEach(this.visit, this);
if (node.decorators) {
this.rightHandNodes.push(...node.decorators);
}
if (node.typeAnnotation) {
this.rightHandNodes.push(node.typeAnnotation);
}
}
ObjectPattern(node) {
node.properties.forEach(this.visit, this);
if (node.decorators) {
this.rightHandNodes.push(...node.decorators);
}
if (node.typeAnnotation) {
this.rightHandNodes.push(node.typeAnnotation);
}
}
RestElement(node) {
super.RestElement(node);
if (node.decorators) {
this.rightHandNodes.push(...node.decorators);
}
if (node.typeAnnotation) {
this.rightHandNodes.push(node.typeAnnotation);
}
}
TSParameterProperty(node) {
this.visit(node.parameter);
if (node.decorators) {
this.rightHandNodes.push(...node.decorators);
}
}
}
class Referencer extends experimental_utils_1.TSESLintScope.Referencer {
constructor(options, scopeManager) {
super(options, scopeManager);
this.typeMode = false;
}
/**
* Override to use PatternVisitor we overrode.
* @param node The Identifier node to visit.
* @param [options] The flag to visit right-hand side nodes.
* @param callback The callback function for left-hand side nodes.
*/
visitPattern(node, options, callback) {
if (!node) {
return;
}
if (typeof options === 'function') {
callback = options;
options = { processRightHandNodes: false };
}
const visitor = new PatternVisitor(this.options, node, callback);
visitor.visit(node);
if (options.processRightHandNodes) {
visitor.rightHandNodes.forEach(this.visit, this);
}
}
/**
* Override.
* Visit `node.typeParameters` and `node.returnType` additionally to find `typeof` expressions.
* @param node The function node to visit.
*/
visitFunction(node) {
const { type, id, typeParameters, params, returnType, body } = node;
const scopeManager = this.scopeManager;
const upperScope = this.currentScope();
// Process the name.
if (type === experimental_utils_1.AST_NODE_TYPES.FunctionDeclaration && id) {
upperScope.__define(id, new experimental_utils_1.TSESLintScope.Definition('FunctionName', id, node, null, null, null));
// Remove overload definition to avoid confusion of no-redeclare rule.
const { defs, identifiers } = upperScope.set.get(id.name);
for (let i = 0; i < defs.length; ++i) {
const def = defs[i];
if (def.type === 'FunctionName' &&
def.node.type === experimental_utils_1.AST_NODE_TYPES.TSDeclareFunction) {
defs.splice(i, 1);
identifiers.splice(i, 1);
break;
}
}
}
else if (type === experimental_utils_1.AST_NODE_TYPES.FunctionExpression && id) {
scopeManager.__nestFunctionExpressionNameScope(node);
}
// Open the function scope.
scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
const innerScope = this.currentScope();
// Process the type parameters
this.visit(typeParameters);
// Process parameter declarations.
for (let i = 0; i < params.length; ++i) {
this.visitPattern(params[i], { processRightHandNodes: true }, (pattern, info) => {
if (pattern.type !== experimental_utils_1.AST_NODE_TYPES.Identifier ||
pattern.name !== 'this') {
innerScope.__define(pattern, new experimental_utils_1.TSESLintScope.ParameterDefinition(pattern, node, i, info.rest));
this.referencingDefaultValue(pattern, info.assignments, null, true);
}
});
}
// Process the return type.
this.visit(returnType);
// Process the body.
if (body && body.type === experimental_utils_1.AST_NODE_TYPES.BlockStatement) {
this.visitChildren(body);
}
else {
this.visit(body);
}
// Close the function scope.
this.close(node);
}
/**
* Override.
* Visit decorators.
* @param node The class node to visit.
*/
visitClass(node) {
this.visitDecorators(node.decorators);
const upperTypeMode = this.typeMode;
this.typeMode = true;
if (node.superTypeParameters) {
this.visit(node.superTypeParameters);
}
if (node.implements) {
node.implements.forEach(this.visit, this);
}
this.typeMode = upperTypeMode;
super.visitClass(node);
}
/**
* Visit typeParameters.
* @param node The node to visit.
*/
visitTypeParameters(node) {
if (node.typeParameters) {
const upperTypeMode = this.typeMode;
this.typeMode = true;
this.visit(node.typeParameters);
this.typeMode = upperTypeMode;
}
}
/**
* Override.
*/
JSXOpeningElement(node) {
this.visit(node.name);
this.visitTypeParameters(node);
node.attributes.forEach(this.visit, this);
}
/**
* Override.
* Don't create the reference object in the type mode.
* @param node The Identifier node to visit.
*/
Identifier(node) {
this.visitDecorators(node.decorators);
if (!this.typeMode) {
super.Identifier(node);
}
this.visit(node.typeAnnotation);
}
/**
* Override.
* Visit decorators.
* @param node The MethodDefinition node to visit.
*/
MethodDefinition(node) {
this.visitDecorators(node.decorators);
super.MethodDefinition(node);
}
/**
* Don't create the reference object for the key if not computed.
* @param node The ClassProperty node to visit.
*/
ClassProperty(node) {
const upperTypeMode = this.typeMode;
const { computed, decorators, key, typeAnnotation, value } = node;
this.typeMode = false;
this.visitDecorators(decorators);
if (computed) {
this.visit(key);
}
this.typeMode = true;
this.visit(typeAnnotation);
this.typeMode = false;
this.visit(value);
this.typeMode = upperTypeMode;
}
/**
* Visit new expression.
* @param node The NewExpression node to visit.
*/
NewExpression(node) {
this.visitTypeParameters(node);
this.visit(node.callee);
node.arguments.forEach(this.visit, this);
}
/**
* Override.
* Visit call expression.
* @param node The CallExpression node to visit.
*/
CallExpression(node) {
this.visitTypeParameters(node);
this.visit(node.callee);
node.arguments.forEach(this.visit, this);
}
/**
* Visit optional member expression.
* @param node The OptionalMemberExpression node to visit.
*/
OptionalMemberExpression(node) {
this.visit(node.object);
if (node.computed) {
this.visit(node.property);
}
}
/**
* Visit optional call expression.
* @param node The OptionalMemberExpression node to visit.
*/
OptionalCallExpression(node) {
this.visitTypeParameters(node);
this.visit(node.callee);
node.arguments.forEach(this.visit, this);
}
/**
* Define the variable of this function declaration only once.
* Because to avoid confusion of `no-redeclare` rule by overloading.
* @param node The TSDeclareFunction node to visit.
*/
TSDeclareFunction(node) {
var _a, _b;
const scopeManager = this.scopeManager;
const upperScope = this.currentScope();
const { id, typeParameters, params, returnType } = node;
// Ignore this if other overload have already existed.
if (id) {
const variable = upperScope.set.get(id.name);
const defs = (_a = variable) === null || _a === void 0 ? void 0 : _a.defs;
const existed = (_b = defs) === null || _b === void 0 ? void 0 : _b.some((d) => d.type === 'FunctionName');
if (!existed) {
upperScope.__define(id, new experimental_utils_1.TSESLintScope.Definition('FunctionName', id, node, null, null, null));
}
}
// Open the function scope.
scopeManager.__nestEmptyFunctionScope(node);
const innerScope = this.currentScope();
// Process the type parameters
this.visit(typeParameters);
// Process parameter declarations.
for (let i = 0; i < params.length; ++i) {
this.visitPattern(params[i], { processRightHandNodes: true }, (pattern, info) => {
innerScope.__define(pattern, new experimental_utils_1.TSESLintScope.ParameterDefinition(pattern, node, i, info.rest));
// Set `variable.eslintUsed` to tell ESLint that the variable is used.
const variable = innerScope.set.get(pattern.name);
if (variable) {
variable.eslintUsed = true;
}
this.referencingDefaultValue(pattern, info.assignments, null, true);
});
}
// Process the return type.
this.visit(returnType);
// Close the function scope.
this.close(node);
}
/**
* Create reference objects for the references in parameters and return type.
* @param node The TSEmptyBodyFunctionExpression node to visit.
*/
TSEmptyBodyFunctionExpression(node) {
const upperTypeMode = this.typeMode;
const { typeParameters, params, returnType } = node;
this.typeMode = true;
this.visit(typeParameters);
params.forEach(this.visit, this);
this.visit(returnType);
this.typeMode = upperTypeMode;
}
/**
* Don't make variable because it declares only types.
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
* @param node The TSInterfaceDeclaration node to visit.
*/
TSInterfaceDeclaration(node) {
this.visitTypeNodes(node);
}
/**
* Don't make variable because it declares only types.
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
* @param node The TSClassImplements node to visit.
*/
TSClassImplements(node) {
this.visitTypeNodes(node);
}
/**
* Don't make variable because it declares only types.
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
* @param node The TSIndexSignature node to visit.
*/
TSIndexSignature(node) {
this.visitTypeNodes(node);
}
/**
* Visit type assertion.
* @param node The TSTypeAssertion node to visit.
*/
TSTypeAssertion(node) {
if (this.typeMode) {
this.visit(node.typeAnnotation);
}
else {
this.typeMode = true;
this.visit(node.typeAnnotation);
this.typeMode = false;
}
this.visit(node.expression);
}
/**
* Visit as expression.
* @param node The TSAsExpression node to visit.
*/
TSAsExpression(node) {
this.visit(node.expression);
if (this.typeMode) {
this.visit(node.typeAnnotation);
}
else {
this.typeMode = true;
this.visit(node.typeAnnotation);
this.typeMode = false;
}
}
/**
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
* @param node The TSTypeAnnotation node to visit.
*/
TSTypeAnnotation(node) {
this.visitTypeNodes(node);
}
/**
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
* @param node The TSTypeParameterDeclaration node to visit.
*/
TSTypeParameterDeclaration(node) {
this.visitTypeNodes(node);
}
/**
* Create reference objects for the references in `typeof` expression.
* @param node The TSTypeQuery node to visit.
*/
TSTypeQuery(node) {
if (this.typeMode) {
this.typeMode = false;
this.visitChildren(node);
this.typeMode = true;
}
else {
this.visitChildren(node);
}
}
/**
* @param node The TSTypeParameter node to visit.
*/
TSTypeParameter(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSInferType node to visit.
*/
TSInferType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSTypeReference node to visit.
*/
TSTypeReference(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSTypeLiteral node to visit.
*/
TSTypeLiteral(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSLiteralType node to visit.
*/
TSLiteralType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSIntersectionType node to visit.
*/
TSIntersectionType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSConditionalType node to visit.
*/
TSConditionalType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSIndexedAccessType node to visit.
*/
TSIndexedAccessType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSMappedType node to visit.
*/
TSMappedType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSOptionalType node to visit.
*/
TSOptionalType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSParenthesizedType node to visit.
*/
TSParenthesizedType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSRestType node to visit.
*/
TSRestType(node) {
this.visitTypeNodes(node);
}
/**
* @param node The TSTupleType node to visit.
*/
TSTupleType(node) {
this.visitTypeNodes(node);
}
/**
* Create reference objects for the object part. (This is `obj.prop`)
* @param node The TSQualifiedName node to visit.
*/
TSQualifiedName(node) {
this.visit(node.left);
}
/**
* Create reference objects for the references in computed keys.
* @param node The TSPropertySignature node to visit.
*/
TSPropertySignature(node) {
const upperTypeMode = this.typeMode;
const { computed, key, typeAnnotation, initializer } = node;
if (computed) {
this.typeMode = false;
this.visit(key);
this.typeMode = true;
}
else {
this.typeMode = true;
this.visit(key);
}
this.visit(typeAnnotation);
this.visit(initializer);
this.typeMode = upperTypeMode;
}
/**
* Create reference objects for the references in computed keys.
* @param node The TSMethodSignature node to visit.
*/
TSMethodSignature(node) {
const upperTypeMode = this.typeMode;
const { computed, key, typeParameters, params, returnType } = node;
if (computed) {
this.typeMode = false;
this.visit(key);
this.typeMode = true;
}
else {
this.typeMode = true;
this.visit(key);
}
this.visit(typeParameters);
params.forEach(this.visit, this);
this.visit(returnType);
this.typeMode = upperTypeMode;
}
/**
* Create variable object for the enum.
* The enum declaration creates a scope for the enum members.
*
* enum E {
* A,
* B,
* C = A + B // A and B are references to the enum member.
* }
*
* const a = 0
* enum E {
* A = a // a is above constant.
* }
*
* @param node The TSEnumDeclaration node to visit.
*/
TSEnumDeclaration(node) {
const { id, members } = node;
const scopeManager = this.scopeManager;
const scope = this.currentScope();
if (id) {
scope.__define(id, new experimental_utils_1.TSESLintScope.Definition('EnumName', id, node));
}
scopeManager.__nestEnumScope(node);
for (const member of members) {
this.visit(member);
}
this.close(node);
}
/**
* Create variable object for the enum member and create reference object for the initializer.
* And visit the initializer.
*
* @param node The TSEnumMember node to visit.
*/
TSEnumMember(node) {
const { id, initializer } = node;
const scope = this.currentScope();
scope.__define(id, new experimental_utils_1.TSESLintScope.Definition('EnumMemberName', id, node));
if (initializer) {
scope.__referencing(id, experimental_utils_1.TSESLintScope.Reference.WRITE, initializer, null, false, true);
this.visit(initializer);
}
}
/**
* Create the variable object for the module name, and visit children.
* @param node The TSModuleDeclaration node to visit.
*/
TSModuleDeclaration(node) {
const scope = this.currentScope();
const { id, body } = node;
if (node.global) {
this.visitGlobalAugmentation(node);
return;
}
if (id && id.type === experimental_utils_1.AST_NODE_TYPES.Identifier) {
scope.__define(id, new experimental_utils_1.TSESLintScope.Definition('NamespaceName', id, node, null, null, null));
}
this.visit(body);
}
TSTypeAliasDeclaration(node) {
this.typeMode = true;
this.visitChildren(node);
this.typeMode = false;
}
/**
* Process the module block.
* @param node The TSModuleBlock node to visit.
*/
TSModuleBlock(node) {
this.scopeManager.__nestBlockScope(node);
this.visitChildren(node);
this.close(node);
}
TSAbstractClassProperty(node) {
this.ClassProperty(node);
}
TSAbstractMethodDefinition(node) {
this.MethodDefinition(node);
}
/**
* Process import equal declaration
* @param node The TSImportEqualsDeclaration node to visit.
*/
TSImportEqualsDeclaration(node) {
const { id, moduleReference } = node;
if (id && id.type === experimental_utils_1.AST_NODE_TYPES.Identifier) {
this.currentScope().__define(id, new experimental_utils_1.TSESLintScope.Definition('ImportBinding', id, node, null, null, null));
}
this.visit(moduleReference);
}
/**
* Process the global augmentation.
* 1. Set the global scope as the current scope.
* 2. Configure the global scope to set `variable.eslintUsed = true` for all defined variables. This means `no-unused-vars` doesn't warn those.
* @param node The TSModuleDeclaration node to visit.
*/
visitGlobalAugmentation(node) {
const scopeManager = this.scopeManager;
const currentScope = this.currentScope();
const globalScope = scopeManager.globalScope;
const originalDefine = globalScope.__define;
globalScope.__define = overrideDefine(originalDefine);
scopeManager.__currentScope = globalScope;
// Skip TSModuleBlock to avoid to create that block scope.
if (node.body && node.body.type === experimental_utils_1.AST_NODE_TYPES.TSModuleBlock) {
node.body.body.forEach(this.visit, this);
}
scopeManager.__currentScope = currentScope;
globalScope.__define = originalDefine;
}
/**
* Process decorators.
* @param decorators The decorator nodes to visit.
*/
visitDecorators(decorators) {
if (decorators) {
decorators.forEach(this.visit, this);
}
}
/**
* Process all child of type nodes
* @param node node to be processed
*/
visitTypeNodes(node) {
if (this.typeMode) {
this.visitChildren(node);
}
else {
this.typeMode = true;
this.visitChildren(node);
this.typeMode = false;
}
}
}
function analyzeScope(ast, parserOptions) {
var _a;
const options = {
ignoreEval: true,
optimistic: false,
directive: false,
nodejsScope: parserOptions.sourceType === 'script' &&
(parserOptions.ecmaFeatures &&
parserOptions.ecmaFeatures.globalReturn) === true,
impliedStrict: false,
sourceType: parserOptions.sourceType,
ecmaVersion: (_a = parserOptions.ecmaVersion, (_a !== null && _a !== void 0 ? _a : 2018)),
childVisitorKeys: typescript_estree_1.visitorKeys,
fallback: eslint_visitor_keys_1.getKeys,
};
const scopeManager = new scope_manager_1.ScopeManager(options);
const referencer = new Referencer(options, scopeManager);
referencer.visit(ast);
return scopeManager;
}
exports.analyzeScope = analyzeScope;
//# sourceMappingURL=analyze-scope.js.map