Skip to content

Commit 9c321fc

Browse files
authored
toposort models and chunk big model files (#210)
This change exports shapes in topological order, and chunk them into models/models_0.ts, models/models_1.ts, models/models_2.ts..., models/models_x.ts. A models/index.ts will re-export shapes from each chunks.
1 parent 062bde7 commit 9c321fc

File tree

5 files changed

+97
-29
lines changed

5 files changed

+97
-29
lines changed

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import software.amazon.smithy.codegen.core.Symbol;
3535
import software.amazon.smithy.codegen.core.SymbolDependency;
3636
import software.amazon.smithy.codegen.core.SymbolProvider;
37+
import software.amazon.smithy.codegen.core.TopologicalIndex;
3738
import software.amazon.smithy.model.Model;
3839
import software.amazon.smithy.model.knowledge.TopDownIndex;
3940
import software.amazon.smithy.model.neighbor.Walker;
@@ -152,15 +153,18 @@ void execute() {
152153

153154
// Generate models that are connected to the service being generated.
154155
LOGGER.fine("Walking shapes from " + service.getId() + " to find shapes to generate");
155-
Set<Shape> serviceShapes = new TreeSet<>(new Walker(nonTraits).walkShapes(service));
156-
157-
// Condense duplicate shapes
158-
Map<String, Shape> shapeMap = condenseShapes(serviceShapes);
156+
// Walk the tree and condense duplicate shapes
157+
Collection<Shape> shapeSet = condenseShapes(new Walker(nonTraits).walkShapes(service));
158+
Model prunedModel = Model.builder().addShapes(shapeSet).build();
159159

160160
// Generate models from condensed shapes
161-
for (Shape shape : shapeMap.values()) {
161+
for (Shape shape : TopologicalIndex.of(prunedModel).getOrderedShapes()) {
162+
shape.accept(this);
163+
}
164+
for (Shape shape : TopologicalIndex.of(prunedModel).getRecursiveShapes()) {
162165
shape.accept(this);
163166
}
167+
SymbolVisitor.writeModelIndex(prunedModel, symbolProvider, fileManifest);
164168

165169
// Generate the client Node and Browser configuration files. These
166170
// files are switched between in package.json based on the targeted
@@ -325,7 +329,7 @@ public Void serviceShape(ServiceShape shape) {
325329
return null;
326330
}
327331

328-
private Map<String, Shape> condenseShapes(Set<Shape> shapes) {
332+
private Collection<Shape> condenseShapes(Set<Shape> shapes) {
329333
Map<String, Shape> shapeMap = new LinkedHashMap<>();
330334

331335
// Check for colliding shapes and prune non-unique shapes
@@ -342,7 +346,7 @@ private Map<String, Shape> condenseShapes(Set<Shape> shapes) {
342346
}
343347
}
344348

345-
return shapeMap;
349+
return shapeMap.values();
346350
}
347351

348352
private boolean isShapeCollision(Shape shapeA, Shape shapeB) {

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

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717

1818
import static java.lang.String.format;
1919

20+
import java.util.Comparator;
21+
import java.util.HashMap;
2022
import java.util.HashSet;
23+
import java.util.Map;
2124
import java.util.Optional;
2225
import java.util.Set;
2326
import java.util.logging.Logger;
27+
28+
import software.amazon.smithy.build.FileManifest;
2429
import software.amazon.smithy.codegen.core.CodegenException;
2530
import software.amazon.smithy.codegen.core.ReservedWordSymbolProvider;
2631
import software.amazon.smithy.codegen.core.ReservedWords;
@@ -75,8 +80,13 @@ final class SymbolVisitor implements SymbolProvider, ShapeVisitor<Symbol> {
7580
private final Model model;
7681
private final ReservedWordSymbolProvider.Escaper escaper;
7782
private final Set<StructureShape> errorShapes = new HashSet<>();
83+
private final ModuleNameDelegator moduleNameDelegator;
7884

7985
SymbolVisitor(Model model) {
86+
this(model, ModuleNameDelegator.DEFAULT_CHUNK_SIZE);
87+
}
88+
89+
SymbolVisitor(Model model, int shapeChunkSize) {
8090
this.model = model;
8191

8292
// Load reserved words from a new-line delimited file.
@@ -96,6 +106,12 @@ final class SymbolVisitor implements SymbolProvider, ShapeVisitor<Symbol> {
96106
model.shapes(OperationShape.class).forEach(operationShape -> {
97107
errorShapes.addAll(operationIndex.getErrors(operationShape));
98108
});
109+
110+
moduleNameDelegator = new ModuleNameDelegator(shapeChunkSize);
111+
}
112+
113+
static void writeModelIndex(Model model, SymbolProvider symbolProvider, FileManifest fileManifest) {
114+
ModuleNameDelegator.writeModelIndex(model, symbolProvider, fileManifest);
99115
}
100116

101117
@Override
@@ -225,7 +241,7 @@ public Symbol documentShape(DocumentShape shape) {
225241
@Override
226242
public Symbol operationShape(OperationShape shape) {
227243
String commandName = flattenShapeName(shape) + "Command";
228-
String moduleName = formatModuleName(shape.getType(), commandName);
244+
String moduleName = moduleNameDelegator.formatModuleName(shape, commandName);
229245
Symbol intermediate = createGeneratedSymbolBuilder(shape, commandName, moduleName).build();
230246
Symbol.Builder builder = intermediate.toBuilder();
231247
// Add input and output type symbols (XCommandInput / XCommandOutput).
@@ -271,7 +287,7 @@ public Symbol resourceShape(ResourceShape shape) {
271287
@Override
272288
public Symbol serviceShape(ServiceShape shape) {
273289
String name = StringUtils.capitalize(shape.getId().getName()) + "Client";
274-
String moduleName = formatModuleName(shape.getType(), name);
290+
String moduleName = moduleNameDelegator.formatModuleName(shape, name);
275291
return createGeneratedSymbolBuilder(shape, name, moduleName).build();
276292
}
277293

@@ -358,7 +374,7 @@ private String flattenShapeName(ToShapeId id) {
358374

359375
private Symbol.Builder createObjectSymbolBuilder(Shape shape) {
360376
String name = flattenShapeName(shape);
361-
String moduleName = formatModuleName(shape.getType(), name);
377+
String moduleName = moduleNameDelegator.formatModuleName(shape, name);
362378
return createGeneratedSymbolBuilder(shape, name, moduleName);
363379
}
364380

@@ -378,18 +394,57 @@ private Symbol.Builder createGeneratedSymbolBuilder(Shape shape, String typeName
378394
.definitionFile(toFilename(namespace));
379395
}
380396

381-
private String formatModuleName(ShapeType shapeType, String name) {
382-
// All shapes except for the service and operations are stored in models.
383-
if (shapeType == ShapeType.SERVICE) {
384-
return "./" + name;
385-
} else if (shapeType == ShapeType.OPERATION) {
386-
return "./commands/" + name;
387-
} else {
388-
return "./models/index";
389-
}
390-
}
391-
392397
private String toFilename(String namespace) {
393398
return namespace + ".ts";
394399
}
400+
401+
/**
402+
* Utility class to locate which path should the symbol be generated into.
403+
* It will break the models into multiple files to prevent it getting too big.
404+
*/
405+
static final class ModuleNameDelegator {
406+
static final int DEFAULT_CHUNK_SIZE = 300;
407+
static final String SHAPE_NAMESPACE_PREFIX = "./models/";
408+
409+
private final Map<Shape, String> visitedModels = new HashMap<>();
410+
private int bucketCount = 0;
411+
private int currentBucketSize = 0;
412+
private final int chunkSize;
413+
414+
ModuleNameDelegator(int shapeChunkSize) {
415+
chunkSize = shapeChunkSize;
416+
}
417+
418+
public String formatModuleName(Shape shape, String name) {
419+
// All shapes except for the service and operations are stored in models.
420+
if (shape.getType() == ShapeType.SERVICE) {
421+
return "./" + name;
422+
} else if (shape.getType() == ShapeType.OPERATION) {
423+
return "./commands/" + name;
424+
} else if (visitedModels.containsKey(shape)) {
425+
return visitedModels.get(shape);
426+
}
427+
// Add models into buckets no bigger than chunk size.
428+
String path = SHAPE_NAMESPACE_PREFIX + "models_" + bucketCount;
429+
visitedModels.put(shape, path);
430+
currentBucketSize++;
431+
if (currentBucketSize == chunkSize) {
432+
bucketCount++;
433+
currentBucketSize = 0;
434+
}
435+
return path;
436+
}
437+
438+
static void writeModelIndex(Model model, SymbolProvider symbolProvider, FileManifest fileManifest) {
439+
TypeScriptWriter writer = new TypeScriptWriter("");
440+
model.shapes()
441+
.map(shape -> symbolProvider.toSymbol(shape).getNamespace())
442+
.filter(namespace -> namespace.startsWith(SHAPE_NAMESPACE_PREFIX))
443+
.distinct()
444+
.sorted(Comparator.naturalOrder())
445+
.forEach(namespace -> writer.write(
446+
"export * from $S;", namespace.replaceFirst(SHAPE_NAMESPACE_PREFIX, "./")));
447+
fileManifest.writeFile("models/index.ts", writer.toString());
448+
}
449+
}
395450
}

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CodegenVisitorTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ public void generatesServiceClients() {
141141
new TypeScriptCodegenPlugin().execute(context);
142142

143143
Assertions.assertTrue(manifest.hasFile("models/index.ts"));
144+
Assertions.assertTrue(manifest.hasFile("models/models_0.ts"));
144145
assertThat(manifest.getFileString("models/index.ts").get(),
146+
containsString("export * from \"./models_0\";"));
147+
assertThat(manifest.getFileString("models/models_0.ts").get(),
145148
containsString("export interface Bar {\n" +
146149
" baz: string | undefined;\n" +
147150
"}"));

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/StructureGeneratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ private String testStructureCodegen(String file, String expectedType) {
471471
.build();
472472

473473
new TypeScriptCodegenPlugin().execute(context);
474-
String contents = manifest.getFileString("/models/index.ts").get();
474+
String contents = manifest.getFileString("/models/models_0.ts").get();
475475

476476
assertThat(contents, containsString(expectedType));
477477
return contents;

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/SymbolProviderTest.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package software.amazon.smithy.typescript.codegen;
22

33
import static org.hamcrest.MatcherAssert.assertThat;
4+
import static org.hamcrest.Matchers.containsString;
45
import static org.hamcrest.Matchers.equalTo;
56
import static org.hamcrest.Matchers.greaterThan;
67

78
import org.junit.jupiter.api.Test;
9+
import software.amazon.smithy.build.MockManifest;
810
import software.amazon.smithy.codegen.core.Symbol;
911
import software.amazon.smithy.codegen.core.SymbolProvider;
1012
import software.amazon.smithy.model.Model;
@@ -25,9 +27,9 @@ public void createsSymbols() {
2527
Symbol symbol = provider.toSymbol(shape);
2628

2729
assertThat(symbol.getName(), equalTo("Hello"));
28-
assertThat(symbol.getNamespace(), equalTo("./models/index"));
30+
assertThat(symbol.getNamespace(), equalTo("./models/models_0"));
2931
assertThat(symbol.getNamespaceDelimiter(), equalTo("/"));
30-
assertThat(symbol.getDefinitionFile(), equalTo("./models/index.ts"));
32+
assertThat(symbol.getDefinitionFile(), equalTo("./models/models_0.ts"));
3133
}
3234

3335
@Test
@@ -38,16 +40,20 @@ public void createsSymbolsIntoTargetNamespace() {
3840
SymbolProvider provider = TypeScriptCodegenPlugin.createSymbolProvider(model);
3941
Symbol symbol1 = provider.toSymbol(shape1);
4042
Symbol symbol2 = provider.toSymbol(shape2);
43+
MockManifest manifest = new MockManifest();
44+
SymbolVisitor.writeModelIndex(model, provider, manifest);
4145

4246
assertThat(symbol1.getName(), equalTo("Hello"));
43-
assertThat(symbol1.getNamespace(), equalTo("./models/index"));
47+
assertThat(symbol1.getNamespace(), equalTo("./models/models_0"));
4448
assertThat(symbol1.getNamespaceDelimiter(), equalTo("/"));
45-
assertThat(symbol1.getDefinitionFile(), equalTo("./models/index.ts"));
49+
assertThat(symbol1.getDefinitionFile(), equalTo("./models/models_0.ts"));
4650

4751
assertThat(symbol2.getName(), equalTo("Hello"));
48-
assertThat(symbol2.getNamespace(), equalTo("./models/index"));
52+
assertThat(symbol2.getNamespace(), equalTo("./models/models_0"));
4953
assertThat(symbol2.getNamespaceDelimiter(), equalTo("/"));
50-
assertThat(symbol2.getDefinitionFile(), equalTo("./models/index.ts"));
54+
assertThat(symbol2.getDefinitionFile(), equalTo("./models/models_0.ts"));
55+
assertThat(manifest.getFileString("models/index.ts").get(),
56+
containsString("export * from \"./models_0\";"));
5157
}
5258

5359
@Test
@@ -78,7 +84,7 @@ public void doesNotEscapeBuiltins() {
7884

7985
// Normal structure with escaping.
8086
assertThat(structSymbol.getName(), equalTo("_Object"));
81-
assertThat(structSymbol.getNamespace(), equalTo("./models/index"));
87+
assertThat(structSymbol.getNamespace(), equalTo("./models/models_0"));
8288

8389
// Reference to built-in type with no escaping.
8490
assertThat(memberSymbol.getName(), equalTo("string"));

0 commit comments

Comments
 (0)