Skip to content

Commit

Permalink
Merge pull request #10 from join-com/new-generator-part-6
Browse files Browse the repository at this point in the history
New generator: part 6
  • Loading branch information
castarco authored Apr 12, 2021
2 parents 605a94d + 6f114fc commit 145acd2
Show file tree
Hide file tree
Showing 14 changed files with 684 additions and 240 deletions.
7 changes: 6 additions & 1 deletion internal/generator/interface_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,12 @@ func (r *Runner) getInterfaceFieldType(fieldSpec *descriptorpb.FieldDescriptorPr
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
baseType = r.getEnumOrMessageTypeName(fieldSpec.GetTypeName(), false)
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
baseType = r.getEnumOrMessageTypeName(fieldSpec.GetTypeName(), true)
fieldTypeName := fieldSpec.GetTypeName()
if fieldTypeName == ".google.protobuf.Timestamp" {
baseType = "Date"
} else {
baseType = r.getEnumOrMessageTypeName(fieldTypeName, true)
}
}

if fieldSpec.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED {
Expand Down
13 changes: 8 additions & 5 deletions internal/generator/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/join-com/protoc-gen-ts/internal/join_proto"
"google.golang.org/protobuf/compiler/protogen"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)

// Sort of a global state for the generator's runner
Expand All @@ -18,11 +19,12 @@ type Runner struct {
currentProtoFilePath string
currentNamespace string
indentLevel int
packagesByFile map[string]string
filesForExportedPackageSymbols map[string]map[string]string // pkg_name -> symbol -> source file path
alternativeImportNames map[string]map[string]string // "current" source file path -> imported source file path -> alternative import name
generateCodeOptions map[string]bool // source file path -> (should we generate .ts file for it)
importCodeOptions map[string]bool // source file path -> (should we generate imports on .ts files when imported in .proto files)
packagesByFile map[string]string // source file path -> package name
filesForExportedPackageSymbols map[string]map[string]string // pkg_name -> symbol -> source file path
alternativeImportNames map[string]map[string]string // "current" source file path -> imported source file path -> alternative import name
generateCodeOptions map[string]bool // source file path -> (should we generate .ts file for it)
importCodeOptions map[string]bool // source file path -> (should we generate imports on .ts files when imported in .proto files)
messageSpecsByFQN map[string]*descriptorpb.DescriptorProto // message fully qualified name -> message "spec"
}

func NewRunner() Runner {
Expand All @@ -36,6 +38,7 @@ func NewRunner() Runner {
alternativeImportNames: make(map[string]map[string]string),
generateCodeOptions: make(map[string]bool),
importCodeOptions: make(map[string]bool),
messageSpecsByFQN: make(map[string]*descriptorpb.DescriptorProto),
}
}

Expand Down
2 changes: 2 additions & 0 deletions internal/generator/runner_collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ func (r *Runner) collectExportedSymbols(packageName string, proto *descriptorpb.
}

// Collect Messages
currentPackageName := "." + r.packagesByFile[r.currentProtoFilePath]
for _, messageSpec := range proto.GetMessageType() {
symbolsMap[strcase.ToCamel(messageSpec.GetName())] = r.currentProtoFilePath
r.messageSpecsByFQN[currentPackageName + "." + messageSpec.GetName()] = messageSpec
}

// TODO?: Services
Expand Down
181 changes: 0 additions & 181 deletions internal/generator/runner_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,184 +190,3 @@ func (r *Runner) generateTypescriptInterfaceField(

r.P(generatedFileStream, fieldSpec.GetJsonName()+separator+r.getInterfaceFieldType(fieldSpec))
}

func (r *Runner) generateTypescriptMessageClasses(generatedFileStream *protogen.GeneratedFile, protoFile *protogen.File) {
for _, messageSpec := range protoFile.Proto.GetMessageType() {
r.generateTypescriptMessageClass(generatedFileStream, messageSpec)
}
}

func (r *Runner) generateTypescriptMessageClass(generatedFileStream *protogen.GeneratedFile, messageSpec *descriptorpb.DescriptorProto) {
requiredFields := false
messageOptions := messageSpec.GetOptions()
if messageOptions != nil {
if messageOptions.GetDeprecated() {
r.P(generatedFileStream, "/**\n * @deprecated\n */")
}

_requiredFields, found := join_proto.GetBooleanCustomMessageOption("typescript_required_fields", messageOptions, r.extensionTypes)
if found {
requiredFields = _requiredFields
}
}

className := strcase.ToCamel(messageSpec.GetName())
r.P(
generatedFileStream,
"@protobufjs.Type.d('"+className+"')",
"export class "+className+" extends protobufjs.Message<"+className+"> implements ConvertibleTo<I"+className+"> {\n",
)
r.indentLevel += 2

for _, fieldSpec := range messageSpec.GetField() {
r.generateTypescriptClassField(generatedFileStream, fieldSpec, messageSpec, messageOptions, requiredFields)
}

r.generateTypescriptClassPatchedMethods(generatedFileStream, messageSpec, requiredFields)

r.indentLevel -= 2
r.P(generatedFileStream, "}\n")
}

