Skip to content

Commit 6b9cf83

Browse files
committed
Generates more packaging and cleanup
Generate a package.json. Generate a tsconfig.json. Make generated code work with strict mode. Always generate a symbol for strings with enums. Use CodeWriterDelegator + auto symbol imports. Utilize new Smithy features for aliases and dependencies.
1 parent 085d870 commit 6b9cf83

File tree

5 files changed

+234
-82
lines changed

5 files changed

+234
-82
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenVisitor.java

Lines changed: 54 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@
1515

1616
package software.amazon.smithy.typescript.codegen;
1717

18-
import java.util.HashMap;
18+
import java.util.Collection;
1919
import java.util.List;
20-
import java.util.Map;
2120
import java.util.Objects;
2221
import software.amazon.smithy.build.FileManifest;
2322
import software.amazon.smithy.build.PluginContext;
2423
import software.amazon.smithy.codegen.core.Symbol;
24+
import software.amazon.smithy.codegen.core.SymbolDependency;
2525
import software.amazon.smithy.codegen.core.SymbolProvider;
2626
import software.amazon.smithy.model.Model;
27+
import software.amazon.smithy.model.knowledge.NeighborProviderIndex;
2728
import software.amazon.smithy.model.knowledge.OperationIndex;
2829
import software.amazon.smithy.model.knowledge.TopDownIndex;
30+
import software.amazon.smithy.model.neighbor.Walker;
2931
import software.amazon.smithy.model.shapes.MemberShape;
3032
import software.amazon.smithy.model.shapes.OperationShape;
3133
import software.amazon.smithy.model.shapes.ServiceShape;
@@ -46,26 +48,53 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
4648
private final FileManifest fileManifest;
4749
private final SymbolProvider symbolProvider;
4850
private final ShapeIndex nonTraits;
49-
private final Map<String, TypeScriptWriter> writers = new HashMap<>();
51+
private final CodeWriterDelegator<TypeScriptWriter> writers;
5052

5153
CodegenVisitor(PluginContext context) {
5254
settings = TypeScriptSettings.from(context.getSettings());
5355
nonTraits = context.getNonTraitShapes();
5456
model = context.getModel();
5557
service = settings.getService(model);
5658
fileManifest = context.getFileManifest();
57-
symbolProvider = TypeScriptCodegenPlugin.createSymbolProvider(model);
59+
symbolProvider = SymbolProvider.cache(TypeScriptCodegenPlugin.createSymbolProvider(model));
60+
61+
Walker walker = new Walker(model.getKnowledge(NeighborProviderIndex.class).getProvider());
62+
writers = CodeWriterDelegator.<TypeScriptWriter>builder()
63+
.model(model)
64+
.symbolProvider(symbolProvider)
65+
.fileManifest(fileManifest)
66+
.factory((shape, symbol) -> new TypeScriptWriter(symbol.getNamespace()))
67+
.beforeWrite((filename, writer, shapes) -> {
68+
// Add dependencies of the shape and any references it has by
69+
// walking the shape's neighbors.
70+
for (Shape shape : shapes) {
71+
writer.addImport(symbolProvider.toSymbol(shape));
72+
walker.walkShapes(shape).forEach(neighbor -> {
73+
writer.addImport(symbolProvider.toSymbol(neighbor));
74+
});
75+
}
76+
})
77+
.build();
5878
}
5979

6080
void execute() {
6181
// Write shared / static content.
6282
fileManifest.writeFile("shared/shapeTypes.ts", getClass(), "shapeTypes.ts");
83+
fileManifest.writeFile("tsconfig.json", getClass(), "tsconfig.json");
6384

6485
// Generate models.
6586
nonTraits.shapes().sorted().forEach(shape -> shape.accept(this));
6687

6788
// Write each pending writer.
68-
writers.forEach((filename, writer) -> fileManifest.writeFile(filename, writer.toString()));
89+
// writers.forEach((filename, writer) -> fileManifest.writeFile(filename, writer.toString()));
90+
writers.writeFiles();
91+
92+
// Write the package.json file, including all symbol dependencies.
93+
PackageJsonGenerator.writePackageJson(settings, fileManifest, SymbolDependency.gatherDependencies(
94+
nonTraits.shapes()
95+
.map(symbolProvider::toSymbol)
96+
.map(Symbol::getDependencies)
97+
.flatMap(Collection::stream)));
6998
}
7099

