Skip to content

add option for smithy codegen attribution comment in generated files #535

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.PHONY: local test build

# used with another locally situated consuming codebase to test changes.
local:
./gradlew clean build publishToMavenLocal

build:
./gradlew build

# run tests
test:
./gradlew test
1 change: 1 addition & 0 deletions smithy-typescript-codegen-test/smithy-build.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"targetNamespace": "Weather",
"package": "weather",
"packageVersion": "0.0.1",
"withAttribution": true,
"packageJson": {
"license": "Apache-2.0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ void execute() {
for (Shape shape : TopologicalIndex.of(prunedModel).getRecursiveShapes()) {
shape.accept(this);
}
SymbolVisitor.writeModelIndex(prunedModel, symbolProvider, fileManifest);
SymbolVisitor.writeModelIndex(settings, prunedModel, symbolProvider, fileManifest);

// Generate the client Node and Browser configuration files. These
// files are switched between in package.json based on the targeted
Expand Down Expand Up @@ -446,7 +446,7 @@ private void generateClient(ServiceShape shape) {
}

if (containedOperations.stream().anyMatch(operation -> operation.hasTrait(PaginatedTrait.ID))) {
PaginationGenerator.writeIndex(model, service, fileManifest);
PaginationGenerator.writeIndex(settings, model, service, fileManifest);
writers.useFileWriter(PaginationGenerator.PAGINATION_INTERFACE_FILE, paginationWriter ->
PaginationGenerator.generateServicePaginationInterfaces(
aggregatedClientName,
Expand All @@ -455,7 +455,7 @@ private void generateClient(ServiceShape shape) {
}

if (containedOperations.stream().anyMatch(operation -> operation.hasTrait(WaitableTrait.ID))) {
WaiterGenerator.writeIndex(model, service, fileManifest);
WaiterGenerator.writeIndex(settings, model, service, fileManifest);
}
}

Expand All @@ -476,14 +476,14 @@ private void generateCommands(ServiceShape shape) {
for (OperationShape operation : containedOperations) {
// Right now this only generates stubs
if (settings.generateClient()) {
CommandGenerator.writeIndex(model, service, symbolProvider, fileManifest);
CommandGenerator.writeIndex(settings, model, service, symbolProvider, fileManifest);
writers.useShapeWriter(operation, commandWriter -> new CommandGenerator(
settings, model, operation, symbolProvider, commandWriter,
runtimePlugins, protocolGenerator, applicationProtocol).run());
}

if (settings.generateServerSdk()) {
ServerCommandGenerator.writeIndex(model, service, symbolProvider, fileManifest);
ServerCommandGenerator.writeIndex(settings, model, service, symbolProvider, fileManifest);
writers.useShapeWriter(operation, symbolProvider, commandWriter -> new ServerCommandGenerator(
settings, model, operation, symbolProvider, commandWriter,
protocolGenerator, applicationProtocol).run());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,13 @@ private void writeSerdeDispatcher(boolean isInput) {
}

static void writeIndex(
TypeScriptSettings settings,
Model model,
ServiceShape service,
SymbolProvider symbolProvider,
FileManifest fileManifest
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());

TopDownIndex topDownIndex = TopDownIndex.of(model);
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static void writeIndex(
List<TypeScriptIntegration> integrations,
ProtocolGenerator protocolGenerator
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());

if (settings.generateClient()) {
writeClientExports(settings, model, symbolProvider, writer, fileManifest, integrations);
Expand Down Expand Up @@ -80,7 +80,7 @@ static void writeServerIndex(
SymbolProvider symbolProvider,
FileManifest fileManifest
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());
ServiceShape service = settings.getService(model);
Symbol symbol = symbolProvider.toSymbol(service);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ private static String getModulePath(String fileLocation) {
}

static void writeIndex(
TypeScriptSettings settings,
Model model,
ServiceShape service,
FileManifest fileManifest
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());
writer.write("export * from \"./$L\"", getModulePath(PAGINATION_INTERFACE_FILE));

TopDownIndex topDownIndex = TopDownIndex.of(model);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,13 @@ private void writeErrorHandlerCase(StructureShape error) {
}

static void writeIndex(
TypeScriptSettings settings,
Model model,
ServiceShape service,
SymbolProvider symbolProvider,
FileManifest fileManifest
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());

TopDownIndex topDownIndex = TopDownIndex.of(model);
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,11 @@ final class SymbolVisitor implements SymbolProvider, ShapeVisitor<Symbol> {
moduleNameDelegator = new ModuleNameDelegator(shapeChunkSize);
}

static void writeModelIndex(Model model, SymbolProvider symbolProvider, FileManifest fileManifest) {
ModuleNameDelegator.writeModelIndex(model, symbolProvider, fileManifest);
static void writeModelIndex(TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
FileManifest fileManifest) {
ModuleNameDelegator.writeModelIndex(settings, model, symbolProvider, fileManifest);
}

@Override
Expand Down Expand Up @@ -455,8 +458,11 @@ public String formatModuleName(Shape shape, String name) {
return path;
}

static void writeModelIndex(Model model, SymbolProvider symbolProvider, FileManifest fileManifest) {
TypeScriptWriter writer = new TypeScriptWriter("");
static void writeModelIndex(TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
FileManifest fileManifest) {
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());
String modelPrefix = Paths.get(".", CodegenUtils.SOURCE_FOLDER, SHAPE_NAMESPACE_PREFIX).toString();
model.shapes()
.map(shape -> symbolProvider.toSymbol(shape).getNamespace())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private TypeScriptWriter checkoutWriter(String filename) {

TypeScriptWriter writer = writers.computeIfAbsent(formattedFilename, f -> {
String moduleName = filename.endsWith(".ts") ? filename.substring(0, filename.length() - 3) : filename;
return new TypeScriptWriter(moduleName);
return new TypeScriptWriter(moduleName, settings.isWithAttribution());
});

// Add newlines/separators between types in the same file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public final class TypeScriptSettings {
private static final String PROTOCOL = "protocol";
private static final String PRIVATE = "private";
private static final String PACKAGE_MANAGER = "packageManager";
private static final String WITH_ATTRIBUTION = "withAttribution";

private String packageName;
private String packageDescription = "";
Expand All @@ -66,6 +67,7 @@ public final class TypeScriptSettings {
private ArtifactType artifactType = ArtifactType.CLIENT;
private boolean disableDefaultValidation = false;
private PackageManager packageManager = PackageManager.YARN;
private boolean withAttribution = false;

@Deprecated
public static TypeScriptSettings from(Model model, ObjectNode config) {
Expand Down Expand Up @@ -101,6 +103,7 @@ public static TypeScriptSettings from(Model model, ObjectNode config, ArtifactTy
config.getStringMember(PACKAGE_MANAGER)
.map(s -> PackageManager.fromString(s.getValue()))
.orElse(PackageManager.YARN));
settings.setWithAttribution(config.getBooleanMemberOrDefault(WITH_ATTRIBUTION, false));

if (artifactType == ArtifactType.SSDK) {
settings.setDisableDefaultValidation(config.getBooleanMemberOrDefault(DISABLE_DEFAULT_VALIDATION));
Expand Down Expand Up @@ -361,6 +364,14 @@ public void setProtocol(ShapeId protocol) {
this.protocol = Objects.requireNonNull(protocol);
}

public boolean isWithAttribution() {
return withAttribution;
}

public void setWithAttribution(boolean withAttribution) {
this.withAttribution = withAttribution;
}

/**
* An enum indicating the type of artifact the code generator will produce.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.function.BiFunction;
import java.util.function.UnaryOperator;
import java.util.logging.Logger;
Expand Down Expand Up @@ -63,10 +66,17 @@ public final class TypeScriptWriter extends CodeWriter {
private final String moduleNameString;
private final ImportDeclarations imports;
private final List<SymbolDependency> dependencies = new ArrayList<>();
private final Set<String> attributions = new TreeSet<>();
private final boolean includeAttribution;

public TypeScriptWriter(String moduleName) {
this(moduleName, false);
}

public TypeScriptWriter(String moduleName, boolean includeAttribution) {
this.moduleName = Paths.get(moduleName);
moduleNameString = moduleName;
this.includeAttribution = includeAttribution;
imports = new ImportDeclarations(moduleName);

setIndentText(" ");
Expand Down Expand Up @@ -286,6 +296,44 @@ public TypeScriptWriter addDependency(SymbolDependencyContainer dependencies) {
return this;
}

/**
* Works the same as the super method, but also looks up the call stack to find
* the caller (usually a Generator) class and note it down as a contributor
* to the file being written.
*
* This will produce a comment at the head of the file indicating that the file is generated
* and the contributing classes.
*
* @param content Content to write.
* @param args String arguments to use for formatting.
*/
@Override
public TypeScriptWriter write(Object content, Object... args) {
if (includeAttribution) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
String callerClassName = Arrays.stream(stackTrace)
.map(element -> {
String fullClassName = element.getClassName();
if (fullClassName.endsWith(TypeScriptWriter.class.getSimpleName())) {
// ignore own class.
return null;
}
if (!fullClassName.contains("smithy.typescript.codegen")) {
// ignore non-smithy-ts portions of the call stack.
return null;
}
String[] names = fullClassName.split("\\.");
return names[names.length - 1];
})
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
addAttribution(callerClassName);
}
super.write(content, args);
return this;
}

Collection<SymbolDependency> getDependencies() {
return dependencies;
}
Expand All @@ -297,12 +345,27 @@ public String toString() {
String strippedContents = StringUtils.stripStart(contents, null);
String strippedImportString = StringUtils.strip(importString, null);

String codeGenContributors = String.join(", ", attributions);
if (codeGenContributors.isEmpty()) {
codeGenContributors = "unspecified";
}
String attributionComment = includeAttribution ? "// smithy-codegen: " + codeGenContributors + "\n" : "";

// Don't add an additional new line between explicit imports and managed imports.
if (!strippedImportString.isEmpty() && strippedContents.startsWith("import ")) {
return strippedImportString + "\n" + strippedContents;
return attributionComment + strippedImportString + "\n" + strippedContents;
}

return importString + contents;
return attributionComment + importString + contents;
}

/**
* @param attribution - identifies a contributing Generator class.
*/
private void addAttribution(String attribution) {
if (attribution != null) {
attributions.add(attribution);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,12 @@ private static String getModulePath(String fileLocation) {
}

static void writeIndex(
TypeScriptSettings settings,
Model model,
ServiceShape service,
FileManifest fileManifest
) {
TypeScriptWriter writer = new TypeScriptWriter("");
TypeScriptWriter writer = new TypeScriptWriter("", settings.isWithAttribution());

TopDownIndex topDownIndex = TopDownIndex.of(model);
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void createsSymbolsIntoTargetNamespace() {
Symbol symbol1 = provider.toSymbol(shape1);
Symbol symbol2 = provider.toSymbol(shape2);
MockManifest manifest = new MockManifest();
SymbolVisitor.writeModelIndex(model, provider, manifest);
SymbolVisitor.writeModelIndex(settings, model, provider, manifest);

assertThat(symbol1.getName(), equalTo("Hello"));
assertThat(symbol1.getNamespace(), equalTo("./" + CodegenUtils.SOURCE_FOLDER + "/models/models_0"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package software.amazon.smithy.typescript.codegen;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

import org.junit.jupiter.api.Test;

public class TypeScriptWriterTest {
private static class ExampleGenerator {
final TypeScriptWriter writer = new TypeScriptWriter("", true);
public String doWrite() {
writer.write("");
return writer.toString();
}
}

@Test
public void writesDocStrings() {
TypeScriptWriter writer = new TypeScriptWriter("foo");
Expand Down Expand Up @@ -47,6 +56,23 @@ public void escapesMultiLineCloseInDocStrings() {
assertThat(result, equalTo("/**\n * This is *\\/ valid documentation.\n */\n"));
}

@Test
public void includesAttribution() {
TypeScriptWriter writer = new TypeScriptWriter("", true);
writer.write("");
String result = writer.toString();

assertThat(result, containsString("// smithy-codegen: TypeScriptWriterTest"));
}

@Test
public void includesAttributionCallerClass() {
ExampleGenerator caller = new ExampleGenerator();
String result = caller.doWrite();

assertThat(result, containsString("// smithy-codegen: TypeScriptWriterTest$ExampleGenerator"));
}

@Test
public void addsFormatterForSymbols() {
// TODO
Expand Down