基于Esprima统计JavaScript内置对象出现次数的实现需求
Hey there! I totally get why regex and raw string checks didn't work out for you—JavaScript code is full of edge cases like variable shadowing, nested scopes, and dynamic property access that regex can't possibly parse accurately. Let's switch gears to using an Abstract Syntax Tree (AST) approach, which will give you reliable, precise counts of intrinsic usage.
Why Regex Fails
Think about it: if someone writes const Object = {}; in their code, regex would count that as an "Object" intrinsic reference, but it's actually a local variable shadowing the global intrinsic. Or dynamic access like window['Array']—regex might miss that entirely. AST parsing solves these problems by understanding the actual structure and scope of the code.
Step-by-Step Implementation
We'll use three tools here:
acorn: A fast, lightweight JS parser to generate the ASTescope: A scope analyzer to tell us if an identifier refers to the global intrinsic or a local variableacorn-walk: To traverse the AST nodes
1. Install Dependencies
First, install the required packages:
npm install acorn acorn-walk escope
2. Full Script Code
Create a script (let's call it intrinsic-counter.js) with this code:
const fs = require('fs'); const acorn = require('acorn'); const walk = require('acorn-walk'); const escope = require('escope'); // Define the list of intrinsics you want to track (expand this as needed) const TARGET_INTRINSICS = new Set([ 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'Date', 'RegExp', 'Map', 'Set', 'Promise', 'JSON', 'Math', 'Symbol', 'WeakMap', 'WeakSet', 'Intl' ]); // Read the target JS file const jsCode = fs.readFileSync('./Main.js', 'utf8'); // Parse code into AST (enable latest JS features) const ast = acorn.parse(jsCode, { ecmaVersion: 'latest' }); // Analyze scopes to distinguish global vs local references const scopeManager = escope.analyze(ast); // Initialize count map const intrinsicCounts = {}; TARGET_INTRINSICS.forEach(name => intrinsicCounts[name] = 0); // Traverse the AST to count valid intrinsic references walk.simple(ast, { // Handle direct identifier references (e.g., `Object.create()`) Identifier(node) { const name = node.name; if (!TARGET_INTRINSICS.has(name)) return; // Check if this identifier refers to the global intrinsic const reference = scopeManager.getReference(node); if (!reference) return; // If resolved is null, it's an unresolved global (matches our intrinsics) // Or if resolved scope is global, it's the actual intrinsic if (reference.resolved === null || (reference.resolved?.scope.type === 'global')) { intrinsicCounts[name]++; } }, // Handle dynamic property access (e.g., `window['Array']`) MemberExpression(node) { // Check if the object is the global `window` or `globalThis` const isGlobalObject = node.object.type === 'Identifier' && (node.object.name === 'window' || node.object.name === 'globalThis'); if (!isGlobalObject) return; // Get the property name (handles both dot notation and bracket notation) let propName; if (node.property.type === 'Identifier') { propName = node.property.name; } else if (node.property.type === 'Literal' && typeof node.property.value === 'string') { propName = node.property.value; } if (propName && TARGET_INTRINSICS.has(propName)) { intrinsicCounts[propName]++; } } }); // Print the final counts console.log('=== Intrinsic Usage Counts ==='); Object.entries(intrinsicCounts) .filter(([_, count]) => count > 0) .sort((a, b) => b[1] - a[1]) .forEach(([name, count]) => { console.log(`${name}: ${count} times`); });
3. How It Works
- AST Parsing:
acornconverts your JS code into a structured AST, which represents every part of the code's syntax and logic. - Scope Analysis:
escopetracks variable scopes, so we can tell if anObjectreference is the global intrinsic or a local variable that shadows it. - Node Traversal: We walk through two key node types:
Identifier: Catches direct uses likeArray.push().MemberExpression: Catches dynamic uses likewindow.JSON.parse().
- Counting: We only increment counts for references that are confirmed to be the global intrinsics, not local shadows.
Customization Tips
- Expand Intrinsic List: Add more intrinsics to
TARGET_INTRINSICSbased on what you want to track (e.g.,Intl,Reflect). - Handle JSX/TypeScript: If your users submit TS/JSX code, switch to
@babel/parserinstead ofacorn—it supports these syntaxes out of the box. - Filter False Positives: For even more precision, you could add checks to exclude references in comments or string literals (the current script ignores these since AST doesn't include comment nodes by default; you can enable comment parsing in
acornif needed).
内容的提问来源于stack exchange,提问作者CryoLogic

