VarMap is a tool that analyzes JavaScript code to create canonical variable mappings, enabling semantic comparison of code where identifiers have been renamed (e.g., minified JavaScript with randomized variable names).
- Scope Analysis: Parse JavaScript using tree-sitter to build a complete scope tree
- Canonicalization: Rename all identifiers to canonical forms based on scope and declaration order
- Mapping Generation: Create bidirectional mappings between original and canonical names
- Multiple Output Formats: Support JSON, YAML, and custom formats for mappings
- Comparing different versions of minified JavaScript
- Analyzing obfuscated code changes over time
- Creating semantic diffs that ignore variable name randomization
- Security research on minified/obfuscated JavaScript
- tree-sitter: Core parsing engine
- tree-sitter-javascript: JavaScript grammar
- Language: Rust (recommended) or Go for performance and tree-sitter integration
- Initialize tree-sitter with JavaScript grammar
- Parse input JavaScript into concrete syntax tree
- Handle syntax errors gracefully (important for minified code)
- Traverse AST to identify scope boundaries:
- Global scope
- Function scopes
- Block scopes (let/const)
- Module scopes (import/export)
- Track variable bindings and their relationships
- Handle JavaScript-specific scoping rules:
- Hoisting (var, function declarations)
- Temporal dead zones (let/const)
- Closure captures
- Rename identifiers using consistent naming scheme:
- Functions:
fn_1,fn_2,fn_3(by declaration order) - Function parameters:
param_1,param_2(within each function) - Variables:
var_1,var_2(within each scope) - Class names:
class_1,class_2 - Method names:
method_1,method_2
- Functions:
- Preserve semantic meaning through consistent ordering
- Handle edge cases:
- Anonymous functions
- Destructuring assignments
- Property access chains
- Create bidirectional identifier mappings
- Track scope relationships
- Generate mapping metadata (declaration positions, scope depth, etc.)
; Function declarations and expressions
(function_declaration name: (identifier) @function-name)
(function_expression name: (identifier) @function-name)
(arrow_function)
; Variable declarations
(variable_declarator name: (identifier) @variable-name)
(formal_parameters (identifier) @parameter-name)
; All identifier references
(identifier) @identifier
; Class declarations
(class_declaration name: (identifier) @class-name)
(method_definition name: (property_name) @method-name)# Canonicalize JavaScript (rename to fn_1, param_1, etc.)
varmap <input-file> > <canonical-file>
# Generate mapping template for editing
varmap <input-file> --map > <map-file>
# Apply edited mappings to create semantic version
varmap <input-file> --map <map-file> > <semantic-file>
OPTIONS:
--map [file] Generate mapping template (no file) or apply mappings (with file)
--preserve-comments Keep comments in output
--verbose Show detailed scope analysis to stderr
-h, --help Show help- JavaScript files (minified or regular)
- Supports ES5, ES6+, CommonJS, ES modules
- Handle syntax errors gracefully
When run without --map, outputs JavaScript with canonical identifiers:
// Input: function a(b,c){var d=b+c;return d;}
// Output: function fn_1(param_1,param_2){var var_1=param_1+param_2;return var_1;}When run with --map (no file), outputs line-based mapping format:
# LINE:COL TYPE SCOPE ORIGINAL CANONICAL NEW_NAME CONTEXT
1:10 func global a fn_1 fn_1 "function a(b,c){var d=b+c;return d;}"
1:12 param func_a b param_1 param_1 "parameter used in addition: b+c"
1:14 param func_a c param_2 param_2 "parameter used in addition: b+c"
1:20 var func_a d var_1 var_1 "stores result of b+c, then returned"
1:32 ref func_a d var_1 var_1 "return value"
Field Descriptions:
- LINE:COL: Position in source file
- TYPE: func, param, var, ref (function, parameter, variable declaration, reference)
- SCOPE: Scope identifier (global, func_1, etc.)
- ORIGINAL: Original identifier from source
- CANONICAL: Generated canonical name (fn_1, param_1, etc.)
- NEW_NAME: User-editable semantic name (initially same as CANONICAL)
- CONTEXT: Code context for human/LLM understanding
When run with --map <file>, outputs JavaScript using NEW_NAME mappings:
// After editing map file NEW_NAME column:
// fn_1 → addNumbers, param_1 → firstNumber, etc.
// Output: function addNumbers(firstNumber,secondNumber){var sum=firstNumber+secondNumber;return sum;}- Set up tree-sitter with JavaScript grammar
- Basic AST traversal and node identification
- Simple identifier extraction
- Basic scope boundary detection
- Complete scope tree construction
- Variable binding resolution
- Handle JavaScript scoping edge cases
- Closure capture detection
- Implement naming scheme
- Consistent identifier replacement
- Preserve code structure and formatting
- Handle destructuring and complex assignments
- Bidirectional mapping creation
- JSON output format
- Position tracking
- Metadata collection
- Command-line interface
- Multiple output formats
- Error handling and reporting
- Documentation and examples
# Compare two versions of minified code structurally
varmap version1.min.js > canonical1.js
varmap version2.min.js > canonical2.js
diff canonical1.js canonical2.js# Generate canonical version
varmap minified.js > canonical.js
# Create mapping template for editing
varmap minified.js --map > minified.map
# Edit minified.map (change NEW_NAME column to semantic names)
# Then apply the mappings
varmap minified.js --map minified.map > readable.js# Generate mapping template and process with LLM
varmap input.js --map | llm-rename-variables > edited.map
varmap input.js --map edited.map > semantic.js
# Or in a pipeline
varmap input.js --map | llm process | varmap input.js --map /dev/stdin > output.js// Input
function outer(x) {
var y = 1;
function inner(z) {
return x + y + z;
}
return inner;
}
// Expected canonical
function fn_1(param_1) {
var var_1 = 1;
function fn_2(param_1) {
return param_1 + var_1 + param_1;
}
return fn_2;
}- Hoisted variables and functions
- let/const temporal dead zones
- Arrow functions and implicit returns
- Destructuring assignments
- Class methods and constructors
- Import/export statements
- Anonymous functions
- IIFE patterns
- Accuracy: Correctly identifies and maps 99%+ of identifiers in well-formed JavaScript
- Performance: Processes typical minified libraries (1MB+) in under 5 seconds
- Robustness: Handles syntax errors and malformed input gracefully
- Consistency: Same input always produces identical canonical output
- Completeness: Supports all modern JavaScript features (ES2023+)
- TypeScript support
- Source map integration
- Semantic hints (detect common patterns like event handlers)
- Integration with existing diff tools
- VSCode extension for live mapping
- API mode for programmatic usage