Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (tree-sorter-ts) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
tree-sorter-ts
A Go CLI tool that automatically sorts TypeScript object literals, arrays, and function parameters marked with special comments. It uses Tree-sitter for accurate AST parsing while preserving exact formatting, comments, and structure.
Features
- 🔧 Sorts object properties alphabetically
- 📊 Sorts array elements with customizable sorting keys
- 🏗️ Sorts constructor/function parameters by name (ignoring modifiers)
- 🎯 Only touches objects/arrays/parameters marked with
/** tree-sorter-ts: keep-sorted **/ - 💬 Preserves all comments (inline and block)
- 🔑 Handles computed property keys like
[EnumName.VALUE] - 📁 Processes files in parallel for performance
- ✨ Supports TypeScript and TSX files
- 🔍 Dry-run mode by default (see changes before applying)
- ✅ Check mode for CI/CD pipelines
- 📐 Optional
with-new-lineformatting for extra spacing - 🚨 Optional
deprecated-at-endto move@deprecatedproperties to the bottom
Installation
Using npm or yarn (Recommended)
# Using npm
npm install --save-dev tree-sorter-ts
# Using yarn
yarn add -D tree-sorter-ts
# Run the installed binary
npx tree-sorter-ts --help
# or
yarn tree-sorter-ts --helpUsing Go
# Using go install
go install github.com/evanrichards/tree-sorter-ts/cmd/tree-sorter-ts@latest
# Using go run
go run github.com/evanrichards/tree-sorter-ts@latest --helpBuilding from source
git clone https://github.com/evanrichards/tree-sorter-ts.git
cd tree-sorter-ts
make build
# Binary will be in ./bin/tree-sorter-ts
./bin/tree-sorter-ts --helpUsage
Basic usage
# Dry-run mode (default) - shows what would change
tree-sorter-ts src/
# Write changes to files
tree-sorter-ts --write src/
# Check mode - exits with code 1 if files need sorting
tree-sorter-ts --check src/
# Check mode with detailed output
tree-sorter-ts --check --verbose src/
# Process a single file
tree-sorter-ts --write src/config.ts
# Process only .ts files (not .tsx)
tree-sorter-ts --extensions=".ts" src/Marking objects for sorting
Add the magic comment before any object literal you want to keep sorted:
const config = {
/** tree-sorter-ts: keep-sorted **/
zebra: "last",
alpha: "first",
beta: "second",
};After running with --write, it becomes:
const config = {
/** tree-sorter-ts: keep-sorted **/
alpha: "first",
beta: "second",
zebra: "last",
};Advanced: with-new-line option
For objects that need extra spacing between properties:
const config = {
/** tree-sorter-ts: keep-sorted with-new-line **/
zebra: "last",
alpha: "first",
beta: "second",
};After sorting:
const config = {
/** tree-sorter-ts: keep-sorted with-new-line **/
alpha: "first",
beta: "second",
zebra: "last",
};Advanced: deprecated-at-end option
Move properties with @deprecated annotations to the bottom of the object:
const config = {
/** tree-sorter-ts: keep-sorted deprecated-at-end **/
activeFeature: true,
/** @deprecated Use newApiUrl instead */
oldApiUrl: "https://old.example.com",
newApiUrl: "https://api.example.com",
legacyMode: true, // @deprecated Will be removed in v2.0
};After sorting:
const config = {
/** tree-sorter-ts: keep-sorted deprecated-at-end **/
activeFeature: true,
newApiUrl: "https://api.example.com",
legacyMode: true, // @deprecated Will be removed in v2.0
/** @deprecated Use newApiUrl instead */
oldApiUrl: "https://old.example.com",
};You can also combine it with with-new-line:
const config = {
/** tree-sorter-ts: keep-sorted deprecated-at-end with-new-line **/
alpha: "first",
beta: "second",
/** @deprecated */
oldValue: "deprecated",
};Multiline magic comments
For better readability, you can split the magic comment across multiple lines:
const config = {
/**
* tree-sorter-ts: keep-sorted
* with-new-line
* deprecated-at-end
*/
activeFeature: true,
beta: "second",
/** @deprecated */
oldFeature: false,
};
// Also works without asterisks on each line:
const config2 = {
/** tree-sorter-ts: keep-sorted
deprecated-at-end
with-new-line **/
gamma: true,
alpha: "first",
};Sorting constructor parameters
Constructor parameters (and function parameters) can be sorted alphabetically by parameter name, ignoring access modifiers:
class UserService {
constructor(
/** tree-sorter-ts: keep-sorted **/
private readonly userRepository: UserRepository,
private readonly logger: Logger,
private readonly cache: CacheService,
private readonly eventBus: EventBus,
) {}
}After running with --write, it becomes:
class UserService {
constructor(
/** tree-sorter-ts: keep-sorted **/
private readonly cache: CacheService,
private readonly eventBus: EventBus,
private readonly logger: Logger,
private readonly userRepository: UserRepository,
) {}
}Features:
- Sorts by parameter name, ignoring modifiers like
private,readonly,public,protected - Works with regular functions, arrow functions, methods, and constructors
- Supports optional parameters (
param?: Type) - Handles destructured parameters (
{ name }: { name: string }) - Preserves parameter types and default values
- Supports all sorting options (
with-new-line,deprecated-at-end)
Examples:
Mixed access modifiers:
class Service {
constructor(
/** tree-sorter-ts: keep-sorted **/
protected readonly zService: ZService,
public aService: AService,
private bService: BService,
) {}
}
// Sorts to: aService, bService, zServiceWith comments and deprecated parameters:
class Service {
constructor(
/** tree-sorter-ts: keep-sorted deprecated-at-end **/
private readonly newService: NewService,
/** @deprecated Use newService instead */
private readonly oldService: OldService,
private readonly activeService: ActiveService,
) {}
}
// Sorts to: activeService, newService, then oldService (deprecated last)Arrow functions and regular functions:
const handler = (
/** tree-sorter-ts: keep-sorted **/
zParam: string,
aParam: number,
mParam: boolean,
) => {}
// Sorts to: aParam, mParam, zParamSorting arrays
Arrays can also be sorted by placing the magic comment inside the array:
const users = [
/** tree-sorter-ts: keep-sorted key="name" **/
{ name: "Zoe", age: 30 },
{ name: "Alice", age: 25 },
{ name: "Bob", age: 28 },
];After sorting:
const users = [
/** tree-sorter-ts: keep-sorted key="name" **/
{ name: "Alice", age: 25 },
{ name: "Bob", age: 28 },
{ name: "Zoe", age: 30 },
];Array sorting options
Sort by object property:
const items = [
/** tree-sorter-ts: keep-sorted key="priority" **/
{ name: "Task C", priority: 3 },
{ name: "Task A", priority: 1 },
{ name: "Task B", priority: 2 },
];Sort by array index (for tuples):
const data = [
/** tree-sorter-ts: keep-sorted key="1" **/
["apple", 5, true],
["banana", 2, false],
["cherry", 8, true]
];
// Sorts by the second element (index 1): 2, 5, 8Sort scalar arrays (no key needed):
const numbers = [
/** tree-sorter-ts: keep-sorted **/
5, 2, 8, 1, 9
];
// Result: [1, 2, 5, 8, 9]
const words = [
/** tree-sorter-ts: keep-sorted **/
"banana", "apple", "cherry"
];
// Result: ["apple", "banana", "cherry"]Nested property access:
const users = [
/** tree-sorter-ts: keep-sorted key="profile.firstName" **/
{ profile: { firstName: "Charlie", lastName: "Brown" } },
{ profile: { firstName: "Alice", lastName: "Smith" } },
{ profile: { firstName: "Bob", lastName: "Jones" } }
];With options (with-new-line and deprecated-at-end):
const features = [
/** tree-sorter-ts: keep-sorted key="name" deprecated-at-end with-new-line **/
{ name: "Feature C", enabled: true },
/** @deprecated */
{ name: "Feature A", enabled: false },
{ name: "Feature B", enabled: true },
];Results in:
const features = [
/** tree-sorter-ts: keep-sorted key="name" deprecated-at-end with-new-line **/
{ name: "Feature B", enabled: true },
{ name: "Feature C", enabled: true },
/** @deprecated */
{ name: "Feature A", enabled: false },
];Graceful handling of missing keys: Elements without the specified key are sorted to the end:
const mixed = [
/** tree-sorter-ts: keep-sorted key="id" **/
{ id: 3, name: "Three" },
{ name: "No ID" }, // Missing 'id' - will sort to end
{ id: 1, name: "One" },
];
// Result: elements with 'id' first (sorted), then elements without 'id'Sorting by comment content
Both arrays and objects can be sorted by their associated comment content using the sort-by-comment option:
const userIds = [
/** tree-sorter-ts: keep-sorted sort-by-comment **/
"u_8234", // Bob Smith
"u_9823", // Alice Johnson
"u_1234", // David Lee
"u_4521", // Carol White
];After sorting:
const userIds = [
/** tree-sorter-ts: keep-sorted sort-by-comment **/
"u_9823", // Alice Johnson
"u_8234", // Bob Smith
"u_4521", // Carol White
"u_1234", // David Lee
];Features:
- Works with inline comments (
// commentor/* comment */) - Works with preceding comments (comments on lines before the element)
- Supports multiline comments
- Compatible with
deprecated-at-endoption - Cannot be used together with
keyoption (will show an error)
Examples:
Preceding comments:
const items = [
/** tree-sorter-ts: keep-sorted sort-by-comment **/
// B
"first",
/**
* A
*/
"second",
// C
"third"
];
// Sorts to: second (A), first (B), third (C)Mixed comment positions:
const mixed = [
/** tree-sorter-ts: keep-sorted sort-by-comment **/
// Delta (before)
"item1",
"item2", // Beta (after)
/**
* Alpha (before multiline)
*/
"item3",
"item4" /* Charlie (after block) */
];
// Sorts by: Alpha, Beta, Charlie, DeltaObjects with comment sorting:
const config = {
/** tree-sorter-ts: keep-sorted sort-by-comment **/
// Production settings
prodUrl: "https://api.example.com",
// Development settings
devUrl: "http://localhost:3000",
// Staging settings
stagingUrl: "https://staging.example.com",
};
// Sorts by: Development, Production, StagingKnown limitation: Object sorting with inline comments (after property values) currently has a bug where the last property may get a duplicated comment. As a workaround, use preceding comments for objects or use the default property-name sorting.
Flags
--check- Check if files are sorted (exit 1 if not)--write- Write changes to files (default: dry-run)--recursive- Process directories recursively (default: true)--extensions- File extensions to process (default: ".ts,.tsx")--workers- Number of parallel workers (default: number of CPUs)--verbose- Show detailed output (default: false)
Examples
CI/CD Integration
The --check mode provides clear, CI-friendly output that shows exactly which files need sorting:
# When files need sorting:
$ tree-sorter-ts --check src/
✗ src/config.ts needs sorting (3 items)
✗ src/services/user.ts needs sorting (1 items)
Processed 15 files
❌ 2 file(s) need sorting
4 item(s) need to be sorted
Error: some files are not properly sorted# GitHub Actions example
- name: Install tree-sorter-ts
run: npm install --save-dev tree-sorter-ts
- name: Check TypeScript objects are sorted
run: npx tree-sorter-ts --check src/The tool exits with code 1 and provides specific file paths when sorting is needed, making it easy to identify issues in CI logs.
Pre-commit Hook
#!/bin/sh
# .git/hooks/pre-commit
tree-sorter-ts --write $(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx)$')With Make
# Run on all TypeScript files
make run
# Check mode
make check
# Run tests
make testDevelopment
Project Structure
The codebase follows a modular architecture with clear separation of concerns:
tree-sorter-ts/
├── cmd/tree-sorter-ts/ # CLI entry point
├── internal/
│ ├── app/ # Application coordination
│ ├── fileutil/ # File system utilities
│ ├── processor/ # Main processing logic
│ │ ├── ast.go # Legacy monolithic processor
│ │ ├── processor.go # New modular processor
│ │ └── *_test.go # Comprehensive test suite
│ ├── config/ # Configuration parsing
│ │ └── sort_config.go # Magic comment configuration
│ ├── parser/ # AST parsing utilities
│ │ └── magic_comments.go # Find sortable structures
│ ├── sorting/ # Core sorting abstractions
│ │ ├── interfaces/ # Core interfaces
│ │ ├── strategies/ # Sorting strategies (plugin-based)
│ │ │ ├── property_name.go # Default alphabetical sorting
│ │ │ ├── comment_content.go # Sort by comment content
│ │ │ └── array_key.go # Array key-based sorting
│ │ ├── types/ # Type-specific implementations
│ │ │ ├── arrays/ # Array sorting logic
│ │ │ └── objects/ # Object sorting logic
│ │ └── common/ # Shared utilities
│ └── reconstruction/ # AST reconstruction
│ ├── array_reconstructor.go # Rebuild sorted arrays
│ └── object_reconstructor.go # Rebuild sorted objects
├── testdata/fixtures/ # Test files
└── main.go # Root entry (for backward compatibility)Architecture Overview
The new modular architecture separates concerns into distinct packages:
- Configuration (
config/) - Parses and validates magic comment options - Parser (
parser/) - Finds structures marked for sorting in the AST - Sorting (
sorting/) - Core sorting logic with plugin-based strategies - Reconstruction (
reconstruction/) - Rebuilds the AST with sorted content
Key Design Patterns
Strategy Pattern: Different sorting strategies (property-name, comment-content, array-key) implement a common interface, allowing easy extension:
type SortStrategy interface {
ExtractKey(item SortableItem, content []byte) (string, error)
GetName() string
}Factory Pattern: Factories create appropriate strategies and reconstructors based on configuration:
strategyFactory := strategies.NewFactory()
strategy, err := strategyFactory.CreateStrategy(config)Interface-Driven Design: Core interfaces allow different types (arrays, objects, constructors) to be handled uniformly:
type Sortable interface {
Extract(node *sitter.Node, content []byte) ([]SortableItem, error)
Sort(items []SortableItem, strategy SortStrategy, deprecatedAtEnd bool, content []byte) ([]SortableItem, error)
CheckIfSorted(items []SortableItem, strategy SortStrategy, deprecatedAtEnd bool, content []byte) bool
}Processing Flow
- Parse - Tree-sitter parses TypeScript/TSX into an AST
- Find - Locate structures with magic comments
- Extract - Extract sortable items (properties, elements, parameters)
- Sort - Apply the appropriate sorting strategy
- Reconstruct - Rebuild the AST content with sorted items
- Write - Update the file with sorted content
This architecture makes it easy to:
- Add new sorting strategies
- Support new structure types
- Test individual components
- Maintain and debug the codebase
Building and Testing
# Build the binary
make build
# Run tests
make test
# Run benchmarks
make bench
# Install locally
make installTODO
File-level declaration sorting - Sort top-level declarations like
export const,const,function, etc./** tree-sorter-ts: keep-sorted **/ export const CONFIG = { ... }; const helper = () => { ... }; export function processData() { ... } export const API_KEY = "..."; const cache = new Map(); // Would sort to (exports first, then by declaration name): /** tree-sorter-ts: keep-sorted **/ export const API_KEY = "..."; export const CONFIG = { ... }; export function processData() { ... } const cache = new Map(); const helper = () => { ... };
Features to include:
- Sort by declaration name (including the modifier like
export) - Support all sorting options except
key(not applicable) with-new-linefor spacing between declarationsdeprecated-at-endfor@deprecateddeclarationssort-by-commentfor organizing by comment content- Natural grouping: exports together, non-exports together
- Sort by declaration name (including the modifier like
Section sorting - Support
start-sortandend-sortcomments for sorting subsectionsconst config = { // Critical settings - do not sort apiUrl: "https://api.example.com", timeout: 5000, /** tree-sorter-ts: start-sort **/ featureFlags: { enableAnalytics: true, enableChat: false, enableNotifications: true, }, permissions: { canEdit: true, canDelete: false, canView: true, }, /** tree-sorter-ts: end-sort **/ // Debug settings - must remain last debug: true, };
Class member sorting - Sort class members including decorators and annotations
class APIClient { /** tree-sorter-ts: keep-sorted **/ @deprecated() legacyEndpoint: string; @inject() httpClient: HttpClient; apiKey: string; baseUrl: string; @observable() isLoading: boolean; }
License
MIT