Skip to content

Commit 9ada040

Browse files
committed
Add better handling of module and filenames
1 parent 20cc42f commit 9ada040

File tree

12 files changed

+189
-61
lines changed

12 files changed

+189
-61
lines changed

smithy-typescript-codegen-test/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
extra["displayName"] = "Smithy :: Typescript :: Codegen :: Test"
1717
extra["moduleName"] = "software.amazon.smithy.typescript.codegen.test"
1818

19+
tasks["jar"].enabled = false
20+
1921
plugins {
2022
java
2123
id("software.amazon.smithy").version("0.3.0")
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$version: "0.4.0"
2+
namespace example.weather.nested.more
3+
4+
structure Baz {
5+
baz: String,
6+
bar: String,
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$version: "0.4.0"
2+
namespace example.weather.nested
3+
4+
structure Foo {
5+
baz: String,
6+
bar: String,
7+
}

smithy-typescript-codegen-test/smithy-build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"plugins": {
44
"typescript-codegen": {
55
"service": "example.weather#Weather",
6+
"targetNamespace": "Weather",
67
"package": "weather",
78
"packageVersion": "0.0.1",
89
"packageJson": {

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

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@
1717

1818
import java.util.Collection;
1919
import java.util.List;
20+
import java.util.Map;
2021
import java.util.Objects;
2122
import software.amazon.smithy.build.FileManifest;
2223
import software.amazon.smithy.build.PluginContext;
24+
import software.amazon.smithy.codegen.core.ShapeIdShader;
2325
import software.amazon.smithy.codegen.core.Symbol;
2426
import software.amazon.smithy.codegen.core.SymbolDependency;
2527
import software.amazon.smithy.codegen.core.SymbolProvider;
2628
import software.amazon.smithy.model.Model;
27-
import software.amazon.smithy.model.knowledge.NeighborProviderIndex;
2829
import software.amazon.smithy.model.knowledge.OperationIndex;
2930
import software.amazon.smithy.model.knowledge.TopDownIndex;
30-
import software.amazon.smithy.model.neighbor.Walker;
3131
import software.amazon.smithy.model.shapes.MemberShape;
3232
import software.amazon.smithy.model.shapes.OperationShape;
3333
import software.amazon.smithy.model.shapes.ServiceShape;
@@ -39,9 +39,16 @@
3939
import software.amazon.smithy.model.shapes.UnionShape;
4040
import software.amazon.smithy.model.traits.EnumTrait;
4141
import software.amazon.smithy.model.traits.ErrorTrait;
42+
import software.amazon.smithy.utils.MapUtils;
4243

4344
class CodegenVisitor extends ShapeVisitor.Default<Void> {
4445

46+
/** A mapping of static resource files to copy over to a new filename. */
47+
private static final Map<String, String> STATIC_FILE_COPIES = MapUtils.of(
48+
"shared/shapeTypes.ts", "shapeTypes.ts",
49+
"tsconfig.json", "tsconfig.json"
50+
);
51+
4552
private final TypeScriptSettings settings;
4653
private final Model model;
4754
private final ServiceShape service;
@@ -56,31 +63,20 @@ class CodegenVisitor extends ShapeVisitor.Default<Void> {
5663
model = context.getModel();
5764
service = settings.getService(model);
5865
fileManifest = context.getFileManifest();
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();
66+
67+
// Shade the generated shape IDs if a target namespace was specified.
68+
String targetNamespace = context.getSettings().getStringMemberOrDefault(ShapeIdShader.TARGET_NAMESPACE, null);
69+
String rootNamespace = targetNamespace == null ? null : service.getId().getNamespace();
70+
71+
symbolProvider = SymbolProvider.cache(
72+
TypeScriptCodegenPlugin.createSymbolProvider(model, rootNamespace, targetNamespace));
73+
74+
writers = TypeScriptWriter.createDelegator(model, symbolProvider, fileManifest);
7875
}
7976

8077
void execute() {
8178
// Write shared / static content.
82-
fileManifest.writeFile("shared/shapeTypes.ts", getClass(), "shapeTypes.ts");
83-
fileManifest.writeFile("tsconfig.json", getClass(), "tsconfig.json");
79+
STATIC_FILE_COPIES.forEach((from, to) -> fileManifest.writeFile(from, getClass(), to));
8480

8581
// Generate models.
8682
nonTraits.shapes().sorted().forEach(shape -> shape.accept(this));

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

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
package software.amazon.smithy.typescript.codegen;
1717

1818
import static java.lang.String.format;
19-
import static software.amazon.smithy.typescript.codegen.TypeScriptUtils.formatModuleName;
2019

20+
import java.util.Locale;
21+
import java.util.StringJoiner;
2122
import java.util.logging.Logger;
23+
import java.util.regex.Pattern;
2224
import software.amazon.smithy.codegen.core.CodegenException;
25+
import software.amazon.smithy.codegen.core.ShapeIdShader;
2326
import software.amazon.smithy.codegen.core.Symbol;
2427
import software.amazon.smithy.codegen.core.SymbolProvider;
2528
import software.amazon.smithy.codegen.core.SymbolReference;
@@ -42,11 +45,13 @@
4245
import software.amazon.smithy.model.shapes.ServiceShape;
4346
import software.amazon.smithy.model.shapes.SetShape;
4447
import software.amazon.smithy.model.shapes.Shape;
48+
import software.amazon.smithy.model.shapes.ShapeId;
4549
import software.amazon.smithy.model.shapes.ShapeVisitor;
4650
import software.amazon.smithy.model.shapes.ShortShape;
4751
import software.amazon.smithy.model.shapes.StringShape;
4852
import software.amazon.smithy.model.shapes.StructureShape;
4953
import software.amazon.smithy.model.shapes.TimestampShape;
54+
import software.amazon.smithy.model.shapes.ToShapeId;
5055
import software.amazon.smithy.model.shapes.UnionShape;
5156
import software.amazon.smithy.model.traits.EnumTrait;
5257
import software.amazon.smithy.model.traits.ErrorTrait;
@@ -65,11 +70,26 @@ final class SymbolVisitor implements SymbolProvider, ShapeVisitor<Symbol> {
6570
private static final String TYPES_NODE_VERSION = "^12.7.5";
6671
private static final String TYPES_BIG_JS_VERSION = "^4.0.5";
6772
private static final String BIG_JS_VERSION = "^5.2.2";
73+
private static final Pattern SHAPE_ID_NAMESPACE_SPLITTER = Pattern.compile("\\.");
74+
private static final Pattern SHAPE_ID_NAMESPACE_PART_SPLITTER = Pattern.compile("_");
75+
private static final Pattern SLASH_SPLITTER = Pattern.compile("/");
6876

6977
private final Model model;
78+
private final ShapeIdShader shader;
79+
private final String targetNamespace;
7080

71-
SymbolVisitor(Model model) {
81+
SymbolVisitor(Model model, String rootNamespace, String targetNamespace) {
7282
this.model = model;
83+
this.targetNamespace = targetNamespace;
84+
85+
if (rootNamespace != null && targetNamespace != null) {
86+
shader = ShapeIdShader.builder()
87+
.rootNamespace(rootNamespace)
88+
.targetNamespace(targetNamespace)
89+
.build();
90+
} else {
91+
shader = null;
92+
}
7393
}
7494

7595
@Override
@@ -188,8 +208,9 @@ public Symbol documentShape(DocumentShape shape) {
188208

189209
@Override
190210
public Symbol operationShape(OperationShape shape) {
191-
String commandName = StringUtils.capitalize(shape.getId().getName()) + "Command";
192-
return createSymbolBuilder(shape, commandName, formatModuleName(shape)).build();
211+
ShapeId shaded = shadeShapeId(shape);
212+
String commandName = StringUtils.capitalize(shaded.getName()) + "Command";
213+
return createSymbolBuilder(shape, commandName, formatModuleName(shaded)).build();
193214
}
194215

195216
@Override
@@ -265,40 +286,66 @@ public Symbol timestampShape(TimestampShape shape) {
265286
return createSymbolBuilder(shape, "Date").build();
266287
}
267288

268-
private static Symbol.Builder createObjectSymbolBuilder(Shape shape) {
269-
String name = StringUtils.capitalize(shape.getId().getName());
270-
return createSymbolBuilder(shape, name, formatModuleName(shape));
289+
private ShapeId shadeShapeId(ToShapeId id) {
290+
return shader == null
291+
? id.toShapeId()
292+
: shader.shade(id.toShapeId(), ShapeIdShader.ShadeOption.SQUASH_INTO_NAMESPACE);
293+
}
294+
295+
private Symbol.Builder createObjectSymbolBuilder(Shape shape) {
296+
ShapeId shaded = shadeShapeId(shape);
297+
String name = StringUtils.capitalize(shaded.getName());
298+
return createSymbolBuilder(shape, name, formatModuleName(shaded));
271299
}
272300

273-
private static Symbol.Builder createSymbolBuilder(Shape shape, String typeName) {
301+
private Symbol.Builder createSymbolBuilder(Shape shape, String typeName) {
274302
return Symbol.builder().putProperty("shape", shape).name(typeName);
275303
}
276304

277-
private static Symbol.Builder createSymbolBuilder(Shape shape, String typeName, String namespace) {
305+
private Symbol.Builder createSymbolBuilder(Shape shape, String typeName, String namespace) {
278306
return Symbol.builder()
279307
.putProperty("shape", shape)
280308
.name(typeName)
281309
.namespace(namespace, "/")
282310
.definitionFile(toFilename(shape, typeName, namespace));
283311
}
284312

285-
private static String toFilename(Shape shape, String name, String namespace) {
313+
private String formatModuleName(ToShapeId id) {
314+
StringBuilder result = new StringBuilder();
315+
316+
for (String part : SHAPE_ID_NAMESPACE_SPLITTER.split(id.toShapeId().getNamespace())) {
317+
String[] inner = SHAPE_ID_NAMESPACE_PART_SPLITTER.split(part);
318+
for (int i = 0; i < inner.length; i++) {
319+
String innerValue = inner[i].toLowerCase(Locale.ENGLISH);
320+
if (i > 0) {
321+
innerValue = StringUtils.capitalize(innerValue);
322+
}
323+
result.append(innerValue);
324+
}
325+
result.append("/");
326+
}
327+
328+
// Remove the trailing "/".
329+
result.deleteCharAt(result.length() - 1);
330+
return result.toString();
331+
}
332+
333+
private String toFilename(Shape shape, String name, String namespace) {
286334
if (shape.isServiceShape()) {
287-
return format("%sClient.ts", name);
335+
return name + "Client.ts";
288336
}
289337

290338
String filename;
291339
if (namespace == null) {
292-
filename = name;
340+
filename = "index.ts";
293341
} else {
294-
StringBuilder builder = new StringBuilder();
295-
String[] parts = namespace.split("/");
296-
for (String part : parts) {
297-
builder.append('/').append(part);
342+
StringJoiner joiner = new StringJoiner("/", "", "/index.ts");
343+
for (String part : SLASH_SPLITTER.split(namespace)) {
344+
joiner.add(part);
298345
}
299-
filename = builder.toString();
346+
filename = joiner.toString();
300347
}
301348

302-
return format("types/%s.ts", filename).replace("//", "/");
349+
return format("types/%s", filename).replace("//", "/");
303350
}
304351
}

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,19 @@ public void execute(PluginContext context) {
4646
* @return Returns the created provider.
4747
*/
4848
public static SymbolProvider createSymbolProvider(Model model) {
49-
SymbolVisitor symbolProvider = new SymbolVisitor(model);
49+
return createSymbolProvider(model, null, null);
50+
}
51+
52+
/**
53+
* Creates a TypeScript symbol provider.
54+
*
55+
* @param model Model to generate symbols for.
56+
* @param rootNamespace The namespace that is the root of the shaded target namespace.
57+
* @param targetNamespace The namespace to shade all ShapeIds into.
58+
* @return Returns the created provider.
59+
*/
60+
public static SymbolProvider createSymbolProvider(Model model, String rootNamespace, String targetNamespace) {
61+
SymbolVisitor symbolProvider = new SymbolVisitor(model, rootNamespace, targetNamespace);
5062

5163
// Load reserved words from a new-line delimited file.
5264
ReservedWords reservedWords = new ReservedWordsBuilder()

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Arrays;
1919
import java.util.Objects;
2020
import software.amazon.smithy.codegen.core.CodegenException;
21+
import software.amazon.smithy.codegen.core.ShapeIdShader;
2122
import software.amazon.smithy.model.Model;
2223
import software.amazon.smithy.model.node.Node;
2324
import software.amazon.smithy.model.node.ObjectNode;
@@ -51,7 +52,8 @@ public final class TypeScriptSettings {
5152
public static TypeScriptSettings from(ObjectNode config) {
5253
TypeScriptSettings settings = new TypeScriptSettings();
5354
config.warnIfAdditionalProperties(Arrays.asList(
54-
PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, SERVICE));
55+
PACKAGE, PACKAGE_DESCRIPTION, PACKAGE_JSON, PACKAGE_VERSION, SERVICE,
56+
ShapeIdShader.TARGET_NAMESPACE));
5557
settings.setService(config.expectStringMember(SERVICE).expectShapeId());
5658
settings.setPackageName(config.expectStringMember(PACKAGE).getValue());
5759
settings.setPackageVersion(config.expectStringMember(PACKAGE_VERSION).getValue());

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.Collection;
1919
import java.util.regex.Pattern;
2020
import java.util.stream.Collectors;
21-
import software.amazon.smithy.model.shapes.ToShapeId;
2221
import software.amazon.smithy.utils.StringUtils;
2322

2423
/**
@@ -72,14 +71,4 @@ static String getEnumVariants(Collection<String> values) {
7271
.map(value -> StringUtils.escapeJavaString(value, ""))
7372
.collect(Collectors.joining(" | "));
7473
}
75-
76-
/**
77-
* Formats a Smithy Shape ID namespace as a typescript module.
78-
*
79-
* @param id Shape ID to format.
80-
* @return Returns the TypeScript module name.
81-
*/
82-
static String formatModuleName(ToShapeId id) {
83-
return id.toShapeId().getNamespace().replace(".", "/");
84-
}
8574
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@
1818
import java.nio.file.Path;
1919
import java.nio.file.Paths;
2020
import java.util.function.BiFunction;
21+
import software.amazon.smithy.build.FileManifest;
2122
import software.amazon.smithy.codegen.core.CodegenException;
2223
import software.amazon.smithy.codegen.core.Symbol;
24+
import software.amazon.smithy.codegen.core.SymbolProvider;
2325
import software.amazon.smithy.codegen.core.SymbolReference;
2426
import software.amazon.smithy.model.Model;
27+
import software.amazon.smithy.model.knowledge.NeighborProviderIndex;
28+
import software.amazon.smithy.model.neighbor.Walker;
2529
import software.amazon.smithy.model.shapes.MemberShape;
2630
import software.amazon.smithy.model.shapes.Shape;
2731
import software.amazon.smithy.model.traits.DocumentationTrait;
@@ -57,6 +61,40 @@ final class TypeScriptWriter extends CodeWriter {
5761
putFormatter('T', new TypeScriptSymbolFormatter());
5862
}
5963

64+
/**
65+
* Handles delegating out access to {@code TypeScriptWriter}s based on
66+
* the resolved filename of a symbol.
67+
*
68+
* @param model Model that contains all shapes being generated.
69+
* @param symbolProvider Symbol provider that converts shapes to symbols.
70+
* @param fileManifest The manifest of where files are written.
71+
* @return Returns the created delegator.
72+
*/
73+
static CodeWriterDelegator<TypeScriptWriter> createDelegator(
74+
Model model,
75+
SymbolProvider symbolProvider,
76+
FileManifest fileManifest
77+
) {
78+
Walker walker = new Walker(model.getKnowledge(NeighborProviderIndex.class).getProvider());
79+
80+
return CodeWriterDelegator.<TypeScriptWriter>builder()
81+
.model(model)
82+
.symbolProvider(symbolProvider)
83+
.fileManifest(fileManifest)
84+
.factory((shape, symbol) -> new TypeScriptWriter(symbol.getNamespace()))
85+
.beforeWrite((filename, writer, shapes) -> {
86+
// Add dependencies of the shape and any references it has by
87+
// walking the shape's neighbors.
88+
for (Shape shape : shapes) {
89+
writer.addImport(symbolProvider.toSymbol(shape));
90+
walker.walkShapes(shape).forEach(neighbor -> {
91+
writer.addImport(symbolProvider.toSymbol(neighbor));
92+
});
93+
}
94+
})
95+
.build();
96+
}
97+
6098
/**
6199
* Imports a symbol if necessary.
62100
*

0 commit comments

Comments
 (0)