Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeWriter rehaul to allow typed sections #1110

Merged
merged 8 commits into from
Mar 2, 2022
Merged

Conversation

mtdowling
Copy link
Member

@mtdowling mtdowling commented Feb 27, 2022

This commit is a major refactor to CodeWriter to introduce a recursively typed AbstractCodeWriter that allows fluent interfaces to work with subtypes and allows the introduction of strongly typed section interceptors to CodeWriter.

This adds a major new feature to CodeWriter: typed sections and interceptors. This provides a much safer plugin system that can be used in code generators. Because the binding of an "interceptor" to a "section" is an interface in and of itself, code generation plugin systems don't need to actually hand off CodeWriters to plugins but rather just ask plugins for interceptors.

// Say you have your own AbstractCodeWriter subclass:
MyWriter writer = new MyWriter();

// Say you have an event named NameEvent that can be intercepted.
// The following lines register section interceptors for that event that just
// append to any previously written text in the section. The first interceptor
// uses one of the accessor methods of the provided NameEvent object.
writer.onSection(CodeInterceptor.appender(NameEvent.class, (w, section) -> w.write("$L", section.name())));
writer.onSection(CodeInterceptor.appender(NameEvent.class, (w, section) -> w.write("Foo")));
writer.onSection(CodeInterceptor.appender(NameEvent.class, (w, section) -> w.write("Bar")));

// injectSection is a shortcut for pushState(section).popState().
// popState() is when code interceptors are applied.
writer.injectSection(new NameEvent("Thomas"));

assert(writer.toString().equals("Name?\nThomas\nFoo\nBar\n"));

You can create a strongly typed section by passing a CodeSection to injectSection or pushState. A CodeSection is just an interface with a sectionName() method that returns the name of the class by default.

record NameEvent(String name) implements CodeSection;

NameEvent event = new NameEvent("Thomas");

You can create CodeInterceptors by implementing CodeInterceptor, or even better, CodeInterceptor.Appender / CodeInterceptor.Prepender that perform writing the previously emitted content correctly (only if non-empty and using writeInlineWithNoFormatting).

class SpecificNameInterceptor extends CodeInterceptor.Appender<NameEvent, MyWriter>() {
    private final String name;

    public SpecificNameInterceptor(String name) {
        this.name = name;
    }

    @Override
    public Class< NameEvent > sectionType() {
        return NameEvent.class;
    }

    @Override
    public void append(MyWriter writer, NameEvent section) {
        if (section.name().equals(name)) {
            writer.write("Hi, $L!", name);
        }
    }
}

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@mtdowling mtdowling requested a review from a team as a code owner February 27, 2022 23:06
@mtdowling mtdowling force-pushed the typed-codewriter-sections branch from bf4e901 to 5459c0c Compare February 28, 2022 06:23
@mtdowling mtdowling force-pushed the typed-codewriter-sections branch from 5459c0c to 6d66e90 Compare February 28, 2022 17:52
This commit is a major refactor to CodeWriter to introduce a recursively
typed AbstractCodeWriter that allows fluent interfaces to work with
subtypes and allows the introduction of strongly typed section
interceptors to CodeWriter.
@mtdowling mtdowling force-pushed the typed-codewriter-sections branch from 6d66e90 to 21255d0 Compare March 1, 2022 04:02
These methods are extremely rarely used and have been obsoleted by
onSection with a custom interceptor. Any existing usage based on
CodeWriter will continue to work using the deprecated methods that were
moved to CodeWriter.
CodeWriter newlines now allow more explicit control when intercepting
sections. The value of a section is intercepted as-is and can be written
back to the section verbatim using writeInlineWithNoFormatting. However,
the contents of $C and inline sections still trim a trailing newline
since newlines for these features are controlled by the call to
write/writeInline/etc that contained the formatter or inline section
definition.
The Smithy Kotlin generator uses the call method which was removed
during the refactor. Adding that back, and renaming the newly added call
method that just statically types a `Consumer` to a method named
`consumer`.
This commit updates onSection to preserve the previous newline behavior
of `onSection(String, Consumer<String>)` while still providing explicit
control over newlines in `onSection(CodeInterceptor)`. This ensures that
existing users of CodeWriter don't break, while allowing new integration
to be added that use CodeInterceptors (usually CodeInterceptor.Append or
CodeInterceptor.Prepend to reduce errors in how newlines are handled).
@mtdowling mtdowling requested a review from JordonPhillips March 1, 2022 22:00
@mtdowling mtdowling merged commit a80fc6e into main Mar 2, 2022
@mtdowling mtdowling deleted the typed-codewriter-sections branch April 8, 2022 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants