ts-summit-ast
v0.5.1
Published
TypeScript port of Summit-AST - Abstract Syntax Tree for Salesforce Apex source code
Maintainers
Readme
ts-summit-ast
TypeScript port of Summit-AST - an abstract syntax tree (AST) data structure to represent Salesforce Apex source code.
Overview
This library provides:
- AST node type definitions for Apex language constructs
- Parser-agnostic translation from parse trees to AST nodes
- JSON serialization/deserialization of AST structures
- Zero runtime dependencies - works with any parser that provides parse trees
Installation
npm install ts-summit-astFeatures
- ✅ Complete AST type definitions for Apex language constructs
- ✅ Built-in Apex parser - Direct parsing of Apex source code to AST
- ✅ Parser-agnostic parse tree interface
- ✅ AST translation from parse trees
- ✅ JSON serialization/deserialization
- ✅ Full TypeScript type safety
- ✅ Zero runtime dependencies
- ✅ Source location tracking
- ✅ Visitor pattern support
- ✅ Position-to-node mapping - Find AST nodes at specific source positions
- ✅ Source code extraction - Extract source text from AST nodes
- ✅ Comment-to-node mapping - Map comments to associated AST nodes
- ✅ ApexDoc parsing - Parse ApexDoc comments into structured AST
- ✅ Rule matching - XPath-like pattern matching for AST nodes
- ✅ Node metadata - Comprehensive node information and path tracking
- ✅ AST validation - Validate AST structure and compare AST nodes
- ✅ CLI tool - Command-line interface for processing Apex files
Usage
Basic Example
Using Built-in Parser (Recommended)
import { parseApexCode } from 'ts-summit-ast';
// Parse Apex source code directly to AST
const sourceCode = `
public class Test {
public void method() {
if (x > 0) {
return "positive";
}
}
}
`;
const result = parseApexCode(sourceCode, {
includeLocation: true,
includeComments: true,
});
if (result.ast) {
console.log('AST kind:', result.ast.kind);
// Output: AST kind: ClassDeclaration
}
if (result.errors.length > 0) {
console.error('Parse errors:', result.errors);
}Using Parse Tree Translation (Parser-Agnostic)
import { ASTTranslator, NodeFactory } from 'ts-summit-ast';
import type { ParseTreeNode } from 'ts-summit-ast';
// Create a parse tree (from your parser of choice)
const parseTree: ParseTreeNode = {
type: 'if_statement',
condition: {
type: 'binary_expression',
operator: '>',
left: { type: 'identifier', text: 'x' },
right: { type: 'number_literal', text: '0' },
},
thenBody: {
type: 'return_statement',
expression: { type: 'string_literal', text: '"positive"' },
},
};
// Translate to AST
const translator = new ASTTranslator();
const result = translator.translate(parseTree);
if (result.ast) {
console.log('AST kind:', result.ast.kind);
// Output: AST kind: IfStatement
}Creating AST Nodes Directly
import { NodeFactory } from 'ts-summit-ast';
// Create an identifier (helper node)
const identifier = NodeFactory.createIdentifier('myVariable');
// Create a variable expression (for variable references in expressions)
const varExpr = NodeFactory.createVariableExpression(identifier);
// Create a binary expression
const left = NodeFactory.createIntegerVal(5, '5');
const right = NodeFactory.createIntegerVal(3, '3');
const binaryExpr = NodeFactory.createBinaryExpression('+', left, right);
// Create an if statement
const condition = NodeFactory.createBooleanVal(true);
const thenBody = NodeFactory.createReturnStatement(
NodeFactory.createStringVal('success', '"success"')
);
const ifStmt = NodeFactory.createIfStatement(condition, thenBody);JSON Serialization
import { JsonSerializer, JsonDeserializer } from 'ts-summit-ast';
// Serialize AST to JSON
const serializer = new JsonSerializer();
const jsonString = serializer.serialize(astNode);
// Deserialize JSON back to AST
const deserializer = new JsonDeserializer();
const astNode = deserializer.deserialize(jsonString);
// Round-trip compatibility is maintainedType Guards
import { isIfStatement, isBinaryExpression, isIdentifier } from 'ts-summit-ast';
if (isIfStatement(node)) {
console.log('Condition:', node.condition);
console.log('Then body:', node.thenBody);
}
if (isBinaryExpression(node)) {
console.log('Operator:', node.operator);
console.log('Left:', node.left);
console.log('Right:', node.right);
}Position-to-Node Mapping
import { findNodeAtPosition, getSourceText } from 'ts-summit-ast';
// Find node at specific position
const position = { line: 5, column: 20 };
const result = findNodeAtPosition(ast, position);
if (result) {
console.log('Node type:', result.nodeType);
console.log('Location:', result.location);
// Extract source code for the node
const codeText = getSourceText(result.node, sourceCode);
console.log('Code:', codeText);
}Comment-to-Node Mapping
import { extractComments, findAssociatedNode } from 'ts-summit-ast';
// Extract all comments with associated nodes
const comments = extractComments(ast, sourceCode, {
associateNodes: true,
});
for (const comment of comments) {
if (comment.associatedNode) {
console.log(`Comment "${comment.text}" applies to: ${comment.associatedNode.kind}`);
}
}Rule Matching
import { wouldTriggerRule, findRuleMatches } from 'ts-summit-ast';
// Check if a node matches a rule pattern
const xpath = "//BinaryExpression[@operator='+']";
const result = wouldTriggerRule(node, xpath);
if (result.matches) {
console.log('Node matches rule pattern');
}
// Find all nodes matching a pattern
const matches = findRuleMatches(ast, xpath);
console.log(`Found ${matches.length} matches`);AST Traversal
import { walkAST } from 'ts-summit-ast';
// Walk AST with custom visitor
walkAST(ast, {
enterNode: (node) => {
console.log('Entering:', node.kind);
// Return false to skip children
},
exitNode: (node) => {
console.log('Exiting:', node.kind);
},
});Visitor Pattern
import { ASTVisitor, DefaultVisitor } from 'ts-summit-ast';
class MyVisitor extends DefaultVisitor {
visit(node: ASTNode): void {
console.log('Visiting:', node.kind);
super.visit(node);
}
}
const visitor = new MyVisitor();
// Traverse AST with visitorApexDoc Parsing
import { parseApexDocComment, parseApexCode, extractComments } from 'ts-summit-ast';
// Parse ApexDoc comment directly
const apexDoc = `
/**
* This is a test method.
* @param value The value to test
* @return True if successful
*/
`;
const docAst = parseApexDocComment(apexDoc); // location is optional
if (docAst) {
console.log('Description:', docAst.description);
console.log('Params:', docAst.params);
console.log('Return:', docAst.returns);
}
// Or extract and parse ApexDoc comments from source code
const result = parseApexCode(sourceCode, {
includeComments: true,
});
if (result.ast) {
const comments = extractComments(result.ast, sourceCode, {
parseApexDoc: true, // Parse ApexDoc comments into AST
associateNodes: true, // Associate comments with their nodes
});
for (const comment of comments) {
if (comment.apexDoc) {
console.log('ApexDoc:', comment.apexDoc);
}
}
}AST Validation
import { validateAST, compareASTs, getASTStatistics } from 'ts-summit-ast';
// Validate AST structure
const validation = validateAST(ast);
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
console.warn('Warnings:', validation.warnings);
}
// Compare two ASTs
const comparison = compareASTs(ast1, ast2);
console.log('Are equal:', comparison.equal);
if (!comparison.equal) {
console.log('Differences:', comparison.differences);
}
// Get AST statistics
const stats = getASTStatistics(ast);
console.log('Total nodes:', stats.totalNodes);
console.log('Node counts:', stats.nodeCounts);
console.log('Depth:', stats.maxDepth);Batch Processing
import { parseMultipleFiles, extractCommentsBatch } from 'ts-summit-ast';
// Parse multiple source files
const sources = [
'public class Class1 { }',
'public class Class2 { }',
'public class Class3 { }',
];
const results = parseMultipleFiles(sources, {
includeLocation: true,
});
// Extract comments from multiple files
const commentResults = extractCommentsBatch(
sources.map((s) => ({ source: s, ast: parseApexCode(s).ast! })),
{
associateNodes: true,
}
);Command Line Interface
# Process a single file
npx summit-tool path/to/file.cls
# Process a directory
npx summit-tool path/to/directory --json
# Output JSON with location information
npx summit-tool path/to/file.cls --json --include-location
# Verbose output
npx summit-tool path/to/file.cls --verboseAPI Reference
Core Types
- ASTNode - Base interface for all AST nodes
- Statement - Statement node types (If, ForLoop, WhileLoop, Return, etc.)
- Expression - Expression node types (Binary, Call, VariableExpression, etc.)
- Declaration - Declaration node types (Class, Method, Variable, etc.)
- TypeRef - Type reference data structure (not a node type - used for type information)
- Literal - Literal node types (StringVal, IntegerVal, DoubleVal, LongVal, DecimalVal, BooleanVal, NullVal)
- Identifier - Helper node type (not an expression - used within other nodes)
Main Classes
ASTTranslator
Translates parse trees to AST nodes.
const translator = new ASTTranslator({
includeLocation: true,
continueOnError: false,
});
const result = translator.translate(parseTree);
// result.ast - The translated AST node
// result.errors - Array of translation errorsJsonSerializer
Serializes AST nodes to JSON.
const serializer = new JsonSerializer({
includeLocation: true,
compact: false,
});
const json = serializer.serialize(astNode);JsonDeserializer
Deserializes JSON to AST nodes.
const deserializer = new JsonDeserializer({
validate: true,
});
const astNode = deserializer.deserialize(jsonString);NodeFactory
Factory methods for creating AST nodes.
// Create various node types
NodeFactory.createIdentifier(name, options);
NodeFactory.createIfStatement(condition, thenBody, elseBody, options);
NodeFactory.createBinaryExpression(operator, left, right, options);
// ... and many moreUtility Functions
Position and Node Finding
findNodeAtPosition(ast, position, options)- Find node at specific positionfindNodesInRange(ast, range, options)- Find all nodes in a rangegetSourceText(node, source, options)- Extract source code textgetSourceRange(node)- Get source location range
Comment Mapping
extractComments(ast, source, options)- Extract all comments with node associationsfindAssociatedNode(ast, comment, source, options)- Find node associated with a comment
Rule Matching
wouldTriggerRule(node, xpathExpression, options)- Check if node matches rulefindRuleMatches(ast, xpathExpression, options)- Find all matching nodes
Node Information
getNodePath(node, root)- Get path from root to nodegetNodeMetadata(node, source?)- Get comprehensive node metadatawalkAST(ast, visitor)- Walk AST with visitor patternisNodeType(node, nodeType)- Type guard for specific node typegetParentNode(node, root)- Get parent node of a given nodegetChildNodesByType(ast, nodeType)- Find all child nodes of a specific typefindNodesByType(ast, nodeType)- Find all nodes of a specific type
Apex Parsing
parseApexCode(source, options)- Parse Apex source code to ASTparseApexSource(source)- Parse Apex source code to parse treeparseMultipleFiles(sources, options)- Parse multiple source files
ApexDoc Parsing
parseApexDocComment(comment, location, options)- Parse ApexDoc comment to structured AST
AST Validation
validateAST(ast)- Validate AST structurecompareASTs(ast1, ast2)- Compare two ASTs for equalitygetASTStatistics(ast)- Get statistics about AST structure
Batch Processing
parseMultipleFiles(sources, options)- Parse multiple source filesextractCommentsBatch(fileData, options)- Extract comments from multiple files
Command Line Tool
The library includes a CLI tool (summit-tool) for processing Apex files from the command line.
Installation
The CLI tool is available when you install the package:
npm install ts-summit-astOr use it directly with npx:
npx summit-tool [options] <file-or-directory>Usage
# Process a single file
npx summit-tool path/to/file.cls
# Process a directory recursively
npx summit-tool path/to/directory
# Output as JSON
npx summit-tool path/to/file.cls --json
# Include location information
npx summit-tool path/to/file.cls --json --include-location
# Verbose output
npx summit-tool path/to/file.cls --verboseOptions
--json- Output results as JSON--include-location- Include source location information in output--verbose- Enable verbose output--help- Show help message
Programmatic Usage
You can also use the SummitTool class programmatically:
import { SummitTool } from 'ts-summit-ast';
const tool = new SummitTool({
json: true,
includeLocation: true,
verbose: false,
});
// Process a file
const result = await tool.processFile('path/to/file.cls');
// Process a directory
const results = await tool.processDirectory('path/to/directory');Parser Integration
Since this library is parser-agnostic, you need to provide parse trees from your chosen parser. The parse tree must conform to the ParseTreeNode interface:
interface ParseTreeNode {
readonly type: string;
readonly location?: SourceRange;
readonly text?: string;
readonly children?: ParseTreeNode[];
readonly [key: string]: unknown;
}Example: Integrating with a Parser
import { ASTTranslator } from 'ts-summit-ast';
// import { YourParser } from 'your-parser-library';
// Parse source code with your parser
// const parseTree = yourParser.parse(sourceCode);
// Adapt to ParseTreeNode format if needed
const adaptedTree: ParseTreeNode = {
type: parseTree.nodeType,
children: parseTree.children?.map(adaptNode),
// ... other properties
};
// Translate to AST
const translator = new ASTTranslator();
const result = translator.translate(adaptedTree);Development
Prerequisites
- Node.js >= 18.0.0
- npm or pnpm
Setup
npm install
# or
pnpm installBuild
npm run buildThis generates:
- CommonJS output in
lib/ - ES Modules output in
esm/ - Type definitions in
lib/*.d.ts
Test
npm testRun tests in watch mode:
npm run test:watchRun tests with UI:
npm run test:uiRun tests with coverage:
npm run test:coverageTest Status: 675 tests passing across 14 test files
Code Quality
This project maintains high code quality standards with:
- Strict TypeScript compliance - Full type safety with strict compiler options
- Comprehensive ESLint rules - All TypeScript ESLint recommended, strict, and type-checked rules enabled
- JSDoc documentation - Complete JSDoc coverage with strict validation
- Explicit null/undefined handling - Uses strict boolean expressions and nullish coalescing for robustness
- Zero lint errors - All code passes strict linting rules
Lint
npm run lintFix linting issues automatically:
npm run lint:fixThe project uses:
- TypeScript ESLint - All recommended, strict, stylistic, and type-checked rules
- JSDoc ESLint - Comprehensive JSDoc validation and documentation requirements
- Import/Export linting - Ensures proper module organization
- Code style rules - Consistent formatting and naming conventions
Type Checking
npm run typecheckRuns TypeScript compiler in check-only mode to verify type safety without generating output.
Format
npm run formatCheck formatting:
npm run format:checkDocumentation
Generate API documentation:
npm run docsAll public APIs are fully documented with JSDoc comments, including:
- Function/method descriptions
- Parameter documentation with types and descriptions
- Return value documentation
- Thrown error documentation
- Template type parameters
- File-level overview comments
Project Structure
ts-summit-ast/
├── src/
│ ├── ast/ # AST node type definitions
│ │ ├── nodes/ # Individual node types
│ │ ├── base.ts # Base interfaces
│ │ ├── visitor.ts # Visitor pattern
│ │ └── type-guards.ts
│ ├── parser/ # Parser-agnostic interfaces
│ │ └── ParseTreeTypes.ts
│ ├── translator/ # Parse tree to AST translation
│ │ ├── ASTTranslator.ts
│ │ ├── NodeFactory.ts
│ │ └── ParseTreeVisitor.ts
│ ├── serialization/ # JSON serialization
│ │ ├── JsonSerializer.ts
│ │ └── JsonDeserializer.ts
│ ├── tool/ # CLI tool
│ │ ├── SummitTool.ts
│ │ └── cli.ts
│ ├── utils/ # Utility functions
│ │ ├── apex-parser.ts
│ │ ├── apexdoc-parser.ts
│ │ ├── ast-validation.ts
│ │ ├── comment-utils.ts
│ │ ├── node-finder.ts
│ │ ├── rule-matching.ts
│ │ ├── source-extraction.ts
│ │ └── traversal.ts
│ └── index.ts # Main entry point
├── tests/
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
├── docs/ # Documentation
│ └── json-schema.md # JSON schema documentation
└── README.mdKey Design Decisions
- Parser-agnostic: Works with any parser that provides parse trees. Includes built-in parser for convenience.
- Zero runtime dependencies: Core library has no external runtime dependencies.
- Type-safe: Full TypeScript type safety throughout with discriminated unions and strict compiler options.
- Immutable: AST nodes are immutable (readonly properties).
- Extensible: Easy to add new node types or extend existing ones.
- Robust error handling: Explicit null/undefined checks and strict boolean expressions throughout.
- Comprehensive documentation: Full JSDoc coverage with strict validation for all public APIs.
- High code quality: Strict linting rules, comprehensive test coverage (675 tests), and zero lint errors.
Supported AST Node Types
Statements
- IfStatement, ForLoopStatement, EnhancedForLoopStatement, WhileLoopStatement, DoWhileLoopStatement
- SwitchStatement, TryStatement
- ReturnStatement, BreakStatement, ContinueStatement, ThrowStatement
- CompoundStatement, ExpressionStatement, VariableDeclarationStatement
Expressions
- BinaryExpression, UnaryExpression, AssignExpression
- CallExpression, FieldExpression, ArrayExpression
- NewExpression, CastExpression, InstanceOfExpression, TernaryExpression
- LambdaExpression, VariableExpression, ThisExpression, SuperExpression
- ParenthesizedExpression
- SoqlExpression, SoslExpression
- TriggerContextVariableExpression
Literals
- StringVal, IntegerVal, DoubleVal, LongVal, DecimalVal, BooleanVal, NullVal, CharacterLiteral
Types
- TypeRef - Data structure (not a node type) for type references with components and array nesting
Declarations
- ClassDeclaration, InterfaceDeclaration, MethodDeclaration (with isConstructor flag for constructors)
- VariableDeclaration, PropertyDeclaration, EnumDeclaration
- EnumValue (not a declaration type - used within EnumDeclaration)
JSON Schema
See docs/json-schema.md for complete JSON schema documentation.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Development Guidelines
When contributing, please ensure:
- All tests pass: Run
npm testbefore submitting - No lint errors: Run
npm run lintand fix any issues - Type checking passes: Run
npm run typecheckto verify type safety - Code is formatted: Run
npm run formatto ensure consistent formatting - JSDoc is complete: All public functions, methods, and classes must have complete JSDoc documentation
- Follow strict TypeScript practices: Use explicit null checks, nullish coalescing, and strict boolean expressions
Code Quality Standards
- Use explicit null/undefined checks instead of truthy/falsy checks
- Use nullish coalescing (
??) instead of logical OR (||) for default values - Use strict boolean expressions (explicit comparisons) in conditionals
- Avoid variable shadowing
- Prefer destructuring for array/object access
- Complete JSDoc documentation for all public APIs
License
Apache-2.0
References
Acknowledgments
This project is a TypeScript port of the original Kotlin-based Summit-AST library by Google.
