Skip to content

Commit 92888fa

Browse files
JavaScript: Templating engine enhancements (#6240)
* JavaScript: Add CaptureValue for typed property access in templates Implement typed captures with property access using a CaptureValue architecture that enables clean syntax like `${method.name}` in templates. Key changes: - Add CaptureValue class that encapsulates property access on captured nodes with a resolve() method - Update Proxy to return CaptureValue for property access (e.g., method.name) - Add getName() method to Capture interface for accessing string names - Simplify Template.apply() to normalize Maps upfront instead of using NormalizedCaptureMap wrapper - Remove NormalizedCaptureMap class (no longer needed) - Update all internal code to use CAPTURE_NAME_SYMBOL for name access - Add tests for property access and array access in templates Benefits: - Cleaner architecture with resolution logic encapsulated in CaptureValue - No string parsing needed - property paths stored directly - User-friendly API - Capture objects work directly as Map keys - Supports deep property chains (method.name.simpleName) - Array access works (elements[0].element) - Simpler implementation without normalization wrapper * JavaScript: Add CaptureValue for typed property access in templates Implement typed captures with property access using a CaptureValue architecture that enables clean syntax like `${method.name}` in templates. Key changes: - Add CaptureValue class that encapsulates property access on captured nodes with a resolve() method - Update Proxy to return CaptureValue for property access (e.g., method.name) - Add getName() method to Capture interface for accessing string names - Simplify Template.apply() to normalize Maps upfront instead of using NormalizedCaptureMap wrapper - Remove NormalizedCaptureMap class (no longer needed) - Update all internal code to use CAPTURE_NAME_SYMBOL for name access - Add tests for property access and array access in templates Benefits: - Cleaner architecture with resolution logic encapsulated in CaptureValue - No string parsing needed - property paths stored directly - User-friendly API - Capture objects work directly as Map keys - Supports deep property chains (method.name.simpleName) - Array access works (elements[0].element) - Simpler implementation without normalization wrapper * Revert accidental change to `comparator.ts` * JavaScript: Implement generic lenient type matching for patterns Implements Feature #6 from ADR 0007 by adding configurable lenient type matching to the pattern matching comparator. This eliminates the need for node-specific workarounds and provides a clean, generic solution. Key changes: - Add lenientTypeMatching parameter to JavaScriptSemanticComparatorVisitor - Implement generic type field comparison in base comparator - Add lenientTypeMatching configuration option to PatternOptions - Remove redundant node-specific overrides from PatternMatchingComparator - Default to lenient matching (true) for backward compatibility - Add comprehensive tests for both lenient and strict matching modes This allows patterns without type annotations to match code with type annotations, enabling faster prototyping while still supporting strict type validation when needed. * JavaScript: Rename 'imports' to 'context' in template options Implements Feature #3 from ADR 0007 by renaming the `imports` option to `context` to better reflect its purpose. The option now supports any type of declaration (imports, types, functions, etc.) that provides context for type attribution, not just import statements. Key changes: - Add `context` field to PatternOptions and TemplateOptions - Deprecate `imports` field with @deprecated JSDoc annotation - Update internal implementation to use `contextStatements` parameter name - Maintain full backward compatibility - `imports` continues to work - Update tests to use new `context` naming - Add comprehensive JSDoc examples showing various context types The `imports` option remains functional as an alias, ensuring no breaking changes for existing code. The new `context` name better communicates that various declaration types can be provided for type attribution. * JavaScript: Implement Builder API for dynamic template construction Implements Feature #5 from ADR 0007 - Builder API for programmatic template and pattern construction when structure is not known at compile time. Implementation: - Added TemplateBuilder class with fluent API (.code(), .param(), .build()) - Added PatternBuilder class with fluent API (.code(), .capture(), .build()) - Added Template.builder() and Pattern.builder() static factory methods - Builders delegate to existing template()/pattern() functions via synthetic TemplateStringsArray - Zero logic duplication - builders just provide alternative construction API Key Features: - Conditional construction: add parts based on runtime logic - Loop-based generation: dynamically create repetitive patterns - Composition: build templates from reusable fragments - Type-safe: full TypeScript support with method chaining Testing: - Comprehensive test suite with 19 tests covering all functionality - Tests for conditional construction, loops, composition, and integration - All 108 test suites pass (956 tests total) Documentation: - Added detailed JSDoc with examples for both builder classes - Clarified capture() behavior: type parameters are for IDE autocomplete only - Improved ADR examples for Feature #2 (Constrained Captures) - Added warnings about structural vs. semantic matching This addresses ADR concerns about template composition (Additional Concerns #8) and provides the foundation for dynamic recipe generation. * JavaScript: Add param() function for template-only parameters Implements Feature #7 from ADR 0007 - provides semantic clarity for standalone templates that don't involve pattern matching. Implementation: - Added TemplateParam interface (simpler than Capture, no property access) - Added TemplateParamImpl class (no Proxy overhead) - Added param<T>(name?) function returning TemplateParam<T> - Updated TemplateParameter type union to include TemplateParam - Updated template processing to handle TemplateParam same as Capture Key Benefits: - Semantic clarity: param() for standalone templates, capture() for patterns - Type safety: Distinct TemplateParam type allows future extensions - Performance: No Proxy overhead since property access isn't needed - Simpler mental model for users When to Use: - param() for standalone templates (no pattern matching) - capture() for patterns and templates used with patterns Example: ```typescript // ✅ GOOD: Use param() for standalone templates const value = param<J.Literal>('value'); template`return ${value} * 2;`.apply(cursor, node, values); // ✅ GOOD: Use capture() with patterns const expr = capture('expr'); pattern`foo(${expr})`; template`bar(${expr})`; ``` Testing: - All 108 test suites pass (956 tests) - No breaking changes - fully backward compatible Documentation: - Comprehensive JSDoc with usage examples - Clear guidance on when to use param() vs capture() - Updated ADR to mark Feature #7 as implemented 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * JavaScript: Add tests for param() function Adds comprehensive test coverage for the param() function to verify: - Basic standalone template usage with param() - Named and unnamed parameters - Multiple parameters in templates - Type verification (TemplateParam vs Capture) All 5 tests pass, demonstrating that param() works correctly for standalone template parameter substitution. * Update ADR * Support for variadic captures * Add support for capture constraints * Polish API for variadic capture constraints * Polish * Add support for non-capturing captures via `any()` * Simplify `capture()` API In most cases client code doesn't really need to use named captures, so the name is now moved into the options. Address some review comments. * Remove variadic `separator` option Turns out we don't need it after all! * Support matching statements in statement blocks * Fix fixtures setup * Fix bug in pattern matching logic * Simplify `JavaScriptComparatorVisitor` * Split templating into multiple submodules * Restore `references` in `tsconfig.json` * Polish --------- Co-authored-by: Claude <[email protected]>
1 parent 46ec2f6 commit 92888fa

38 files changed

+8374
-4577
lines changed

doc/adr/0007-javascript-templating-enhancements.md

Lines changed: 1899 additions & 0 deletions
Large diffs are not rendered by default.

rewrite-javascript/rewrite/fixtures/search-recipe.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export class FindIdentifier extends Recipe {
3737
return new class extends JavaScriptVisitor<ExecutionContext> {
3838
protected async visitIdentifier(ident: J.Identifier, ctx: ExecutionContext): Promise<J | undefined> {
3939
if (ident.simpleName === identifier) {
40-
return foundSearchResult(ident)
40+
return foundSearchResult(ident);
4141
}
4242
return super.visitIdentifier(ident, ctx);
4343
}

rewrite-javascript/rewrite/fixtures/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
"include": [
88
"./**/*.ts"
99
],
10+
"exclude": [],
1011
"references": [
1112
{
1213
"path": "../tsconfig.build.json"
1314
}
1415
]
15-
}
16+
}

rewrite-javascript/rewrite/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"scripts": {
2323
"build": "rm -rf ./dist tsconfig.build.tsbuildinfo && tsc --build tsconfig.build.json",
2424
"postbuild": "chmod +x dist/rpc/server.js && npm link",
25+
"typecheck": "tsc --noEmit",
2526
"dev": "tsc --watch -p tsconfig.json",
2627
"test": "npm run build && NODE_OPTIONS='--max-old-space-size=4096' jest",
2728
"build:fixtures": "tsc --build fixtures/tsconfig.json",

0 commit comments

Comments
 (0)