📄 no-shadow.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 23 |
📦 Imports | 9 |
📊 Variables & Constants | 29 |
📑 Type Aliases | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-shadow.ts
📦 Imports¶
Name | Source |
---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
DefinitionType |
@typescript-eslint/scope-manager |
ScopeType |
@typescript-eslint/scope-manager |
AST_NODE_TYPES |
@typescript-eslint/utils |
ASTUtils |
@typescript-eslint/utils |
createRule |
../util |
isDefinitionFile |
../util |
isTypeImport |
../util/isTypeImport |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
allowedFunctionVariableDefTypes |
Set<any> |
const | `new Set([ | |
AST_NODE_TYPES.TSCallSignatureDeclaration, | ||||
AST_NODE_TYPES.TSFunctionType, | ||||
AST_NODE_TYPES.TSMethodSignature, | ||||
AST_NODE_TYPES.TSEmptyBodyFunctionExpression, | ||||
AST_NODE_TYPES.TSDeclareFunction, | ||||
AST_NODE_TYPES.TSConstructSignatureDeclaration, | ||||
AST_NODE_TYPES.TSConstructorType, | ||||
])` | ✗ | |||
functionsHoistedNodes |
Set<any> |
const | new Set([AST_NODE_TYPES.FunctionDeclaration]) |
✗ |
typesHoistedNodes |
Set<any> |
const | `new Set([ | |
AST_NODE_TYPES.TSInterfaceDeclaration, | ||||
AST_NODE_TYPES.TSTypeAliasDeclaration, | ||||
])` | ✗ | |||
isShadowedValue |
any |
const | `!('isValueVariable' in shadowed) | |
!firstDefinition | ||||
(!isTypeImport(firstDefinition) && shadowed.isValueVariable)` | ✗ | |||
isShadowedValue |
any |
const | 'isValueVariable' in shadowed ? shadowed.isValueVariable : true |
✗ |
typeParameter |
any |
const | variable.identifiers[0].parent |
✗ |
typeParameterDecl |
any |
const | typeParameter.parent |
✗ |
functionExpr |
any |
const | typeParameterDecl.parent |
✗ |
methodDefinition |
any |
const | functionExpr.parent |
✗ |
typeParameter |
any |
const | variable.identifiers[0].parent |
✗ |
typeParameterDecl |
any |
const | typeParameter.parent |
✗ |
classDecl |
any |
const | typeParameterDecl.parent |
✗ |
block |
any |
const | variable.scope.block |
✗ |
block |
any |
const | variable.scope.block |
✗ |
currentNode |
any |
let/var | node |
✗ |
upper |
any |
const | scope.upper |
✗ |
fun |
any |
const | variableScope.block |
✗ |
node |
any |
let/var | outerDef.name as TSESTree.Node | undefined |
✗ |
location |
any |
const | callExpression.range[1] |
✗ |
outerScope |
any |
const | scopeVar.scope |
✗ |
outer |
any |
const | outerDef?.parent?.range |
✗ |
innerScope |
any |
const | variable.scope |
✗ |
inner |
any |
const | innerDef?.name.range |
✗ |
fileName |
any |
const | context.filename |
✗ |
variables |
any |
const | scope.variables |
✗ |
shadowed |
any |
const | `scope.upper | |
? ASTUtils.findVariable(scope.upper, variable.name) | ||||
: null` | ✗ | |||
isESLintGlobal |
boolean |
const | 'writeable' in shadowed |
✗ |
stack |
any[] |
const | [...globalScope.childScopes] |
✗ |
scope |
any |
const | stack.pop()! |
✗ |
Functions¶
isGlobalAugmentation(scope: TSESLint.Scope.Scope): boolean
¶
Code
-
JSDoc:
-
Parameters:
scope: TSESLint.Scope.Scope
- Return Type:
boolean
- Calls:
isGlobalAugmentation
isThisParam(variable: TSESLint.Scope.Variable): boolean
¶
Code
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
isTypeValueShadow(variable: TSESLint.Scope.Variable, shadowed: TSESLint.Scope.Variable): boolean
¶
Code
function isTypeValueShadow(
variable: TSESLint.Scope.Variable,
shadowed: TSESLint.Scope.Variable,
): boolean {
if (options.ignoreTypeValueShadow !== true) {
return false;
}
if (!('isValueVariable' in variable)) {
// this shouldn't happen...
return false;
}
const firstDefinition = shadowed.defs.at(0);
const isShadowedValue =
!('isValueVariable' in shadowed) ||
!firstDefinition ||
(!isTypeImport(firstDefinition) && shadowed.isValueVariable);
return variable.isValueVariable !== isShadowedValue;
}
- Parameters:
variable: TSESLint.Scope.Variable
shadowed: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
shadowed.defs.at
isTypeImport (from ../util/isTypeImport)
- Internal Comments:
isFunctionTypeParameterNameValueShadow(variable: TSESLint.Scope.Variable, shadowed: TSESLint.Scope.Variable): boolean
¶
Code
function isFunctionTypeParameterNameValueShadow(
variable: TSESLint.Scope.Variable,
shadowed: TSESLint.Scope.Variable,
): boolean {
if (options.ignoreFunctionTypeParameterNameValueShadow !== true) {
return false;
}
if (!('isValueVariable' in variable)) {
// this shouldn't happen...
return false;
}
const isShadowedValue =
'isValueVariable' in shadowed ? shadowed.isValueVariable : true;
if (!isShadowedValue) {
return false;
}
return variable.defs.every(def =>
allowedFunctionVariableDefTypes.has(def.node.type),
);
}
- Parameters:
variable: TSESLint.Scope.Variable
shadowed: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
variable.defs.every
allowedFunctionVariableDefTypes.has
- Internal Comments:
isGenericOfStaticMethod(variable: TSESLint.Scope.Variable): boolean
¶
Code
function isGenericOfStaticMethod(
variable: TSESLint.Scope.Variable,
): boolean {
if (!('isTypeVariable' in variable)) {
// this shouldn't happen...
return false;
}
if (!variable.isTypeVariable) {
return false;
}
if (variable.identifiers.length === 0) {
return false;
}
const typeParameter = variable.identifiers[0].parent;
if (typeParameter.type !== AST_NODE_TYPES.TSTypeParameter) {
return false;
}
const typeParameterDecl = typeParameter.parent;
if (
typeParameterDecl.type !== AST_NODE_TYPES.TSTypeParameterDeclaration
) {
return false;
}
const functionExpr = typeParameterDecl.parent;
if (
functionExpr.type !== AST_NODE_TYPES.FunctionExpression &&
functionExpr.type !== AST_NODE_TYPES.TSEmptyBodyFunctionExpression
) {
return false;
}
const methodDefinition = functionExpr.parent;
if (methodDefinition.type !== AST_NODE_TYPES.MethodDefinition) {
return false;
}
return methodDefinition.static;
}
- Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
- Internal Comments:
isGenericOfClass(variable: TSESLint.Scope.Variable): boolean
¶
Code
function isGenericOfClass(variable: TSESLint.Scope.Variable): boolean {
if (!('isTypeVariable' in variable)) {
// this shouldn't happen...
return false;
}
if (!variable.isTypeVariable) {
return false;
}
if (variable.identifiers.length === 0) {
return false;
}
const typeParameter = variable.identifiers[0].parent;
if (typeParameter.type !== AST_NODE_TYPES.TSTypeParameter) {
return false;
}
const typeParameterDecl = typeParameter.parent;
if (
typeParameterDecl.type !== AST_NODE_TYPES.TSTypeParameterDeclaration
) {
return false;
}
const classDecl = typeParameterDecl.parent;
return (
classDecl.type === AST_NODE_TYPES.ClassDeclaration ||
classDecl.type === AST_NODE_TYPES.ClassExpression
);
}
- Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
- Internal Comments:
isGenericOfAStaticMethodShadow(variable: TSESLint.Scope.Variable, shadowed: TSESLint.Scope.Variable): boolean
¶
Code
- Parameters:
variable: TSESLint.Scope.Variable
shadowed: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
isGenericOfStaticMethod
isGenericOfClass
`isImportDeclaration(definition: | TSESTree.ImportDeclaration¶
| TSESTree.TSImportEqualsDeclaration): definition is TSESTree.ImportDeclaration`
Code
- Parameters:
definition: | TSESTree.ImportDeclaration | TSESTree.TSImportEqualsDeclaration
- Return Type:
definition is TSESTree.ImportDeclaration
isExternalModuleDeclarationWithName(scope: TSESLint.Scope.Scope, name: string): boolean
¶
Code
- Parameters:
scope: TSESLint.Scope.Scope
name: string
- Return Type:
boolean
isExternalDeclarationMerging(scope: TSESLint.Scope.Scope, variable: TSESLint.Scope.Variable, shadowed: TSESLint.Scope.Variable): boolean
¶
Code
function isExternalDeclarationMerging(
scope: TSESLint.Scope.Scope,
variable: TSESLint.Scope.Variable,
shadowed: TSESLint.Scope.Variable,
): boolean {
const [firstDefinition] = shadowed.defs;
const [secondDefinition] = variable.defs;
return (
isTypeImport(firstDefinition) &&
isImportDeclaration(firstDefinition.parent) &&
isExternalModuleDeclarationWithName(
scope,
firstDefinition.parent.source.value,
) &&
(secondDefinition.node.type === AST_NODE_TYPES.TSInterfaceDeclaration ||
secondDefinition.node.type === AST_NODE_TYPES.TSTypeAliasDeclaration)
);
}
- Parameters:
scope: TSESLint.Scope.Scope
variable: TSESLint.Scope.Variable
shadowed: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
isTypeImport (from ../util/isTypeImport)
isImportDeclaration
isExternalModuleDeclarationWithName
isAllowed(variable: TSESLint.Scope.Variable): boolean
¶
Code
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
options.allow!.includes
- Internal Comments:
isDuplicatedClassNameVariable(variable: TSESLint.Scope.Variable): boolean
¶
Code
-
JSDoc:
/** * Checks if a variable of the class name in the class scope of ClassDeclaration. * * ClassDeclaration creates two variables of its name into its outer scope and its class scope. * So we should ignore the variable in the class scope. * @param variable The variable to check. * @returns Whether or not the variable of the class name in the class scope of ClassDeclaration. */
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
isDuplicatedEnumNameVariable(variable: TSESLint.Scope.Variable): boolean
¶
Code
-
JSDoc:
/** * Checks if a variable of the class name in the class scope of TSEnumDeclaration. * * TSEnumDeclaration creates two variables of its name into its outer scope and its class scope. * So we should ignore the variable in the class scope. * @param variable The variable to check. * @returns Whether or not the variable of the class name in the class scope of TSEnumDeclaration. */
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
isInRange(node: TSESTree.Node | null, location: number): boolean | null
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node | null
location: number
- Return Type:
boolean | null
findSelfOrAncestor(node: TSESTree.Node | undefined, match: (node: TSESTree.Node) => boolean): TSESTree.Node | undefined
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node | undefined
match: (node: TSESTree.Node) => boolean
- Return Type:
TSESTree.Node | undefined
- Calls:
match
getOuterScope(scope: TSESLint.Scope.Scope): TSESLint.Scope.Scope | null
¶
Code
-
JSDoc:
-
Parameters:
scope: TSESLint.Scope.Scope
- Return Type:
TSESLint.Scope.Scope | null
isInitPatternNode(variable: TSESLint.Scope.Variable, shadowedVariable: TSESLint.Scope.Variable): boolean
¶
Code
function isInitPatternNode(
variable: TSESLint.Scope.Variable,
shadowedVariable: TSESLint.Scope.Variable,
): boolean {
const outerDef = shadowedVariable.defs.at(0);
if (!outerDef) {
return false;
}
const { variableScope } = variable.scope;
if (
!(
(variableScope.block.type ===
AST_NODE_TYPES.ArrowFunctionExpression ||
variableScope.block.type === AST_NODE_TYPES.FunctionExpression) &&
getOuterScope(variableScope) === shadowedVariable.scope
)
) {
return false;
}
const fun = variableScope.block;
const { parent } = fun;
const callExpression = findSelfOrAncestor(
parent,
node => node.type === AST_NODE_TYPES.CallExpression,
);
if (!callExpression) {
return false;
}
let node = outerDef.name as TSESTree.Node | undefined;
const location = callExpression.range[1];
while (node) {
if (node.type === AST_NODE_TYPES.VariableDeclarator) {
if (isInRange(node.init, location)) {
return true;
}
if (
(node.parent.parent.type === AST_NODE_TYPES.ForInStatement ||
node.parent.parent.type === AST_NODE_TYPES.ForOfStatement) &&
isInRange(node.parent.parent.right, location)
) {
return true;
}
break;
} else if (node.type === AST_NODE_TYPES.AssignmentPattern) {
if (isInRange(node.right, location)) {
return true;
}
} else if (
[
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.CatchClause,
AST_NODE_TYPES.ClassDeclaration,
AST_NODE_TYPES.ClassExpression,
AST_NODE_TYPES.ExportNamedDeclaration,
AST_NODE_TYPES.FunctionDeclaration,
AST_NODE_TYPES.FunctionExpression,
AST_NODE_TYPES.ImportDeclaration,
].includes(node.type)
) {
break;
}
node = node.parent;
}
return false;
}
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
shadowedVariable: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
shadowedVariable.defs.at
getOuterScope
findSelfOrAncestor
isInRange
[ AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.CatchClause, AST_NODE_TYPES.ClassDeclaration, AST_NODE_TYPES.ClassExpression, AST_NODE_TYPES.ExportNamedDeclaration, AST_NODE_TYPES.FunctionDeclaration, AST_NODE_TYPES.FunctionExpression, AST_NODE_TYPES.ImportDeclaration, ].includes
isOnInitializer(variable: TSESLint.Scope.Variable, scopeVar: TSESLint.Scope.Variable): boolean
¶
Code
function isOnInitializer(
variable: TSESLint.Scope.Variable,
scopeVar: TSESLint.Scope.Variable,
): boolean {
const outerScope = scopeVar.scope;
const outerDef = scopeVar.defs.at(0);
const outer = outerDef?.parent?.range;
const innerScope = variable.scope;
const innerDef = variable.defs.at(0);
const inner = innerDef?.name.range;
return !!(
outer &&
inner &&
outer[0] < inner[0] &&
inner[1] < outer[1] &&
((innerDef.type === DefinitionType.FunctionName &&
innerDef.node.type === AST_NODE_TYPES.FunctionExpression) ||
innerDef.node.type === AST_NODE_TYPES.ClassExpression) &&
outerScope === innerScope.upper
);
}
-
JSDoc:
/** * Checks if a variable is inside the initializer of scopeVar. * * To avoid reporting at declarations such as `var a = function a() {};`. * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. * @param variable The variable to check. * @param scopeVar The scope variable to look for. * @returns Whether or not the variable is inside initializer of scopeVar. */
-
Parameters:
variable: TSESLint.Scope.Variable
scopeVar: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
scopeVar.defs.at
variable.defs.at
getNameRange(variable: TSESLint.Scope.Variable): TSESTree.Range | undefined
¶
Code
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
TSESTree.Range | undefined
- Calls:
variable.defs.at
isInTdz(variable: TSESLint.Scope.Variable, scopeVar: TSESLint.Scope.Variable): boolean
¶
Code
function isInTdz(
variable: TSESLint.Scope.Variable,
scopeVar: TSESLint.Scope.Variable,
): boolean {
const outerDef = scopeVar.defs.at(0);
const inner = getNameRange(variable);
const outer = getNameRange(scopeVar);
if (!inner || !outer || inner[1] >= outer[0]) {
return false;
}
if (!outerDef) {
return true;
}
if (options.hoist === 'functions') {
return !functionsHoistedNodes.has(outerDef.node.type);
}
if (options.hoist === 'types') {
return !typesHoistedNodes.has(outerDef.node.type);
}
if (options.hoist === 'functions-and-types') {
return (
!functionsHoistedNodes.has(outerDef.node.type) &&
!typesHoistedNodes.has(outerDef.node.type)
);
}
return true;
}
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
scopeVar: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
scopeVar.defs.at
getNameRange
functionsHoistedNodes.has
typesHoistedNodes.has
getDeclaredLocation(variable: TSESLint.Scope.Variable): { column: number; global: false; line: number } | { global: true }
¶
Code
function getDeclaredLocation(
variable: TSESLint.Scope.Variable,
): { column: number; global: false; line: number } | { global: true } {
const identifier = variable.identifiers.at(0);
if (identifier) {
return {
column: identifier.loc.start.column + 1,
global: false,
line: identifier.loc.start.line,
};
}
return {
global: true,
};
}
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
{ column: number; global: false; line: number } | { global: true }
- Calls:
variable.identifiers.at
isDeclareInDTSFile(variable: TSESLint.Scope.Variable): boolean
¶
Code
function isDeclareInDTSFile(variable: TSESLint.Scope.Variable): boolean {
const fileName = context.filename;
if (!isDefinitionFile(fileName)) {
return false;
}
return variable.defs.some(def => {
return (
(def.type === DefinitionType.Variable && def.parent.declare) ||
(def.type === DefinitionType.ClassName && def.node.declare) ||
(def.type === DefinitionType.TSEnumName && def.node.declare) ||
(def.type === DefinitionType.TSModuleName && def.node.declare)
);
});
}
-
JSDoc:
-
Parameters:
variable: TSESLint.Scope.Variable
- Return Type:
boolean
- Calls:
isDefinitionFile (from ../util)
variable.defs.some
checkForShadows(scope: TSESLint.Scope.Scope): void
¶
Code
function checkForShadows(scope: TSESLint.Scope.Scope): void {
// ignore global augmentation
if (isGlobalAugmentation(scope)) {
return;
}
const variables = scope.variables;
for (const variable of variables) {
// ignore "arguments"
if (variable.identifiers.length === 0) {
continue;
}
// this params are pseudo-params that cannot be shadowed
if (isThisParam(variable)) {
continue;
}
// ignore variables of a class name in the class scope of ClassDeclaration
if (isDuplicatedClassNameVariable(variable)) {
continue;
}
// ignore variables of a class name in the class scope of ClassDeclaration
if (isDuplicatedEnumNameVariable(variable)) {
continue;
}
// ignore configured allowed names
if (isAllowed(variable)) {
continue;
}
// ignore variables with the declare keyword in .d.ts files
if (isDeclareInDTSFile(variable)) {
continue;
}
// Gets shadowed variable.
const shadowed = scope.upper
? ASTUtils.findVariable(scope.upper, variable.name)
: null;
if (!shadowed) {
continue;
}
// ignore type value variable shadowing if configured
if (isTypeValueShadow(variable, shadowed)) {
continue;
}
// ignore function type parameter name shadowing if configured
if (isFunctionTypeParameterNameValueShadow(variable, shadowed)) {
continue;
}
// ignore static class method generic shadowing class generic
// this is impossible for the scope analyser to understand
// so we have to handle this manually in this rule
if (isGenericOfAStaticMethodShadow(variable, shadowed)) {
continue;
}
if (isExternalDeclarationMerging(scope, variable, shadowed)) {
continue;
}
const isESLintGlobal = 'writeable' in shadowed;
if (
(shadowed.identifiers.length > 0 ||
(options.builtinGlobals && isESLintGlobal)) &&
!isOnInitializer(variable, shadowed) &&
!(
options.ignoreOnInitialization &&
isInitPatternNode(variable, shadowed)
) &&
!(options.hoist !== 'all' && isInTdz(variable, shadowed))
) {
const location = getDeclaredLocation(shadowed);
context.report({
node: variable.identifiers[0],
...(location.global
? {
messageId: 'noShadowGlobal',
data: {
name: variable.name,
},
}
: {
messageId: 'noShadow',
data: {
name: variable.name,
shadowedColumn: location.column,
shadowedLine: location.line,
},
}),
});
}
}
}
-
JSDoc:
-
Parameters:
scope: TSESLint.Scope.Scope
- Return Type:
void
- Calls:
isGlobalAugmentation
isThisParam
isDuplicatedClassNameVariable
isDuplicatedEnumNameVariable
isAllowed
isDeclareInDTSFile
ASTUtils.findVariable
isTypeValueShadow
isFunctionTypeParameterNameValueShadow
isGenericOfAStaticMethodShadow
isExternalDeclarationMerging
isOnInitializer
isInitPatternNode
isInTdz
getDeclaredLocation
context.report
- Internal Comments:
// ignore global augmentation // ignore "arguments" // this params are pseudo-params that cannot be shadowed // ignore variables of a class name in the class scope of ClassDeclaration (x2) // ignore configured allowed names // ignore variables with the declare keyword in .d.ts files // Gets shadowed variable. (x2) // ignore type value variable shadowing if configured // ignore function type parameter name shadowing if configured // ignore static class method generic shadowing class generic // this is impossible for the scope analyser to understand // so we have to handle this manually in this rule
Type Aliases¶
MessageIds
¶
Options
¶
type Options = [
{
allow?: string[];
builtinGlobals?: boolean;
hoist?: 'all' | 'functions' | 'functions-and-types' | 'never' | 'types';
ignoreFunctionTypeParameterNameValueShadow?: boolean;
ignoreOnInitialization?: boolean;
ignoreTypeValueShadow?: boolean;
},
];