Skip to content

JavaScript: add capture.derived() for transformed capture values in templates#6954

Draft
knutwannheden wants to merge 1 commit intomainfrom
feat/capture-derived
Draft

JavaScript: add capture.derived() for transformed capture values in templates#6954
knutwannheden wants to merge 1 commit intomainfrom
feat/capture-derived

Conversation

@knutwannheden
Copy link
Copy Markdown
Contributor

@knutwannheden knutwannheden commented Mar 12, 2026

Example

Before — dynamic after function required to transform a captured value:

const unit = capture({
    name: 'unit',
    constraint: (n) => isLiteral(n) && UNIT_MAP[n.value] !== undefined
});
return {
    before: pattern`${obj}.add(${amount}, ${unit})`,
    after: (match) => {
        const unitStr = (match.get(unit) as J.Literal).value as string;
        return template`${obj}.add({${raw(UNIT_MAP[unitStr])}: ${amount}})`;
    }
};

After — fully declarative with capture.derived():

const unit = capture({
    name: 'unit',
    constraint: (n) => isLiteral(n) && UNIT_MAP[n.value] !== undefined
});
const temporalUnit = capture.derived(unit, (node) => {
    return raw(UNIT_MAP[(node as J.Literal).value as string]);
});
return {
    before: pattern`${obj}.add(${amount}, ${unit})`,
    after: template`${obj}.add({${temporalUnit}: ${amount}})`,
};

Summary

  • Add capture.derived(source, transform, options?) that computes template substitutions from another capture's matched value
  • DerivedCapture resolves at template application time: looks up the source capture in match results, applies the transform function, and uses the result for substitution
  • Transform can return RawCode (for identifier/property name positions) or a J node (for direct AST substitution)
  • Throws if used in a before pattern (derived captures have no matching behavior)
  • Supports optional { type } for type attribution, same as regular captures
  • Eliminates the need for dynamic after: (match) => Template in many rewrite rules, keeping rules fully declarative

Test plan

  • Derived capture with raw() output maps a captured literal value to a property name
  • Derived capture returning a J node directly passes through unchanged
  • Using a derived capture in a before pattern throws a clear error
  • End-to-end rewrite rule using derived capture instead of dynamic after
  • Different matched values produce correct transformed outputs
  • All 245 existing templating tests still pass (no regressions)
  • TypeScript typecheck passes cleanly

@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Mar 12, 2026
@knutwannheden knutwannheden changed the title feat: add capture.derived() for transformed capture values in templates JavaScript: add capture.derived() for transformed capture values in templates Mar 12, 2026
@knutwannheden knutwannheden added enhancement New feature or request javascript labels Mar 12, 2026
Derived captures compute their template substitution from another
capture's matched value, eliminating the need for dynamic `after`
functions in many rewrite rules. The transform function receives the
matched node and returns either a RawCode (for identifier/property
name substitution) or a J node (for direct AST substitution).

Derived captures throw if used in patterns (they have no matching
behavior) and support optional type attribution for proper parsing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request javascript

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant