TypeScript scope analyser for ESLint that provides comprehensive scope analysis capabilities for JavaScript and TypeScript code
—
The definition tracking system categorizes how variables are defined and provides detailed context about their declarations across different language constructs.
The main Definition type that represents all possible definition types in the system.
type Definition =
| CatchClauseDefinition
| ClassNameDefinition
| FunctionNameDefinition
| ImplicitGlobalVariableDefinition
| ImportBindingDefinition
| ParameterDefinition
| TSEnumMemberDefinition
| TSEnumNameDefinition
| TSModuleNameDefinition
| TypeDefinition
| VariableDefinition;Enumeration of all definition type identifiers used throughout the system.
enum DefinitionType {
CatchClause = "CatchClause",
ClassName = "ClassName",
FunctionName = "FunctionName",
ImplicitGlobalVariable = "ImplicitGlobalVariable",
ImportBinding = "ImportBinding",
Parameter = "Parameter",
TSEnumMember = "TSEnumMemberName",
TSEnumName = "TSEnumName",
TSModuleName = "TSModuleName",
Type = "Type",
Variable = "Variable"
}All definitions extend from a common base that provides essential definition information.
interface DefinitionBase {
/** Unique identifier for this definition instance */
readonly $id: number;
/** The type of this definition */
readonly type: DefinitionType;
/** The identifier node being defined */
readonly name: TSESTree.BindingName;
/** The AST node that creates this definition */
readonly node: TSESTree.Node;
/** The parent AST node containing the definition */
readonly parent: TSESTree.Node | null;
/** True if this definition creates a type binding */
readonly isTypeDefinition: boolean;
/** True if this definition creates a variable binding */
readonly isVariableDefinition: boolean;
}Definitions created by variable declarations (var, let, const).
class VariableDefinition extends DefinitionBase {
readonly type: DefinitionType.Variable;
readonly node: TSESTree.VariableDeclarator;
readonly parent: TSESTree.VariableDeclaration;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Usage Examples:
// VariableDefinition examples:
var x = 1; // VariableDefinition for 'x'
let y = 2; // VariableDefinition for 'y'
const z = 3; // VariableDefinition for 'z'
const [a, b] = arr; // VariableDefinition for 'a' and 'b'
const {name, age} = obj; // VariableDefinition for 'name' and 'age'Definitions created by function declarations and named function expressions.
class FunctionNameDefinition extends DefinitionBase {
readonly type: DefinitionType.FunctionName;
readonly node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Definitions created by function parameters.
class ParameterDefinition extends DefinitionBase {
readonly type: DefinitionType.Parameter;
readonly node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression
| TSESTree.ArrowFunctionExpression;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Definitions created by class declarations and expressions.
class ClassNameDefinition extends DefinitionBase {
readonly type: DefinitionType.ClassName;
readonly node: TSESTree.ClassDeclaration | TSESTree.ClassExpression;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: true;
readonly isVariableDefinition: true;
}Definitions created by catch clause parameters.
class CatchClauseDefinition extends DefinitionBase {
readonly type: DefinitionType.CatchClause;
readonly node: TSESTree.CatchClause;
readonly parent: TSESTree.TryStatement;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Definitions created by import statements.
class ImportBindingDefinition extends DefinitionBase {
readonly type: DefinitionType.ImportBinding;
readonly node: TSESTree.ImportDefaultSpecifier | TSESTree.ImportNamespaceSpecifier
| TSESTree.ImportSpecifier;
readonly parent: TSESTree.ImportDeclaration;
readonly isTypeDefinition: boolean; // Depends on import type
readonly isVariableDefinition: boolean; // Depends on import type
}Definitions for implicit global variables.
class ImplicitGlobalVariableDefinition extends DefinitionBase {
readonly type: DefinitionType.ImplicitGlobalVariable;
readonly node: TSESTree.Program;
readonly parent: null;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Definitions created by TypeScript type declarations.
class TypeDefinition extends DefinitionBase {
readonly type: DefinitionType.Type;
readonly node: TSESTree.TSTypeAliasDeclaration | TSESTree.TSInterfaceDeclaration;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: true;
readonly isVariableDefinition: false;
}Definitions for TypeScript enums and their members.
class TSEnumNameDefinition extends DefinitionBase {
readonly type: DefinitionType.TSEnumName;
readonly node: TSESTree.TSEnumDeclaration;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: true;
readonly isVariableDefinition: true;
}
class TSEnumMemberDefinition extends DefinitionBase {
readonly type: DefinitionType.TSEnumMember;
readonly node: TSESTree.TSEnumMember;
readonly parent: TSESTree.TSEnumDeclaration;
readonly isTypeDefinition: false;
readonly isVariableDefinition: true;
}Definitions created by TypeScript module (namespace) declarations.
class TSModuleNameDefinition extends DefinitionBase {
readonly type: DefinitionType.TSModuleName;
readonly node: TSESTree.TSModuleDeclaration;
readonly parent: TSESTree.Node | null;
readonly isTypeDefinition: true;
readonly isVariableDefinition: true;
}Usage Examples:
import { analyze } from "@typescript-eslint/scope-manager";
import { parse } from "@typescript-eslint/parser";
const code = `
// Variable definitions
const message = "Hello";
let count = 0;
// Function definition
function greet(name: string) { // Parameter definition for 'name'
return message + ", " + name;
}
// Class definition
class User {
constructor(public name: string) {} // Parameter definition for 'name'
}
// Type definitions
interface Point {
x: number;
y: number;
}
type StringOrNumber = string | number;
// Enum definitions
enum Color {
Red, // TSEnumMemberDefinition
Green, // TSEnumMemberDefinition
Blue // TSEnumMemberDefinition
}
// Module definition
namespace Utils {
export function helper() {}
}
// Import definitions
import { parse } from "@typescript-eslint/parser";
// Catch clause definition
try {
// code
} catch (error) { // CatchClauseDefinition for 'error'
console.log(error);
}
`;
const ast = parse(code);
const scopeManager = analyze(ast, { sourceType: 'module' });
// Analyze all definitions
console.log('=== Definition Analysis ===');
scopeManager.variables.forEach(variable => {
console.log(`\nVariable: ${variable.name}`);
console.log(`Definitions: ${variable.defs.length}`);
variable.defs.forEach((def, index) => {
console.log(` Definition ${index}:`);
console.log(` Type: ${def.type}`);
console.log(` Node: ${def.node.type}`);
console.log(` Is type definition: ${def.isTypeDefinition}`);
console.log(` Is variable definition: ${def.isVariableDefinition}`);
if (def.parent) {
console.log(` Parent: ${def.parent.type}`);
}
});
});Analyze definitions by their characteristics:
// Categorize definitions
const allDefinitions: Definition[] = [];
scopeManager.variables.forEach(variable => {
allDefinitions.push(...variable.defs);
});
// Group by definition type
const defsByType = new Map<DefinitionType, Definition[]>();
allDefinitions.forEach(def => {
if (!defsByType.has(def.type)) {
defsByType.set(def.type, []);
}
defsByType.get(def.type)!.push(def);
});
console.log('Definitions by type:');
defsByType.forEach((defs, type) => {
console.log(` ${type}: ${defs.length}`);
});
// Categorize by context
const typeDefs = allDefinitions.filter(def => def.isTypeDefinition);
const valueDefs = allDefinitions.filter(def => def.isVariableDefinition);
const dualDefs = allDefinitions.filter(def =>
def.isTypeDefinition && def.isVariableDefinition
);
console.log('\nDefinitions by context:');
console.log(` Type-only: ${typeDefs.length - dualDefs.length}`);
console.log(` Value-only: ${valueDefs.length - dualDefs.length}`);
console.log(` Dual context: ${dualDefs.length}`);Analyze where definitions occur in the code:
// Analyze definition locations
console.log('=== Definition Location Analysis ===');
const defsByScope = new Map<string, Definition[]>();
scopeManager.scopes.forEach(scope => {
scope.variables.forEach(variable => {
variable.defs.forEach(def => {
const scopeType = scope.type;
if (!defsByScope.has(scopeType)) {
defsByScope.set(scopeType, []);
}
defsByScope.get(scopeType)!.push(def);
});
});
});
defsByScope.forEach((defs, scopeType) => {
console.log(`\n${scopeType} scope:`);
console.log(` Definitions: ${defs.length}`);
const typeCount = new Map<DefinitionType, number>();
defs.forEach(def => {
typeCount.set(def.type, (typeCount.get(def.type) || 0) + 1);
});
typeCount.forEach((count, type) => {
console.log(` ${type}: ${count}`);
});
});Special analysis for import definitions:
// Analyze import definitions
const importDefs = allDefinitions.filter(def =>
def.type === DefinitionType.ImportBinding
) as ImportBindingDefinition[];
console.log('=== Import Definition Analysis ===');
console.log(`Total imports: ${importDefs.length}`);
const importTypes = new Map<string, number>();
importDefs.forEach(def => {
const nodeType = def.node.type;
importTypes.set(nodeType, (importTypes.get(nodeType) || 0) + 1);
});
console.log('Import types:');
importTypes.forEach((count, type) => {
console.log(` ${type}: ${count}`);
});
// Examples:
// ImportDefaultSpecifier: import React from 'react'
// ImportNamespaceSpecifier: import * as React from 'react'
// ImportSpecifier: import { useState } from 'react'Analyze function parameters:
// Analyze parameter definitions
const paramDefs = allDefinitions.filter(def =>
def.type === DefinitionType.Parameter
) as ParameterDefinition[];
console.log('=== Parameter Definition Analysis ===');
console.log(`Total parameters: ${paramDefs.length}`);
paramDefs.forEach(def => {
const functionNode = def.node;
console.log(`Parameter in ${functionNode.type}:`);
console.log(` Name: ${(def.name as TSESTree.Identifier).name}`);
// Analyze parameter patterns
if (def.name.type === 'ObjectPattern') {
console.log(` Destructured object parameter`);
} else if (def.name.type === 'ArrayPattern') {
console.log(` Destructured array parameter`);
} else if (def.name.type === 'RestElement') {
console.log(` Rest parameter`);
}
});Analyze TypeScript type definitions:
// Analyze type definitions
const typeDefs = allDefinitions.filter(def =>
def.type === DefinitionType.Type
) as TypeDefinition[];
console.log('=== Type Definition Analysis ===');
console.log(`Total type definitions: ${typeDefs.length}`);
typeDefs.forEach(def => {
console.log(`Type definition: ${(def.name as TSESTree.Identifier).name}`);
console.log(` Node type: ${def.node.type}`);
if (def.node.type === 'TSInterfaceDeclaration') {
console.log(` Interface definition`);
} else if (def.node.type === 'TSTypeAliasDeclaration') {
console.log(` Type alias definition`);
}
});Install with Tessl CLI
npx tessl i tessl/npm-typescript-eslint--scope-manager