71100
@Override
@@ -150,8 +179,7 @@ public Void structureShape(StructureShape shape) {
150179
*/
151180
private void renderNonErrorStructure(StructureShape shape) {
152181
Symbol symbol = symbolProvider.toSymbol(shape);
153-
TypeScriptWriter writer = getWriter(symbol.getDefinitionFile(), symbol.getNamespace());
154-
writer.addImport("SmithyStructure", "$SmithyStructure", "./shared/shapeTypes");
182+
TypeScriptWriter writer = writers.createWriter(shape);
155183
writer.openBlock("export class $L implements $$SmithyStructure {", symbol.getName());
156184
writer.write("readonly $$id = $S;", shape.getId());
157185

@@ -222,8 +250,7 @@ private void renderNonErrorStructure(StructureShape shape) {
222250
private void renderErrorStructure(StructureShape shape) {
223251
ErrorTrait errorTrait = shape.getTrait(ErrorTrait.class).orElseThrow(IllegalStateException::new);
224252
Symbol symbol = symbolProvider.toSymbol(shape);
225-
TypeScriptWriter writer = getWriter(symbol.getDefinitionFile(), symbol.getNamespace());
226-
writer.addImport("SmithyException", "$SmithyException", "./shared/shapeTypes");
253+
TypeScriptWriter writer = writers.createWriter(shape);
227254
writer.openBlock("export class $L extends $$SmithyException {", symbol.getName());
228255

229256
// Write properties.
@@ -321,8 +348,7 @@ private void renderErrorStructure(StructureShape shape) {
321348
public Void unionShape(UnionShape shape) {
322349
Symbol symbol = symbolProvider.toSymbol(shape);
323350

324-
TypeScriptWriter writer = getWriter(symbol.getDefinitionFile(), symbol.getNamespace());
325-
writer.addImport("TaggedUnion", "./shared/shapeTypes");
351+
TypeScriptWriter writer = writers.createWriter(shape);
326352
writer.openBlock("export type $L = TaggedUnion<{", symbol.getName());
327353
StructuredMemberWriter config = new StructuredMemberWriter(
328354
model, symbolProvider, shape.getAllMembers().values());
@@ -394,17 +420,25 @@ public Void unionShape(UnionShape shape) {
394420
*/
395421
@Override
396422
public Void stringShape(StringShape shape) {
397-
shape.getTrait(EnumTrait.class).filter(EnumTrait::hasNames).ifPresent(trait -> {
423+
shape.getTrait(EnumTrait.class).ifPresent(trait -> {
398424
Symbol symbol = symbolProvider.toSymbol(shape);
399-
TypeScriptWriter writer = getWriter(symbol.getDefinitionFile(), symbol.getNamespace());
400-
writer.openBlock("export enum $L {", symbol.getName());
401-
trait.getValues().forEach((value, body) -> body.getName().ifPresent(name -> {
402-
body.getDocumentation().ifPresent(writer::writeDocs);
403-
writer.write("$L = $S,", TypeScriptUtils.sanitizePropertyName(name), value);
404-
}));
405-
writer.closeBlock("};");
425+
TypeScriptWriter writer = writers.createWriter(shape);
426+
// Unnamed enums generate a union of string literals.
427+
if (!trait.hasNames()) {
428+
writer.write("export type $L = $L",
429+
symbol.getName(), TypeScriptUtils.getEnumVariants(trait.getValues().keySet()));
430+
} else {
431+
// Named enums generate an actual enum type.
432+
writer.openBlock("export enum $L {", symbol.getName());
433+
trait.getValues().forEach((value, body) -> body.getName().ifPresent(name -> {
434+
body.getDocumentation().ifPresent(writer::writeDocs);
435+
writer.write("$L = $S,", TypeScriptUtils.sanitizePropertyName(name), value);
436+
}));
437+
writer.closeBlock("};");
438+
}
406439
});
407440

441+
// Normal string shapes don't generate any code on their own.
408442
return null;
409443
}
410444

@@ -426,8 +460,7 @@ public Void serviceShape(ServiceShape shape) {
426460

427461
// TODO: This does not need to be an exported type. Move this to the command.
428462
private void renderErrorUnion(OperationIndex operationIndex, OperationShape operation) {
429-
Symbol symbol = symbolProvider.toSymbol(operation);
430-
TypeScriptWriter writer = getWriter(symbol.getDefinitionFile(), symbol.getNamespace());
463+
TypeScriptWriter writer = writers.createWriter(operation);
431464
List<StructureShape> errors = operationIndex.getErrors(operation);
432465

433466
if (errors.size() == 0) {
@@ -442,16 +475,4 @@ private void renderErrorUnion(OperationIndex operationIndex, OperationShape oper
442475
writer.write(" | $T$L", target, endOfLine);
443476
}
444477
}
445-
446-
private TypeScriptWriter getWriter(String filename, String moduleName) {
447-
boolean needsNewline = writers.containsKey(filename);
448-
TypeScriptWriter writer = writers.computeIfAbsent(filename, f -> new TypeScriptWriter(moduleName));
449-
450-
// Add newlines between types in the same file.
451-
if (needsNewline) {
452-
writer.write("");
453-
}
454-
455-
return writer;
456-
}
457478
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.smithy.typescript.codegen;
17+
18+
import java.io.InputStream;
19+
import java.util.Map;
20+
import software.amazon.smithy.build.FileManifest;
21+
import software.amazon.smithy.codegen.core.SymbolDependency;
22+
import software.amazon.smithy.model.node.Node;
23+
import software.amazon.smithy.model.node.ObjectNode;
24+
import software.amazon.smithy.utils.IoUtils;
25+
26+
/**
27+
* Private class used to generates a package.json file for the project.
28+
*
29+
* TODO: Make this extensible and pluggable.
30+
*/
31+
final class PackageJsonGenerator {
32+
33+
public static final String NORMAL_DEPENDENCY = "dependencies";
34+
public static final String DEV_DEPENDENCY = "devDependencies";
35+
public static final String PEER_DEPENDENCY = "peerDependencies";
36+
public static final String BUNDLED_DEPENDENCY = "bundledDependencies";
37+
public static final String OPTIONAL_DEPENDENCY = "optionalDependencies";
38+
39+
private PackageJsonGenerator() {}
40+
41+
static void writePackageJson(
42+
TypeScriptSettings settings,
43+
FileManifest manifest,
44+
Map<String, Map<String, SymbolDependency>> dependencies
45+
) {
46+
// Write the package.json file.
47+
InputStream resource = PackageJsonGenerator.class.getResourceAsStream("base-package.json");
48+
ObjectNode node = Node.parse(IoUtils.toUtf8String(resource))
49+
.expectObjectNode()
50+
.merge(settings.getPackageJson());
51+
52+
// Merge TypeScript dependencies into the package.json file.
53+
for (Map.Entry<String, Map<String, SymbolDependency>> depEntry : dependencies.entrySet()) {
54+
ObjectNode currentValue = node.getObjectMember(depEntry.getKey()).orElse(Node.objectNode());
55+
ObjectNode.Builder builder = currentValue.toBuilder();
56+
for (Map.Entry<String, SymbolDependency> entry : depEntry.getValue().entrySet()) {
57+
builder.withMember(entry.getKey(), entry.getValue().getVersion());
58+
}
59+
node = node.withMember(depEntry.getKey(), builder.build());
60+
}
61+
62+
// Expand template parameters.
63+
String template = Node.prettyPrintJson(node);
64+
template = template.replace("${package}", settings.getPackageName());
65+
template = template.replace("${packageDescription}", settings.getPackageDescription());
66+
template = template.replace("${packageVersion}", settings.getPackageVersion());
67+
manifest.writeFile("package.json", template);
68+
}
69+
}

0 commit comments

Comments
 (0)