func (r *Runner) generateTypescriptClassField(
generatedFileStream *protogen.GeneratedFile,
fieldSpec *descriptorpb.FieldDescriptorProto,
messageSpec *descriptorpb.DescriptorProto,
messageOptions *descriptorpb.MessageOptions,
requiredFields bool,
) {
fieldOptions := fieldSpec.GetOptions()
if fieldOptions != nil {
if messageOptions.GetDeprecated() {
r.P(generatedFileStream, "/**\n * @deprecated\n */")
}
}

separator := "?: "
requiredField, foundRequired := join_proto.GetBooleanCustomFieldOption("typescript_required", fieldOptions, r.extensionTypes)
optionalField, foundOptional := join_proto.GetBooleanCustomFieldOption("typescript_optional", fieldOptions, r.extensionTypes)
if foundRequired && requiredField && foundOptional && optionalField {
utils.LogError("incompatible options for field " + fieldSpec.GetName() + " in " + messageSpec.GetName())
}
if requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField {
separator = "!: "
}

r.P(
generatedFileStream,
r.getMessageFieldDecorator(fieldSpec),
"public "+fieldSpec.GetJsonName()+separator+r.getClassFieldType(fieldSpec)+"\n",
)
}

func (r *Runner) generateTypescriptClassPatchedMethods(generatedFileStream *protogen.GeneratedFile, messageSpec *descriptorpb.DescriptorProto, requiredFields bool) {
// asInterface method
r.generateAsInterfaceMethod(generatedFileStream, messageSpec, requiredFields)

// Decode method

// Encode method

// Create method
}

func (r *Runner) generateAsInterfaceMethod(generatedFileStream *protogen.GeneratedFile, messageSpec *descriptorpb.DescriptorProto, requiredFields bool) {
className := strcase.ToCamel(messageSpec.GetName())
r.P(generatedFileStream, "public asInterface(): I"+className+"{")
r.indentLevel += 2

if messageHasEnums(messageSpec) {
r.P(generatedFileStream, "return {")
r.indentLevel += 2

for _, fieldSpec := range messageSpec.GetField() {
r.generatePatchedInterfaceField(generatedFileStream, fieldSpec, messageSpec, requiredFields)
}

r.indentLevel -= 2
r.P(generatedFileStream, "}")
} else {
r.P(generatedFileStream, "return this")
}

r.indentLevel -= 2
r.P(generatedFileStream, "}\n")
}

func (r *Runner) generatePatchedInterfaceField(
generatedFileStream *protogen.GeneratedFile,
fieldSpec *descriptorpb.FieldDescriptorProto,
messageSpec *descriptorpb.DescriptorProto,
requiredFields bool,
) {
fieldOptions := fieldSpec.GetOptions()
separator := "?: "
requiredField, foundRequired := join_proto.GetBooleanCustomFieldOption("typescript_required", fieldOptions, r.extensionTypes)
optionalField, foundOptional := join_proto.GetBooleanCustomFieldOption("typescript_optional", fieldOptions, r.extensionTypes)
if foundRequired && requiredField && foundOptional && optionalField {
utils.LogError("incompatible options for field " + fieldSpec.GetName() + " in " + messageSpec.GetName())
}
confirmRequired := false
if requiredFields && !(foundOptional && optionalField) || foundRequired && requiredField {
confirmRequired = true
separator = ": "
}

isRepeated := fieldSpec.GetLabel() == descriptorpb.FieldDescriptorProto_LABEL_REPEATED

value := "this." + fieldSpec.GetJsonName()
switch fieldSpec.GetType() {
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
unionTypeName := r.getEnumOrMessageTypeName(fieldSpec.GetTypeName(), false)
enumTypeName := unionTypeName + "_Enum"
if confirmRequired {
if isRepeated {
value += ".map((e) => " + enumTypeName + "[e]! as " + unionTypeName + "),"
} else {
value = enumTypeName + "[" + value + "]! as " + unionTypeName + ","
}
} else {
if isRepeated {
value += "?.map((e) => " + enumTypeName + "[e]! as " + unionTypeName + "),"
} else {
value = "((" + value + " !== undefined) ? (" + enumTypeName + "[" + value + "]!) : undefined) as " + unionTypeName + " | undefined,"
}
}
case descriptorpb.FieldDescriptorProto_TYPE_MESSAGE:
if confirmRequired {
if isRepeated {
value += ".map((o) => o.asInterface()),"
} else {
value += ".asInterface(),"
}
} else {
if isRepeated {
value += "?.map((o) => o.asInterface()),"
} else {
value += "?.asInterface(),"
}
}
default:
value += ","
}

r.P(generatedFileStream, fieldSpec.GetJsonName()+separator+value)
}

func messageHasEnums(messageSpec *descriptorpb.DescriptorProto) bool {
for _, fieldSpec := range messageSpec.GetField() {
switch t := fieldSpec.GetType(); t {
case descriptorpb.FieldDescriptorProto_TYPE_ENUM:
return true
}
}

for _, nestedMessageSpec := range messageSpec.GetNestedType() {
hasEnums := messageHasEnums(nestedMessageSpec)
if hasEnums {
return true
}
}

return false
}
Loading

0 comments on commit 145acd2

Please sign in to comment.