β¬ οΈ Back to Table of Contents
π no-redeclare¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 4 |
| π¦ Imports | 6 |
| π Type Aliases | 2 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/no-redeclare.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'no-redeclare' |
meta.type |
'suggestion' |
meta.docs.description |
'Disallow variable redeclaration' |
meta.docs.extendsBaseRule |
true |
meta.messages.redeclared |
"'{{id}}' is already defined." |
meta.messages.redeclaredAsBuiltin |
"'{{id}}' is already defined as a built-in global variable." |
meta.messages.redeclaredBySyntax |
"'{{id}}' is already defined by a variable declaration." |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { builtinGlobals: { type: 'boolean', description: 'Wheth... |
defaultOptions |
[ { builtinGlobals: true, ignoreDeclarationMerge: true, }, ] |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
ScopeType |
@typescript-eslint/scope-manager |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getNameLocationInGlobalDirectiveComment |
../util |
Functions¶
create(context: any, [options]: any): { ArrowFunctionExpression: (node: TSESTree.Node) => void; B⦶
Parameters:
contextany[options]any
Returns: { ArrowFunctionExpression: (node: TSESTree.Node) => void; BlockStatement: (node: TSESTree.Node) => void; ForInStatement: (node: TSESTree.Node) => void; ForOfStatement: (node: TSESTree.Node) => void; ForStatement: (node: TSESTree.Node) => void; FunctionDeclaration: (node: TSESTree.Node) => void; FunctionExpression: (node: TSESTree.Node) => void; Program(node: any): void; SwitchStatement: (node: TSESTree.Node) => void; }
Calls:
getNameLocationInGlobalDirectiveComment (from ../util)variable.identifiers .map(id => ({ identifier: id, parent: id.parent, })) // ignore function declarations because TS will treat them as an overload .filteridentifiers.everyCLASS_DECLARATION_MERGE_NODES.hasidentifiers.filterFUNCTION_DECLARATION_MERGE_NODES.hasENUM_DECLARATION_MERGE_NODES.hasiterateDeclarationscontext.reportcontext.sourceCode.getScopefindVariablesInScope
Internal Comments:
// interfaces merging (x3)
// namespace/module merging (x3)
// class + interface/namespace merging (x6)
// safe declaration merging (x3)
// there's more than one class declaration, which needs to be reported
// there's more than one function declaration, which needs to be reported
// enum + namespace merging (x3)
// there's more than one enum declaration, which needs to be reported
/*
* If the type of a declaration is different from the type of
* the first declaration, it shows the location of the first
* declaration.
*/ (x2)
// Report extra declarations.
/**
* Find variables in the current scope.
*/
/*
* In ES5, some node type such as `BlockStatement` doesn't have that scope.
* `scope.block` is a different node in such a case.
*/
// Node.js or ES modules has a special scope.
// The special scope's block is the Program node. (x3)
Code
create(context, [options]) {
const CLASS_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
AST_NODE_TYPES.ClassDeclaration,
AST_NODE_TYPES.TSInterfaceDeclaration,
AST_NODE_TYPES.TSModuleDeclaration,
]);
const FUNCTION_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
AST_NODE_TYPES.FunctionDeclaration,
AST_NODE_TYPES.TSModuleDeclaration,
]);
const ENUM_DECLARATION_MERGE_NODES = new Set<AST_NODE_TYPES>([
AST_NODE_TYPES.TSEnumDeclaration,
AST_NODE_TYPES.TSModuleDeclaration,
]);
function* iterateDeclarations(variable: TSESLint.Scope.Variable): Generator<
{
loc?: TSESTree.SourceLocation;
node?: TSESTree.Comment | TSESTree.Identifier;
type: 'builtin' | 'comment' | 'syntax';
},
void
> {
if (
options.builtinGlobals &&
'eslintImplicitGlobalSetting' in variable &&
(variable.eslintImplicitGlobalSetting === 'readonly' ||
variable.eslintImplicitGlobalSetting === 'writable')
) {
yield { type: 'builtin' };
}
if (
'eslintExplicitGlobalComments' in variable &&
variable.eslintExplicitGlobalComments
) {
for (const comment of variable.eslintExplicitGlobalComments) {
yield {
loc: getNameLocationInGlobalDirectiveComment(
context.sourceCode,
comment,
variable.name,
),
node: comment,
type: 'comment',
};
}
}
const identifiers = variable.identifiers
.map(id => ({
identifier: id,
parent: id.parent,
}))
// ignore function declarations because TS will treat them as an overload
.filter(
({ parent }) => parent.type !== AST_NODE_TYPES.TSDeclareFunction,
);
if (options.ignoreDeclarationMerge && identifiers.length > 1) {
if (
// interfaces merging
identifiers.every(
({ parent }) =>
parent.type === AST_NODE_TYPES.TSInterfaceDeclaration,
)
) {
return;
}
if (
// namespace/module merging
identifiers.every(
({ parent }) => parent.type === AST_NODE_TYPES.TSModuleDeclaration,
)
) {
return;
}
if (
// class + interface/namespace merging
identifiers.every(({ parent }) =>
CLASS_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const classDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.ClassDeclaration,
);
if (classDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one class declaration, which needs to be reported
for (const { identifier } of classDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
if (
// class + interface/namespace merging
identifiers.every(({ parent }) =>
FUNCTION_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const functionDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.FunctionDeclaration,
);
if (functionDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one function declaration, which needs to be reported
for (const { identifier } of functionDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
if (
// enum + namespace merging
identifiers.every(({ parent }) =>
ENUM_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const enumDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.TSEnumDeclaration,
);
if (enumDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one enum declaration, which needs to be reported
for (const { identifier } of enumDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
}
for (const { identifier } of identifiers) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
}
function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
for (const variable of scope.variables) {
const [declaration, ...extraDeclarations] =
iterateDeclarations(variable);
if (extraDeclarations.length === 0) {
continue;
}
/*
* If the type of a declaration is different from the type of
* the first declaration, it shows the location of the first
* declaration.
*/
const detailMessageId =
declaration.type === 'builtin'
? 'redeclaredAsBuiltin'
: 'redeclaredBySyntax';
const data = { id: variable.name };
// Report extra declarations.
for (const { loc, node, type } of extraDeclarations) {
const messageId =
type === declaration.type ? 'redeclared' : detailMessageId;
if (node) {
context.report({ loc, node, messageId, data });
} else if (loc) {
context.report({ loc, messageId, data });
}
}
}
}
/**
* Find variables in the current scope.
*/
function checkForBlock(node: TSESTree.Node): void {
const scope = context.sourceCode.getScope(node);
/*
* In ES5, some node type such as `BlockStatement` doesn't have that scope.
* `scope.block` is a different node in such a case.
*/
if (scope.block === node) {
findVariablesInScope(scope);
}
}
return {
ArrowFunctionExpression: checkForBlock,
BlockStatement: checkForBlock,
ForInStatement: checkForBlock,
ForOfStatement: checkForBlock,
ForStatement: checkForBlock,
FunctionDeclaration: checkForBlock,
FunctionExpression: checkForBlock,
Program(node): void {
const scope = context.sourceCode.getScope(node);
findVariablesInScope(scope);
// Node.js or ES modules has a special scope.
if (
scope.type === ScopeType.global &&
// The special scope's block is the Program node.
scope.block === scope.childScopes[0]?.block
) {
findVariablesInScope(scope.childScopes[0]);
}
},
SwitchStatement: checkForBlock,
};
}
Internal helpers¶
Declared inside another function in this file.
iterateDeclarations(variable: TSESLint.Scope.Variable): Generator< { loc?: TSESTree.SourceLocation; node?: TSESTree⦶
Parameters:
variableTSESLint.Scope.Variable
Returns: Generator<
{
loc?: TSESTree.SourceLocation;
node?: TSESTree.Comment | TSESTree.Identifier;
type: 'builtin' | 'comment' | 'syntax';
},
void
>
Calls:
getNameLocationInGlobalDirectiveComment (from ../util)variable.identifiers .map(id => ({ identifier: id, parent: id.parent, })) // ignore function declarations because TS will treat them as an overload .filteridentifiers.everyCLASS_DECLARATION_MERGE_NODES.hasidentifiers.filterFUNCTION_DECLARATION_MERGE_NODES.hasENUM_DECLARATION_MERGE_NODES.has
Internal Comments:
// interfaces merging (x3)
// namespace/module merging (x3)
// class + interface/namespace merging (x6)
// safe declaration merging (x3)
// there's more than one class declaration, which needs to be reported
// there's more than one function declaration, which needs to be reported
// enum + namespace merging (x3)
// there's more than one enum declaration, which needs to be reported
Code
function* iterateDeclarations(variable: TSESLint.Scope.Variable): Generator<
{
loc?: TSESTree.SourceLocation;
node?: TSESTree.Comment | TSESTree.Identifier;
type: 'builtin' | 'comment' | 'syntax';
},
void
> {
if (
options.builtinGlobals &&
'eslintImplicitGlobalSetting' in variable &&
(variable.eslintImplicitGlobalSetting === 'readonly' ||
variable.eslintImplicitGlobalSetting === 'writable')
) {
yield { type: 'builtin' };
}
if (
'eslintExplicitGlobalComments' in variable &&
variable.eslintExplicitGlobalComments
) {
for (const comment of variable.eslintExplicitGlobalComments) {
yield {
loc: getNameLocationInGlobalDirectiveComment(
context.sourceCode,
comment,
variable.name,
),
node: comment,
type: 'comment',
};
}
}
const identifiers = variable.identifiers
.map(id => ({
identifier: id,
parent: id.parent,
}))
// ignore function declarations because TS will treat them as an overload
.filter(
({ parent }) => parent.type !== AST_NODE_TYPES.TSDeclareFunction,
);
if (options.ignoreDeclarationMerge && identifiers.length > 1) {
if (
// interfaces merging
identifiers.every(
({ parent }) =>
parent.type === AST_NODE_TYPES.TSInterfaceDeclaration,
)
) {
return;
}
if (
// namespace/module merging
identifiers.every(
({ parent }) => parent.type === AST_NODE_TYPES.TSModuleDeclaration,
)
) {
return;
}
if (
// class + interface/namespace merging
identifiers.every(({ parent }) =>
CLASS_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const classDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.ClassDeclaration,
);
if (classDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one class declaration, which needs to be reported
for (const { identifier } of classDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
if (
// class + interface/namespace merging
identifiers.every(({ parent }) =>
FUNCTION_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const functionDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.FunctionDeclaration,
);
if (functionDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one function declaration, which needs to be reported
for (const { identifier } of functionDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
if (
// enum + namespace merging
identifiers.every(({ parent }) =>
ENUM_DECLARATION_MERGE_NODES.has(parent.type),
)
) {
const enumDecls = identifiers.filter(
({ parent }) => parent.type === AST_NODE_TYPES.TSEnumDeclaration,
);
if (enumDecls.length === 1) {
// safe declaration merging
return;
}
// there's more than one enum declaration, which needs to be reported
for (const { identifier } of enumDecls) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
return;
}
}
for (const { identifier } of identifiers) {
yield { loc: identifier.loc, node: identifier, type: 'syntax' };
}
}
findVariablesInScope(scope: TSESLint.Scope.Scope): void¶
Parameters:
scopeTSESLint.Scope.Scope
Returns: void
Calls:
iterateDeclarationscontext.report
Internal Comments:
/*
* If the type of a declaration is different from the type of
* the first declaration, it shows the location of the first
* declaration.
*/ (x2)
// Report extra declarations.
Code
function findVariablesInScope(scope: TSESLint.Scope.Scope): void {
for (const variable of scope.variables) {
const [declaration, ...extraDeclarations] =
iterateDeclarations(variable);
if (extraDeclarations.length === 0) {
continue;
}
/*
* If the type of a declaration is different from the type of
* the first declaration, it shows the location of the first
* declaration.
*/
const detailMessageId =
declaration.type === 'builtin'
? 'redeclaredAsBuiltin'
: 'redeclaredBySyntax';
const data = { id: variable.name };
// Report extra declarations.
for (const { loc, node, type } of extraDeclarations) {
const messageId =
type === declaration.type ? 'redeclared' : detailMessageId;
if (node) {
context.report({ loc, node, messageId, data });
} else if (loc) {
context.report({ loc, messageId, data });
}
}
}
}
checkForBlock(node: TSESTree.Node): void¶
Find variables in the current scope.
Calls:
context.sourceCode.getScopefindVariablesInScope
Internal Comments:
/*
* In ES5, some node type such as `BlockStatement` doesn't have that scope.
* `scope.block` is a different node in such a case.
*/
Code
Type Aliases¶
MessageIds¶
Options¶
Generated by Syntax Scribe