Sophisticated system for tracking variable and module references across different import/export patterns. The ReferenceTracker class enables ESLint rules to understand cross-module dependencies and trace variable usage through complex import/export scenarios.
Core class for tracking references across modules and scopes.
/**
* The reference tracker for tracking variable references across modules
* @param globalScope - The global scope to start tracking from
* @param options - Optional configuration for tracking behavior
*/
class ReferenceTracker {
constructor(globalScope: Scope, options?: ReferenceTrackerOptions);
/**
* Iterate the references of global variables
* @param traceMap - Map defining what to track
* @returns Iterator of reference results
*/
iterateGlobalReferences(traceMap: TraceMap): IterableIterator<ReferenceResult>;
/**
* Iterate the references of CommonJS modules
* @param traceMap - Map defining what to track
* @returns Iterator of reference results
*/
iterateCjsReferences(traceMap: TraceMap): IterableIterator<ReferenceResult>;
/**
* Iterate the references of ES modules
* @param traceMap - Map defining what to track
* @returns Iterator of reference results
*/
iterateEsmReferences(traceMap: TraceMap): IterableIterator<ReferenceResult>;
}
interface ReferenceTrackerOptions {
mode?: "legacy" | "strict";
globalObjectNames?: string[];
}
interface ReferenceResult {
node: Node;
path: string[];
type: symbol;
info: any;
}
interface TraceMap {
[key: string]: {
[READ]?: any;
[CALL]?: any;
[CONSTRUCT]?: any;
[key: string]: TraceMap | any;
};
}Symbols used to specify what types of references to track.
/**
* Symbol representing read access tracking
*/
const READ: symbol;
/**
* Symbol representing function call tracking
*/
const CALL: symbol;
/**
* Symbol representing constructor call tracking
*/
const CONSTRUCT: symbol;
/**
* Symbol representing ECMAScript module tracking
*/
const ESM: symbol;Static Properties:
ReferenceTracker class also provides these constants as static properties:
/**
* Static property for read access tracking
*/
static READ: symbol;
/**
* Static property for function call tracking
*/
static CALL: symbol;
/**
* Static property for constructor call tracking
*/
static CONSTRUCT: symbol;
/**
* Static property for ECMAScript module tracking
*/
static ESM: symbol;Static Access:
// Available as static properties
ReferenceTracker.READ
ReferenceTracker.CALL
ReferenceTracker.CONSTRUCT
ReferenceTracker.ESMTrack usage of global variables and objects.
import { ReferenceTracker, READ, CALL } from "eslint-utils";
create(context) {
return {
Program() {
const scope = context.getScope();
const tracker = new ReferenceTracker(scope);
const traceMap = {
console: {
[READ]: true,
log: { [CALL]: true },
warn: { [CALL]: true },
error: { [CALL]: true }
},
setTimeout: { [CALL]: true },
setInterval: { [CALL]: true }
};
for (const { node, path, type } of tracker.iterateGlobalReferences(traceMap)) {
if (type === READ) {
context.report(node, `Reading global: ${path.join('.')}`);
} else if (type === CALL) {
context.report(node, `Calling global: ${path.join('.')}`);
}
}
}
};
}Track CommonJS require() calls and their usage.
import { ReferenceTracker, READ, CALL, CONSTRUCT } from "eslint-utils";
create(context) {
return {
Program() {
const scope = context.getScope();
const tracker = new ReferenceTracker(scope);
const traceMap = {
"lodash": {
[READ]: true,
map: { [CALL]: true },
filter: { [CALL]: true },
reduce: { [CALL]: true }
},
"express": {
[CALL]: true, // express()
Router: { [CONSTRUCT]: true } // new express.Router()
},
"fs": {
readFile: { [CALL]: true },
writeFile: { [CALL]: true }
}
};
for (const { node, path, type } of tracker.iterateCjsReferences(traceMap)) {
if (path[0] === "lodash" && type === CALL) {
context.report(node, `Using lodash.${path.slice(1).join('.')}`);
} else if (path[0] === "fs") {
context.report(node, `File system operation: ${path.join('.')}`);
}
}
}
};
}Track ES6 import statements and their usage.
import { ReferenceTracker, READ, CALL, ESM } from "eslint-utils";
create(context) {
return {
Program() {
const scope = context.getScope();
const tracker = new ReferenceTracker(scope);
const traceMap = {
"react": {
[ESM]: true, // Mark as ES module
createElement: { [CALL]: true },
useState: { [CALL]: true },
useEffect: { [CALL]: true },
Component: { [READ]: true }
},
"vue": {
[ESM]: true,
createApp: { [CALL]: true },
ref: { [CALL]: true },
computed: { [CALL]: true }
}
};
for (const { node, path, type } of tracker.iterateEsmReferences(traceMap)) {
if (path[0] === "react" && path[1] === "useState" && type === CALL) {
context.report(node, "useState hook detected");
} else if (path[0] === "vue" && type === CALL) {
context.report(node, `Vue composition API: ${path.slice(1).join('.')}`);
}
}
}
};
}Complex trace maps with nested structures.
import { ReferenceTracker, READ, CALL, CONSTRUCT } from "eslint-utils";
create(context) {
return {
Program() {
const scope = context.getScope();
const tracker = new ReferenceTracker(scope, {
mode: "strict",
globalObjectNames: ["window", "global", "globalThis"]
});
const traceMap = {
"axios": {
[CALL]: { info: "axios direct call" },
get: { [CALL]: { info: "axios.get" } },
post: { [CALL]: { info: "axios.post" } },
create: {
[CALL]: { info: "axios.create" },
// Track methods on axios instances
"*": {
get: { [CALL]: { info: "instance.get" } },
post: { [CALL]: { info: "instance.post" } }
}
}
},
"jquery": {
[CALL]: { info: "jQuery function" },
[READ]: { info: "jQuery object" },
ajax: { [CALL]: { info: "jQuery.ajax" } },
fn: {
[READ]: { info: "jQuery.fn" },
extend: { [CALL]: { info: "jQuery.fn.extend" } }
}
}
};
// Track CommonJS requires
for (const { node, path, type, info } of tracker.iterateCjsReferences(traceMap)) {
context.report({
node,
message: `${info}: ${path.join('.')} (${type.toString()})`
});
}
// Track ES module imports
for (const { node, path, type, info } of tracker.iterateEsmReferences(traceMap)) {
context.report({
node,
message: `ES Module ${info}: ${path.join('.')}`
});
}
}
};
}Handle destructuring patterns in imports.
import { ReferenceTracker, READ, CALL } from "eslint-utils";
create(context) {
return {
Program() {
const scope = context.getScope();
const tracker = new ReferenceTracker(scope);
// This will track:
// const { readFile, writeFile } = require('fs');
// import { useState, useEffect } from 'react';
const traceMap = {
"fs": {
readFile: { [CALL]: true },
writeFile: { [CALL]: true },
promises: {
readFile: { [CALL]: true },
writeFile: { [CALL]: true }
}
},
"react": {
[ESM]: true,
useState: { [CALL]: true },
useEffect: { [CALL]: true },
useMemo: { [CALL]: true }
}
};
for (const { node, path, type } of tracker.iterateCjsReferences(traceMap)) {
if (path.length > 1 && type === CALL) {
context.report(node, `Destructured call: ${path.join('.')}`);
}
}
for (const { node, path, type } of tracker.iterateEsmReferences(traceMap)) {
if (path[0] === "react" && type === CALL) {
context.report(node, `React hook: ${path[1]}`);
}
}
}
};
}Control how the tracker handles import declarations:
const tracker = new ReferenceTracker(scope, { mode: "legacy" });Customize which global object names to consider:
const tracker = new ReferenceTracker(scope, {
globalObjectNames: ["window", "global", "globalThis", "self"]
});const traceMap = {
"deprecated-lib": {
[READ]: { deprecated: true },
"*": { [CALL]: { deprecated: true } }
}
};
for (const { node, info } of tracker.iterateCjsReferences(traceMap)) {
if (info.deprecated) {
context.report(node, "This library is deprecated");
}
}const traceMap = {
"eval": { [CALL]: { security: "dangerous" } },
"child_process": {
exec: { [CALL]: { security: "review-required" } },
spawn: { [CALL]: { security: "review-required" } }
}
};const traceMap = {
"heavy-library": {
[READ]: { performance: "expensive" },
heavyOperation: { [CALL]: { performance: "very-expensive" } }
}
};