β¬ οΈ Back to Table of Contents
π no-unnecessary-qualifier¶
π Analysis Summary¶
| Metric | Count |
|---|---|
| π§ Functions | 12 |
| π¦ Imports | 4 |
π Table of Contents¶
π οΈ File Location:¶
π packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts
π€ Default Export¶
| Property | Value |
|---|---|
name |
'no-unnecessary-qualifier' |
meta.type |
'suggestion' |
meta.docs.description |
'Disallow unnecessary namespace qualifiers' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.unnecessaryQualifier |
"Qualifier is unnecessary since '{{ name }}' is in scope." |
meta.schema |
[] |
defaultOptions |
[] |
Entry point: create β documented under Functions.
π¦ Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getParserServices |
../util |
Functions¶
create(context: any): { 'ExportNamedDeclaration[declaration.type="TSEnumDeclarati⦶
Parameters:
contextany
Returns: { 'ExportNamedDeclaration[declaration.type="TSEnumDeclaration"]': (node: TSESTree.ExportNamedDeclaration | TSESTree.TSEnumDeclaration | TSESTree.TSModuleDeclaration) => void; 'ExportNamedDeclaration[declaration.type="TSEnumDeclaration"]:exit': () => void; 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"]': (node: TSESTree.ExportNamedDeclaration | TSESTree.TSEnumDeclaration | TSESTree.TSModuleDeclaration) => void; 'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"]:exit': () => void; 'MemberExpression:exit': (node: TSESTree.Node) => void; 'MemberExpression[computed=false]'(node: TSESTree.MemberExpression): void; TSEnumDeclaration: (node: TSESTree.ExportNamedDeclaration | TSESTree.TSEnumDeclaration | TSESTree.TSModuleDeclaration) => void; 'TSEnumDeclaration:exit': () => void; 'TSModuleDeclaration:exit': () => void; 'TSModuleDeclaration > TSModuleBlock'(node: TSESTree.TSModuleBlock): void; TSQualifiedName(node: TSESTree.TSQualifiedName): void; 'TSQualifiedName:exit': (node: TSESTree.Node) => void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckertsutils.isSymbolFlagSetchecker.getAliasedSymbolsymbol.getDeclarationssymbolDeclarations.somenamespacesInScope.sometryGetAliasedSymbolsymbolIsNamespaceInScopechecker.getSymbolsInScopescope.findchecker.getExportSymbolOfSymbolservices.getSymbolAtLocationesTreeNodeToTSNodeMap.getgetSymbolInScopecontext.sourceCode.getTextsymbolsAreEqualqualifierIsUnnecessarycontext.reportfixer.removeRangenamespacesInScope.pushnamespacesInScope.popisPropertyAccessExpressionisEntityNameExpressionvisitNamespaceAccessenterDeclaration
Internal Comments:
// If the symbol in scope is different, the qualifier is necessary. (x2)
// Only look for nested qualifier errors if we didn't already fail on the outer qualifier.
Code
create(context) {
const namespacesInScope: ts.Node[] = [];
let currentFailedNamespaceExpression: TSESTree.Node | null = null;
const services = getParserServices(context);
const esTreeNodeToTSNodeMap = services.esTreeNodeToTSNodeMap;
const checker = services.program.getTypeChecker();
function tryGetAliasedSymbol(
symbol: ts.Symbol,
checker: ts.TypeChecker,
): ts.Symbol | null {
return tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)
? checker.getAliasedSymbol(symbol)
: null;
}
function symbolIsNamespaceInScope(symbol: ts.Symbol): boolean {
const symbolDeclarations = symbol.getDeclarations() ?? [];
if (
symbolDeclarations.some(decl =>
namespacesInScope.some(ns => ns === decl),
)
) {
return true;
}
const alias = tryGetAliasedSymbol(symbol, checker);
return alias != null && symbolIsNamespaceInScope(alias);
}
function getSymbolInScope(
node: ts.Node,
flags: ts.SymbolFlags,
name: string,
): ts.Symbol | undefined {
const scope = checker.getSymbolsInScope(node, flags);
return scope.find(scopeSymbol => scopeSymbol.name === name);
}
function symbolsAreEqual(accessed: ts.Symbol, inScope: ts.Symbol): boolean {
return accessed === checker.getExportSymbolOfSymbol(inScope);
}
function qualifierIsUnnecessary(
qualifier: TSESTree.EntityName | TSESTree.MemberExpression,
name: TSESTree.Identifier,
): boolean {
const namespaceSymbol = services.getSymbolAtLocation(qualifier);
if (
namespaceSymbol == null ||
!symbolIsNamespaceInScope(namespaceSymbol)
) {
return false;
}
const accessedSymbol = services.getSymbolAtLocation(name);
if (accessedSymbol == null) {
return false;
}
// If the symbol in scope is different, the qualifier is necessary.
const tsQualifier = esTreeNodeToTSNodeMap.get(qualifier);
const fromScope = getSymbolInScope(
tsQualifier,
accessedSymbol.flags,
context.sourceCode.getText(name),
);
return !!fromScope && symbolsAreEqual(accessedSymbol, fromScope);
}
function visitNamespaceAccess(
node: TSESTree.Node,
qualifier: TSESTree.EntityName | TSESTree.MemberExpression,
name: TSESTree.Identifier,
): void {
// Only look for nested qualifier errors if we didn't already fail on the outer qualifier.
if (
!currentFailedNamespaceExpression &&
qualifierIsUnnecessary(qualifier, name)
) {
currentFailedNamespaceExpression = node;
context.report({
node: qualifier,
messageId: 'unnecessaryQualifier',
data: {
name: context.sourceCode.getText(name),
},
fix(fixer) {
return fixer.removeRange([qualifier.range[0], name.range[0]]);
},
});
}
}
function enterDeclaration(
node:
| TSESTree.ExportNamedDeclaration
| TSESTree.TSEnumDeclaration
| TSESTree.TSModuleDeclaration,
): void {
namespacesInScope.push(esTreeNodeToTSNodeMap.get(node));
}
function exitDeclaration(): void {
namespacesInScope.pop();
}
function resetCurrentNamespaceExpression(node: TSESTree.Node): void {
if (node === currentFailedNamespaceExpression) {
currentFailedNamespaceExpression = null;
}
}
function isPropertyAccessExpression(
node: TSESTree.Node,
): node is TSESTree.MemberExpression {
return node.type === AST_NODE_TYPES.MemberExpression && !node.computed;
}
function isEntityNameExpression(
node: TSESTree.Node,
): node is TSESTree.Identifier | TSESTree.MemberExpression {
return (
node.type === AST_NODE_TYPES.Identifier ||
(isPropertyAccessExpression(node) &&
isEntityNameExpression(node.object))
);
}
return {
'ExportNamedDeclaration[declaration.type="TSEnumDeclaration"]':
enterDeclaration,
'ExportNamedDeclaration[declaration.type="TSEnumDeclaration"]:exit':
exitDeclaration,
'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"]':
enterDeclaration,
'ExportNamedDeclaration[declaration.type="TSModuleDeclaration"]:exit':
exitDeclaration,
'MemberExpression:exit': resetCurrentNamespaceExpression,
'MemberExpression[computed=false]'(
node: TSESTree.MemberExpression,
): void {
const property = node.property as TSESTree.Identifier;
if (isEntityNameExpression(node.object)) {
visitNamespaceAccess(node, node.object, property);
}
},
TSEnumDeclaration: enterDeclaration,
'TSEnumDeclaration:exit': exitDeclaration,
'TSModuleDeclaration:exit': exitDeclaration,
'TSModuleDeclaration > TSModuleBlock'(
node: TSESTree.TSModuleBlock,
): void {
enterDeclaration(node.parent);
},
TSQualifiedName(node: TSESTree.TSQualifiedName): void {
visitNamespaceAccess(node, node.left, node.right);
},
'TSQualifiedName:exit': resetCurrentNamespaceExpression,
};
}
Internal helpers¶
Declared inside another function in this file.
tryGetAliasedSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): ts.Symbol | null¶
Parameters:
symbolts.Symbolcheckerts.TypeChecker
Returns: ts.Symbol | null
Calls:
tsutils.isSymbolFlagSetchecker.getAliasedSymbol
Code
symbolIsNamespaceInScope(symbol: ts.Symbol): boolean¶
Parameters:
symbolts.Symbol
Returns: boolean
Calls:
symbol.getDeclarationssymbolDeclarations.somenamespacesInScope.sometryGetAliasedSymbolsymbolIsNamespaceInScope
Code
function symbolIsNamespaceInScope(symbol: ts.Symbol): boolean {
const symbolDeclarations = symbol.getDeclarations() ?? [];
if (
symbolDeclarations.some(decl =>
namespacesInScope.some(ns => ns === decl),
)
) {
return true;
}
const alias = tryGetAliasedSymbol(symbol, checker);
return alias != null && symbolIsNamespaceInScope(alias);
}
getSymbolInScope(node: ts.Node, flags: ts.SymbolFlags, name: string): ts.Symbol | undefined¶
Parameters:
nodets.Nodeflagsts.SymbolFlagsnamestring
Returns: ts.Symbol | undefined
Calls:
checker.getSymbolsInScopescope.find
Code
symbolsAreEqual(accessed: ts.Symbol, inScope: ts.Symbol): boolean¶
Parameters:
accessedts.SymbolinScopets.Symbol
Returns: boolean
Calls:
checker.getExportSymbolOfSymbol
Code
qualifierIsUnnecessary(qualifier: TSESTree.EntityName | TSESTree.MemberExβ¦, name: TSESTree.Identifier): boolean¶
Parameters:
qualifierTSESTree.EntityName | TSESTree.MemberExpressionnameTSESTree.Identifier
Returns: boolean
Calls:
services.getSymbolAtLocationsymbolIsNamespaceInScopeesTreeNodeToTSNodeMap.getgetSymbolInScopecontext.sourceCode.getTextsymbolsAreEqual
Internal Comments:
Code
function qualifierIsUnnecessary(
qualifier: TSESTree.EntityName | TSESTree.MemberExpression,
name: TSESTree.Identifier,
): boolean {
const namespaceSymbol = services.getSymbolAtLocation(qualifier);
if (
namespaceSymbol == null ||
!symbolIsNamespaceInScope(namespaceSymbol)
) {
return false;
}
const accessedSymbol = services.getSymbolAtLocation(name);
if (accessedSymbol == null) {
return false;
}
// If the symbol in scope is different, the qualifier is necessary.
const tsQualifier = esTreeNodeToTSNodeMap.get(qualifier);
const fromScope = getSymbolInScope(
tsQualifier,
accessedSymbol.flags,
context.sourceCode.getText(name),
);
return !!fromScope && symbolsAreEqual(accessedSymbol, fromScope);
}
visitNamespaceAccess(node: TSESTree.Node, qualifier: TSESTree.EntityName | TSESTree.MemberExβ¦, name: TSESTree.Identifier): void¶
Parameters:
nodeTSESTree.NodequalifierTSESTree.EntityName | TSESTree.MemberExpressionnameTSESTree.Identifier
Returns: void
Calls:
qualifierIsUnnecessarycontext.reportcontext.sourceCode.getTextfixer.removeRange
Internal Comments:
Code
function visitNamespaceAccess(
node: TSESTree.Node,
qualifier: TSESTree.EntityName | TSESTree.MemberExpression,
name: TSESTree.Identifier,
): void {
// Only look for nested qualifier errors if we didn't already fail on the outer qualifier.
if (
!currentFailedNamespaceExpression &&
qualifierIsUnnecessary(qualifier, name)
) {
currentFailedNamespaceExpression = node;
context.report({
node: qualifier,
messageId: 'unnecessaryQualifier',
data: {
name: context.sourceCode.getText(name),
},
fix(fixer) {
return fixer.removeRange([qualifier.range[0], name.range[0]]);
},
});
}
}
enterDeclaration(node: | TSESTree.ExportNamedDeclaration | TSEβ¦): void¶
Parameters:
node| TSESTree.ExportNamedDeclaration | TSESTree.TSEnumDeclaration | TSESTree.TSModuleDeclaration
Returns: void
Calls:
namespacesInScope.pushesTreeNodeToTSNodeMap.get
Code
exitDeclaration(): void¶
Returns: void
Calls:
namespacesInScope.pop
resetCurrentNamespaceExpression(node: TSESTree.Node): void¶
Parameters:
nodeTSESTree.Node
Returns: void
Code
isPropertyAccessExpression(node: TSESTree.Node): node is TSESTree.MemberExpression¶
Parameters:
nodeTSESTree.Node
Returns: node is TSESTree.MemberExpression
Code
isEntityNameExpression(node: TSESTree.Node): node is TSESTree.Identifier | TSESTree.MemberExpression¶
Parameters:
nodeTSESTree.Node
Returns: node is TSESTree.Identifier | TSESTree.MemberExpression
Calls:
isPropertyAccessExpressionisEntityNameExpression
Code
Generated by Syntax Scribe