diff --git a/src/idl_gen_ts.cpp b/src/idl_gen_ts.cpp index 1eb6d814362..e19ea683625 100644 --- a/src/idl_gen_ts.cpp +++ b/src/idl_gen_ts.cpp @@ -39,16 +39,16 @@ struct ImportDefinition { std::string bare_file_path; std::string rel_file_path; std::string object_name; - const Definition* dependent = nullptr; - const Definition* dependency = nullptr; + const Definition *dependent = nullptr; + const Definition *dependency = nullptr; }; struct NsDefinition { std::string path; std::string filepath; std::string symbolic_name; - const Namespace* ns = nullptr; - std::map definitions; + const Namespace *ns = nullptr; + std::map definitions; }; Namer::Config TypeScriptDefaultConfig() { @@ -108,8 +108,8 @@ class TsGenerator : public BaseGenerator { public: typedef std::map import_set; - TsGenerator(const Parser& parser, const std::string& path, - const std::string& file_name) + TsGenerator(const Parser &parser, const std::string &path, + const std::string &file_name) : BaseGenerator(parser, path, file_name, "", "_", "ts"), namer_(WithFlagOptions(TypeScriptDefaultConfig(), parser.opts, path), TypescriptKeywords()), @@ -125,7 +125,7 @@ class TsGenerator : public BaseGenerator { return true; } - std::string GetTypeName(const EnumDef& def, const bool = false, + std::string GetTypeName(const EnumDef &def, const bool = false, const bool force_ns_wrap = false) { if (force_ns_wrap) { return namer_.NamespacedType(def); @@ -133,7 +133,7 @@ class TsGenerator : public BaseGenerator { return namer_.Type(def); } - std::string GetTypeName(const StructDef& def, const bool object_api = false, + std::string GetTypeName(const StructDef &def, const bool object_api = false, const bool force_ns_wrap = false) { if (object_api && parser_.opts.generate_object_based_api) { if (force_ns_wrap) { @@ -152,8 +152,8 @@ class TsGenerator : public BaseGenerator { // Save out the generated code for a single class while adding // declaration boilerplate. - bool SaveType(const Definition& definition, const std::string& class_code, - import_set& imports, import_set& bare_imports) { + bool SaveType(const Definition &definition, const std::string &class_code, + import_set &imports, import_set &bare_imports) { if (!class_code.length()) return true; std::string code; @@ -184,7 +184,7 @@ class TsGenerator : public BaseGenerator { return parser_.opts.file_saver->SaveFile(basename.c_str(), code, false); } - void TrackNsDef(const Definition& definition, std::string type_name) { + void TrackNsDef(const Definition &definition, std::string type_name) { std::string path; std::string filepath; std::string symbolic_name; @@ -227,7 +227,7 @@ class TsGenerator : public BaseGenerator { import_set bare_imports; import_set imports; std::string enumcode; - auto& enum_def = **it; + auto &enum_def = **it; GenEnum(enum_def, &enumcode, imports, false); GenEnum(enum_def, &enumcode, imports, true); std::string type_name = GetTypeName(enum_def); @@ -243,7 +243,7 @@ class TsGenerator : public BaseGenerator { import_set bare_imports; import_set imports; AddImport(bare_imports, "* as flatbuffers", "flatbuffers"); - auto& struct_def = **it; + auto &struct_def = **it; std::string declcode; GenStruct(parser_, struct_def, &declcode, imports); std::string type_name = GetTypeName(struct_def); @@ -267,7 +267,7 @@ class TsGenerator : public BaseGenerator { ns_defs_[nsDef.path] = nsDef; } - for (const auto& it : ns_defs_) { + for (const auto &it : ns_defs_) { code = "// " + std::string(FlatBuffersGeneratedWarning()) + "\n\n" + "/* eslint-disable @typescript-eslint/no-unused-vars, " "@typescript-eslint/no-explicit-any, " @@ -275,7 +275,7 @@ class TsGenerator : public BaseGenerator { // export all definitions in ns entry point module int export_counter = 0; - for (const auto& def : it.second.definitions) { + for (const auto &def : it.second.definitions) { std::vector rel_components; // build path for root level vs child level if (it.second.ns->components.size() > 0) { @@ -309,7 +309,7 @@ class TsGenerator : public BaseGenerator { // re-export child namespace(s) in parent const auto child_ns_level = it.second.ns->components.size() + 1; - for (const auto& it2 : ns_defs_) { + for (const auto &it2 : ns_defs_) { if (it2.second.ns->components.size() != child_ns_level) continue; auto ts_file_path = it2.second.path + ".ts"; code += "export * as " + it2.second.symbolic_name + " from './"; @@ -332,15 +332,15 @@ class TsGenerator : public BaseGenerator { } // Generate a documentation comment, if available. - static void GenDocComment(const std::vector& dc, - std::string* code_ptr, - const char* indent = nullptr) { + static void GenDocComment(const std::vector &dc, + std::string *code_ptr, + const char *indent = nullptr) { if (dc.empty()) { // Don't output empty comment blocks with 0 lines of comment content. return; } - std::string& code = *code_ptr; + std::string &code = *code_ptr; if (indent) code += indent; code += "/**\n"; for (auto it = dc.begin(); it != dc.end(); ++it) { @@ -356,22 +356,22 @@ class TsGenerator : public BaseGenerator { code += " */\n"; } - static void GenDocComment(std::string* code_ptr) { + static void GenDocComment(std::string *code_ptr) { GenDocComment(std::vector(), code_ptr); } // Generate an enum declaration and an enum string lookup table. - void GenEnum(EnumDef& enum_def, std::string* code_ptr, import_set& imports, + void GenEnum(EnumDef &enum_def, std::string *code_ptr, import_set &imports, bool reverse) { if (enum_def.generated) return; if (reverse) return; // FIXME. - std::string& code = *code_ptr; + std::string &code = *code_ptr; GenDocComment(enum_def.doc_comment, code_ptr); code += "export enum "; code += GetTypeName(enum_def); code += " {\n"; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { - auto& ev = **it; + auto &ev = **it; if (!ev.doc_comment.empty()) { if (it != enum_def.Vals().begin()) { code += '\n'; @@ -411,7 +411,7 @@ class TsGenerator : public BaseGenerator { code += "\n"; } - static std::string GenType(const Type& type) { + static std::string GenType(const Type &type) { switch (type.base_type) { case BASE_TYPE_BOOL: case BASE_TYPE_CHAR: @@ -447,7 +447,7 @@ class TsGenerator : public BaseGenerator { } } - std::string GenGetter(const Type& type, const std::string& arguments) { + std::string GenGetter(const Type &type, const std::string &arguments) { switch (type.base_type) { case BASE_TYPE_STRING: return GenBBAccess() + ".__string" + arguments; @@ -472,12 +472,12 @@ class TsGenerator : public BaseGenerator { std::string GenBBAccess() const { return "this.bb!"; } - std::string GenDefaultValue(const FieldDef& field, import_set& imports) { + std::string GenDefaultValue(const FieldDef &field, import_set &imports) { if (field.IsScalarOptional()) { return null_keyword_; } - const auto& value = field.value; + const auto &value = field.value; if (value.type.enum_def && value.type.base_type != BASE_TYPE_UNION && value.type.base_type != BASE_TYPE_VECTOR) { switch (value.type.base_type) { @@ -487,7 +487,7 @@ class TsGenerator : public BaseGenerator { std::string enum_name = AddImport(imports, *value.type.enum_def, *value.type.enum_def) .name; - const EnumVal* val = + const EnumVal *val = value.type.enum_def->FindByValue(value.constant); if (val == nullptr) { val = value.type.enum_def->MinValue(); @@ -507,7 +507,7 @@ class TsGenerator : public BaseGenerator { return "BigInt('" + value.constant + "')"; } default: { - const EnumVal* val = value.type.enum_def->FindByValue(value.constant); + const EnumVal *val = value.type.enum_def->FindByValue(value.constant); if (val == nullptr) { val = value.type.enum_def->MinValue(); } @@ -522,7 +522,16 @@ class TsGenerator : public BaseGenerator { case BASE_TYPE_BOOL: return value.constant == "0" ? "false" : "true"; - case BASE_TYPE_STRING: + case BASE_TYPE_STRING: { + // NOTE: Strings without a default value are parsed as "0" by + // the parser, so therefore we have to treat "0" as a null-signifying + // value. + if (value.constant == "0" || value.constant == "null") { + return "null"; + } else { + return "\"" + value.constant + "\""; + } + } case BASE_TYPE_UNION: case BASE_TYPE_STRUCT: { return null_keyword_; @@ -550,8 +559,8 @@ class TsGenerator : public BaseGenerator { } } - std::string GenTypeName(import_set& imports, const Definition& owner, - const Type& type, bool input, + std::string GenTypeName(import_set &imports, const Definition &owner, + const Type &type, bool input, bool allowNull = false) { if (!input) { if (IsString(type) || type.base_type == BASE_TYPE_STRUCT) { @@ -600,7 +609,7 @@ class TsGenerator : public BaseGenerator { } } - static Type GetUnionUnderlyingType(const Type& type) { + static Type GetUnionUnderlyingType(const Type &type) { if (type.enum_def != nullptr && type.enum_def->underlying_type.base_type != type.base_type) { return type.enum_def->underlying_type; @@ -609,14 +618,14 @@ class TsGenerator : public BaseGenerator { } } - static Type GetUnderlyingVectorType(const Type& vector_type) { + static Type GetUnderlyingVectorType(const Type &vector_type) { return (vector_type.base_type == BASE_TYPE_UTYPE) ? GetUnionUnderlyingType(vector_type) : vector_type; } // Returns the method name for use with add/put calls. - std::string GenWriteMethod(const Type& type) { + std::string GenWriteMethod(const Type &type) { // Forward to signed versions since unsigned versions don't exist switch (type.base_type) { case BASE_TYPE_UTYPE: @@ -647,12 +656,12 @@ class TsGenerator : public BaseGenerator { return value != 1 ? " * " + NumToString(value) : ""; } - void GenStructArgs(import_set& imports, const StructDef& struct_def, - const Definition& owner, std::string* arguments, - const std::string& nameprefix) { + void GenStructArgs(import_set &imports, const StructDef &struct_def, + const Definition &owner, std::string *arguments, + const std::string &nameprefix) { for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; if (IsStruct(field.value.type)) { // Generate arguments for a struct inside a struct. To ensure names // don't clash, and to make it obvious these arguments are constructing @@ -667,15 +676,15 @@ class TsGenerator : public BaseGenerator { } } - void GenStructBody(const StructDef& struct_def, std::string* body, - const std::string& nameprefix) { + void GenStructBody(const StructDef &struct_def, std::string *body, + const std::string &nameprefix) { *body += " builder.prep("; *body += NumToString(struct_def.minalign) + ", "; *body += NumToString(struct_def.bytesize) + ");\n"; for (auto it = struct_def.fields.vec.rbegin(); it != struct_def.fields.vec.rend(); ++it) { - auto& field = **it; + auto &field = **it; if (field.padding) { *body += " builder.pad(" + NumToString(field.padding) + ");\n"; } @@ -772,12 +781,12 @@ class TsGenerator : public BaseGenerator { } } - std::string GenerateNewExpression(const std::string& object_name) { + std::string GenerateNewExpression(const std::string &object_name) { return "new " + namer_.Type(object_name) + "()"; } - void GenerateRootAccessor(StructDef& struct_def, std::string* code_ptr, - std::string& code, const std::string& object_name, + void GenerateRootAccessor(StructDef &struct_def, std::string *code_ptr, + std::string &code, const std::string &object_name, bool size_prefixed) { if (!struct_def.fixed) { GenDocComment(code_ptr); @@ -797,8 +806,8 @@ class TsGenerator : public BaseGenerator { } } - void GenerateFinisher(StructDef& struct_def, std::string* code_ptr, - std::string& code, bool size_prefixed) { + void GenerateFinisher(StructDef &struct_def, std::string *code_ptr, + std::string &code, bool size_prefixed) { if (parser_.root_struct_def_ == &struct_def) { std::string sizePrefixed("SizePrefixed"); GenDocComment(code_ptr); @@ -821,14 +830,14 @@ class TsGenerator : public BaseGenerator { } } - bool UnionHasStringType(const EnumDef& union_enum) { + bool UnionHasStringType(const EnumDef &union_enum) { return std::any_of(union_enum.Vals().begin(), union_enum.Vals().end(), - [](const EnumVal* ev) { + [](const EnumVal *ev) { return !ev->IsZero() && IsString(ev->union_type); }); } - std::string GenUnionGenericTypeTS(const EnumDef& union_enum) { + std::string GenUnionGenericTypeTS(const EnumDef &union_enum) { // TODO: make it work without any // return std::string("T") + (UnionHasStringType(union_enum) ? "|string" : // ""); @@ -836,13 +845,13 @@ class TsGenerator : public BaseGenerator { (UnionHasStringType(union_enum) ? "|string" : ""); } - std::string GenUnionTypeTS(const EnumDef& union_enum, import_set& imports) { + std::string GenUnionTypeTS(const EnumDef &union_enum, import_set &imports) { std::string ret; std::set type_list; for (auto it = union_enum.Vals().begin(); it != union_enum.Vals().end(); ++it) { - const auto& ev = **it; + const auto &ev = **it; if (ev.IsZero()) { continue; } @@ -865,8 +874,8 @@ class TsGenerator : public BaseGenerator { return ret; } - static bool CheckIfNameClashes(const import_set& imports, - const std::string& name) { + static bool CheckIfNameClashes(const import_set &imports, + const std::string &name) { // TODO: this would be better as a hashset. for (auto it = imports.begin(); it != imports.end(); it++) { if (it->second.name == name) { @@ -876,11 +885,11 @@ class TsGenerator : public BaseGenerator { return false; } - std::string GenSymbolExpression(const StructDef& struct_def, + std::string GenSymbolExpression(const StructDef &struct_def, const bool has_name_clash, - const std::string& import_name, - const std::string& name, - const std::string& object_name) { + const std::string &import_name, + const std::string &name, + const std::string &object_name) { std::string symbols_expression; if (has_name_clash) { @@ -904,10 +913,11 @@ class TsGenerator : public BaseGenerator { return symbols_expression; } - std::string GenSymbolExpression(const EnumDef& enum_def, + std::string GenSymbolExpression(const EnumDef &enum_def, const bool has_name_clash, - const std::string& import_name, - const std::string& name, const std::string&) { + const std::string &import_name, + const std::string &name, + const std::string &) { std::string symbols_expression; if (has_name_clash) { symbols_expression += import_name + " as " + name; @@ -923,7 +933,7 @@ class TsGenerator : public BaseGenerator { return symbols_expression; } - std::vector PathComponents(const std::string& path) const { + std::vector PathComponents(const std::string &path) const { std::vector components; size_t start = 0; while (start < path.size()) { @@ -938,8 +948,8 @@ class TsGenerator : public BaseGenerator { return components; } - std::string RelativeDirectory(const std::vector& from, - const std::vector& to) const { + std::string RelativeDirectory(const std::vector &from, + const std::vector &to) const { size_t common = 0; while (common < from.size() && common < to.size() && from[common] == to[common]) { @@ -966,8 +976,8 @@ class TsGenerator : public BaseGenerator { } template - ImportDefinition AddImport(import_set& imports, const Definition& dependent, - const DefinitionT& dependency) { + ImportDefinition AddImport(import_set &imports, const Definition &dependent, + const DefinitionT &dependency) { // The unique name of the dependency, fully qualified in its namespace. const std::string unique_name = GetTypeName( dependency, /*object_api = */ false, /*force_ns_wrap=*/true); @@ -992,31 +1002,24 @@ class TsGenerator : public BaseGenerator { const std::string symbols_expression = GenSymbolExpression( dependency, has_name_clash, import_name, name, object_name); - const Namespace* dependent_ns = dependent.defined_namespace - ? dependent.defined_namespace - : parser_.empty_namespace_; - const Namespace* dependency_ns = dependency.defined_namespace - ? dependency.defined_namespace - : parser_.empty_namespace_; - - const std::string dependent_dirs = - namer_.Directories(*dependent_ns, SkipDir::OutputPath); - const std::string dependency_dirs = - namer_.Directories(*dependency_ns, SkipDir::OutputPath); - - const auto dependent_components = PathComponents(dependent_dirs); - const auto dependency_components = PathComponents(dependency_dirs); - - std::string rel_dir = - RelativeDirectory(dependent_components, dependency_components); - if (rel_dir.empty()) rel_dir = "."; - if (!rel_dir.empty()) rel_dir += kPathSeparator; - - std::string rel_file_path = - rel_dir + namer_.File(dependency, SkipFile::SuffixAndExtension); + std::string bare_file_path; + std::string rel_file_path; + if (dependent.defined_namespace) { + const auto &dep_comps = dependent.defined_namespace->components; + for (size_t i = 0; i < dep_comps.size(); i++) { + rel_file_path += i == 0 ? ".." : (kPathSeparator + std::string("..")); + } + if (dep_comps.size() == 0) { + rel_file_path += "."; + } + } else { + rel_file_path += ".."; + } - std::string bare_file_path = - kPathSeparator + dependency_dirs + + bare_file_path += + kPathSeparator + + namer_.Directories(dependency.defined_namespace->components, + SkipDir::OutputPath) + namer_.File(dependency, SkipFile::SuffixAndExtension); ImportDefinition import; @@ -1037,7 +1040,7 @@ class TsGenerator : public BaseGenerator { return import; } - void AddImport(import_set& imports, std::string import_name, + void AddImport(import_set &imports, std::string import_name, std::string fileName) { ImportDefinition import; import.name = import_name; @@ -1047,16 +1050,16 @@ class TsGenerator : public BaseGenerator { } // Generate a TS union type based on a union's enum - std::string GenObjApiUnionTypeTS(import_set& imports, - const StructDef& dependent, - const IDLOptions&, - const EnumDef& union_enum) { + std::string GenObjApiUnionTypeTS(import_set &imports, + const StructDef &dependent, + const IDLOptions &, + const EnumDef &union_enum) { std::string ret = ""; std::set type_list; for (auto it = union_enum.Vals().begin(); it != union_enum.Vals().end(); ++it) { - const auto& ev = **it; + const auto &ev = **it; if (ev.IsZero()) { continue; } @@ -1082,17 +1085,17 @@ class TsGenerator : public BaseGenerator { return ret; } - std::string GenUnionConvFuncName(const EnumDef& enum_def) { + std::string GenUnionConvFuncName(const EnumDef &enum_def) { return namer_.Function("unionTo", enum_def); } - std::string GenUnionListConvFuncName(const EnumDef& enum_def) { + std::string GenUnionListConvFuncName(const EnumDef &enum_def) { return namer_.Function("unionListTo", enum_def); } - std::string GenUnionConvFunc(const Type& union_type, import_set& imports) { + std::string GenUnionConvFunc(const Type &union_type, import_set &imports) { if (union_type.enum_def) { - const auto& enum_def = *union_type.enum_def; + const auto &enum_def = *union_type.enum_def; const auto valid_union_type = GenUnionTypeTS(enum_def, imports); const auto valid_union_type_with_null = @@ -1106,13 +1109,13 @@ class TsGenerator : public BaseGenerator { const auto enum_type = AddImport(imports, enum_def, enum_def).name; - const auto union_enum_loop = [&](const std::string& accessor_str) { + const auto union_enum_loop = [&](const std::string &accessor_str) { ret += " switch(" + enum_type + "[type]) {\n"; ret += " case 'NONE': return " + null_keyword_ + "; \n"; for (auto it = enum_def.Vals().begin(); it != enum_def.Vals().end(); ++it) { - const auto& ev = **it; + const auto &ev = **it; if (ev.IsZero()) { continue; } @@ -1156,12 +1159,12 @@ class TsGenerator : public BaseGenerator { // Used for generating a short function that returns the correct class // based on union enum type. Assume the context is inside the non object api // type - std::string GenUnionValTS(import_set& imports, const StructDef& dependent, - const std::string& field_name, - const Type& union_type, + std::string GenUnionValTS(import_set &imports, const StructDef &dependent, + const std::string &field_name, + const Type &union_type, const bool is_array = false) { if (union_type.enum_def) { - const auto& enum_def = *union_type.enum_def; + const auto &enum_def = *union_type.enum_def; const auto enum_type = AddImport(imports, dependent, enum_def).name; const std::string union_accessor = "this." + field_name; @@ -1220,22 +1223,21 @@ class TsGenerator : public BaseGenerator { return ""; } - std::string GenNullCheckConditional(const std::string& nullCheckVar, - const std::string& trueVal, - const std::string& falseVal = "") { - std::string false_val = falseVal.empty() ? null_keyword_ : falseVal; + std::string GenNullCheckConditional(const std::string &nullCheckVar, + const std::string &trueVal, + const std::string &falseVal) { return "(" + nullCheckVar + " !== " + null_keyword_ + " ? " + trueVal + - " : " + false_val + ")"; + " : " + falseVal + ")"; } - std::string GenStructMemberValueTS(const StructDef& struct_def, - const std::string& prefix, - const std::string& delimiter, + std::string GenStructMemberValueTS(const StructDef &struct_def, + const std::string &prefix, + const std::string &delimiter, const bool nullCheck = true) { std::string ret; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; auto curr_member_accessor = prefix + "." + namer_.Method(field); if (prefix != "this") { @@ -1269,9 +1271,9 @@ class TsGenerator : public BaseGenerator { return ret; } - void GenObjApi(const Parser& parser, StructDef& struct_def, - std::string& obj_api_unpack_func, std::string& obj_api_class, - import_set& imports) { + void GenObjApi(const Parser &parser, StructDef &struct_def, + std::string &obj_api_unpack_func, std::string &obj_api_class, + import_set &imports) { const auto class_name = GetTypeName(struct_def, /*object_api=*/true); std::string unpack_func = "\nunpack(): " + class_name + @@ -1312,7 +1314,7 @@ class TsGenerator : public BaseGenerator { for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; if (field.deprecated) continue; const auto field_method = namer_.Method(field); @@ -1334,10 +1336,9 @@ class TsGenerator : public BaseGenerator { // Emit a scalar field const auto is_string = IsString(field.value.type); if (IsScalar(field.value.type.base_type) || is_string) { - const auto has_null_default = is_string || HasNullDefault(field); - - field_type += GenTypeName(imports, field, field.value.type, false, - has_null_default); + field_type += + GenTypeName(imports, field, field.value.type, false, + !HasDefaultValue(field) || HasNullDefault(field)); field_val = "this." + namer_.Method(field) + "()"; if (field.value.type.base_type != BASE_TYPE_STRING) { @@ -1354,7 +1355,7 @@ class TsGenerator : public BaseGenerator { auto is_vector = false; switch (field.value.type.base_type) { case BASE_TYPE_STRUCT: { - const auto& sd = *field.value.type.struct_def; + const auto &sd = *field.value.type.struct_def; field_type += AddImport(imports, struct_def, sd).object_name; const std::string field_accessor = @@ -1384,7 +1385,7 @@ class TsGenerator : public BaseGenerator { switch (vectortype.base_type) { case BASE_TYPE_STRUCT: { - const auto& sd = *field.value.type.struct_def; + const auto &sd = *field.value.type.struct_def; const auto field_type_name = GetTypeName(sd, /*object_api=*/true); field_type += field_type_name; @@ -1473,7 +1474,7 @@ class TsGenerator : public BaseGenerator { switch (vectortype.base_type) { case BASE_TYPE_STRUCT: { - const auto& sd = *field.value.type.struct_def; + const auto &sd = *field.value.type.struct_def; const auto field_type_name = GetTypeName(sd, /*object_api=*/true); field_type += field_type_name; @@ -1651,24 +1652,24 @@ class TsGenerator : public BaseGenerator { obj_api_unpack_func = unpack_func + "\n\n" + unpack_to_func; } - static bool CanCreateFactoryMethod(const StructDef& struct_def) { + static bool CanCreateFactoryMethod(const StructDef &struct_def) { // to preserve backwards compatibility, we allow the first field to be a // struct return struct_def.fields.vec.size() < 2 || std::all_of( std::begin(struct_def.fields.vec) + 1, std::end(struct_def.fields.vec), - [](const FieldDef* f) -> bool { + [](const FieldDef *f) -> bool { FLATBUFFERS_ASSERT(f != nullptr); return f->value.type.base_type != BASE_TYPE_STRUCT; }); } // Generate an accessor struct with constructor for a flatbuffers struct. - void GenStruct(const Parser& parser, StructDef& struct_def, - std::string* code_ptr, import_set& imports) { + void GenStruct(const Parser &parser, StructDef &struct_def, + std::string *code_ptr, import_set &imports) { if (struct_def.generated) return; - std::string& code = *code_ptr; + std::string &code = *code_ptr; // Special case for the root struct, since no one will necessarily reference // it, we have to explicitly add it to the import list. @@ -1721,7 +1722,7 @@ class TsGenerator : public BaseGenerator { // Emit field accessors for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; if (field.deprecated) continue; std::string offset_prefix = ""; @@ -1731,13 +1732,14 @@ class TsGenerator : public BaseGenerator { offset_prefix = " const offset = " + GenBBAccess() + ".__offset(this.bb_pos, " + NumToString(field.value.offset) + ");\n"; - offset_prefix += " return offset ? "; + offset_prefix += " return "; } // Emit a scalar field const auto is_string = IsString(field.value.type); if (IsScalar(field.value.type.base_type) || is_string) { - const auto has_null_default = is_string || HasNullDefault(field); + const auto has_null_default = + !field.IsRequired() && !HasDefaultValue(field); GenDocComment(field.doc_comment, code_ptr); std::string prefix = namer_.Method(field) + "("; @@ -1774,9 +1776,16 @@ class TsGenerator : public BaseGenerator { if (is_string) { index += ", optionalEncoding"; } - code += - offset_prefix + GenGetter(field.value.type, "(" + index + ")"); - if (field.value.type.base_type != BASE_TYPE_ARRAY) { + if (field.IsRequired()) { + code += + offset_prefix + GenGetter(field.value.type, "(" + index + ")"); + ; + } else { + code += offset_prefix + "offset ? " + + GenGetter(field.value.type, "(" + index + ")"); + } + if (field.value.type.base_type != BASE_TYPE_ARRAY && + !field.IsRequired()) { code += " : " + GenDefaultValue(field, imports); } code += ";\n"; @@ -1801,8 +1810,8 @@ class TsGenerator : public BaseGenerator { code += MaybeAdd(field.value.offset) + ", " + GenBBAccess() + ");\n"; } else { - code += offset_prefix + "(obj || " + GenerateNewExpression(type) + - ").__init("; + code += offset_prefix + "offset ? (obj || " + + GenerateNewExpression(type) + ").__init("; code += field.value.type.struct_def->fixed ? "this.bb_pos + offset" : GenBBAccess() + ".__indirect(this.bb_pos + offset)"; @@ -1960,7 +1969,7 @@ class TsGenerator : public BaseGenerator { code += "):" + vectortypename + "|" + null_keyword_ + " {\n"; if (vectortype.base_type == BASE_TYPE_STRUCT) { - code += offset_prefix + "(obj || " + + code += offset_prefix + "offset ? (obj || " + GenerateNewExpression(vectortypename); code += ").__init("; code += vectortype.struct_def->fixed @@ -1973,7 +1982,8 @@ class TsGenerator : public BaseGenerator { } else if (IsString(vectortype)) { index += ", optionalEncoding"; } - code += offset_prefix + GenGetter(vectortype, "(" + index + ")"); + code += offset_prefix + "offset ? " + + GenGetter(vectortype, "(" + index + ")"); } code += " : "; if (field.value.type.element == BASE_TYPE_BOOL) { @@ -1998,14 +2008,14 @@ class TsGenerator : public BaseGenerator { GenDocComment(field.doc_comment, code_ptr); code += namer_.Method(field); - const auto& union_enum = *(field.value.type.enum_def); + const auto &union_enum = *(field.value.type.enum_def); const auto union_type = GenUnionGenericTypeTS(union_enum); code += "(obj:" + union_type + "):" + union_type + "|" + null_keyword_ + " " "{\n"; - code += offset_prefix + + code += offset_prefix + "offset ? " + GenGetter(field.value.type, "(obj, this.bb_pos + offset)") + " : " + null_keyword_ + ";\n"; break; @@ -2058,7 +2068,7 @@ class TsGenerator : public BaseGenerator { // Emit a length helper GenDocComment(code_ptr); code += namer_.Method(field, "Length"); - code += "():number {\n" + offset_prefix; + code += "():number {\n" + offset_prefix + "offset ? "; code += GenBBAccess() + ".__vector_len(this.bb_pos + offset) : 0;\n}\n\n"; @@ -2069,15 +2079,30 @@ class TsGenerator : public BaseGenerator { GenDocComment(code_ptr); code += namer_.Method(field, "Array"); - code += "():" + GenType(vectorType) + "Array|" + null_keyword_ + - " {\n" + offset_prefix; - - code += "new " + GenType(vectorType) + "Array(" + GenBBAccess() + - ".bytes().buffer, " + GenBBAccess() + - ".bytes().byteOffset + " + GenBBAccess() + - ".__vector(this.bb_pos + offset), " + GenBBAccess() + - ".__vector_len(this.bb_pos + offset)) : " + null_keyword_ + - ";\n}\n\n"; + + std::string return_type = + (field.IsRequired() || HasDefaultValue(field)) + ? "Array" + : ("Array|" + null_keyword_); + if (field.IsRequired()) { + code += "():" + GenType(vectorType) + return_type + " {\n" + + offset_prefix + "new " + GenType(vectorType) + "Array(" + + GenBBAccess() + ".bytes().buffer, " + GenBBAccess() + + ".bytes().byteOffset + " + GenBBAccess() + + ".__vector(this.bb_pos + offset), " + GenBBAccess() + + ".__vector_len(this.bb_pos + offset));\n}\n\n"; + } else { + std::string value = HasDefaultValue(field) + ? "new " + GenType(vectorType) + "Array()" + : "null"; + code += "():" + GenType(vectorType) + return_type + " {\n" + + offset_prefix + "offset ? new " + GenType(vectorType) + + "Array(" + GenBBAccess() + ".bytes().buffer, " + + GenBBAccess() + ".bytes().byteOffset + " + GenBBAccess() + + ".__vector(this.bb_pos + offset), " + GenBBAccess() + + ".__vector_len(this.bb_pos + offset)) : " + value + + ";\n}\n\n"; + } } } } @@ -2129,7 +2154,7 @@ class TsGenerator : public BaseGenerator { // Generate a set of static methods that allow table construction for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; if (field.deprecated) continue; const auto argname = GetArgName(field); @@ -2173,7 +2198,7 @@ class TsGenerator : public BaseGenerator { std::string type = GenTypeName(imports, struct_def, vector_type, true) + "[]"; if (type == "number[]") { - const auto& array_type = GenType(vector_type); + const auto &array_type = GenType(vector_type); // the old type should be deprecated in the future std::string type_old = "number[]|Uint8Array"; std::string type_new = "number[]|" + array_type + "Array"; @@ -2225,7 +2250,7 @@ class TsGenerator : public BaseGenerator { code += " const offset = builder.endObject();\n"; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - auto& field = **it; + auto &field = **it; if (!field.deprecated && field.IsRequired()) { code += " builder.requiredField(offset, "; code += NumToString(field.value.offset); @@ -2245,7 +2270,7 @@ class TsGenerator : public BaseGenerator { code += "(builder:flatbuffers.Builder"; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - const auto& field = **it; + const auto &field = **it; if (field.deprecated) continue; code += ", " + GetArgName(field) + ":" + GetArgType(imports, struct_def, field, true); @@ -2258,7 +2283,7 @@ class TsGenerator : public BaseGenerator { std::string methodPrefix = object_name; for (auto it = struct_def.fields.vec.begin(); it != struct_def.fields.vec.end(); ++it) { - const auto& field = **it; + const auto &field = **it; if (field.deprecated) continue; const auto arg_name = GetArgName(field); @@ -2304,17 +2329,45 @@ class TsGenerator : public BaseGenerator { } } - bool HasNullDefault(const FieldDef& field) { + bool HasNullDefault(const FieldDef &field) { return field.IsOptional() && field.value.constant == "null"; } - std::string GetArgType(import_set& imports, const Definition& owner, - const FieldDef& field, bool allowNull) { + static bool HasDefaultValue(const FieldDef &field) { + switch (field.value.type.base_type) { + // These types can't have defaults + case BASE_TYPE_UNION: + case BASE_TYPE_STRUCT: { + return false; + } + + // Arrays always have a default (empty array) + case BASE_TYPE_ARRAY: + return true; + + // The only supported default for vectors is [] + case BASE_TYPE_VECTOR: + return field.value.constant == "[]"; + + // Even strings are presumed to be null-default if the default value is + // "0", this is intended behavior. + case BASE_TYPE_STRING: { + return field.value.constant != "0" && field.value.constant != "null"; + } + + default: { + return field.value.constant != "null"; + } + } + } + + std::string GetArgType(import_set &imports, const Definition &owner, + const FieldDef &field, bool allowNull) { return GenTypeName(imports, owner, field.value.type, true, allowNull && field.IsOptional()); } - std::string GetArgName(const FieldDef& field) { + std::string GetArgName(const FieldDef &field) { auto argname = namer_.Variable(field); if (!IsScalar(field.value.type.base_type)) { argname += "Offset"; @@ -2322,21 +2375,21 @@ class TsGenerator : public BaseGenerator { return argname; } - std::string GetPrefixedName(const StructDef& struct_def, - const char* prefix = "") { + std::string GetPrefixedName(const StructDef &struct_def, + const char *prefix = "") { return prefix + struct_def.name; } }; // namespace ts } // namespace ts -static bool GenerateTS(const Parser& parser, const std::string& path, - const std::string& file_name) { +static bool GenerateTS(const Parser &parser, const std::string &path, + const std::string &file_name) { ts::TsGenerator generator(parser, path, file_name); return generator.generate(); } -static std::string TSMakeRule(const Parser& parser, const std::string& path, - const std::string& file_name) { +static std::string TSMakeRule(const Parser &parser, const std::string &path, + const std::string &file_name) { std::string filebase = flatbuffers::StripPath(flatbuffers::StripExtension(file_name)); ts::TsGenerator generator(parser, path, file_name); @@ -2354,35 +2407,36 @@ namespace { class TsCodeGenerator : public CodeGenerator { public: - Status GenerateCode(const Parser& parser, const std::string& path, - const std::string& filename) override { + Status GenerateCode(const Parser &parser, const std::string &path, + const std::string &filename) override { if (!GenerateTS(parser, path, filename)) { return Status::ERROR; } return Status::OK; } - Status GenerateCode(const uint8_t*, int64_t, const CodeGenOptions&) override { + Status GenerateCode(const uint8_t *, int64_t, + const CodeGenOptions &) override { return Status::NOT_IMPLEMENTED; } - Status GenerateMakeRule(const Parser& parser, const std::string& path, - const std::string& filename, - std::string& output) override { + Status GenerateMakeRule(const Parser &parser, const std::string &path, + const std::string &filename, + std::string &output) override { output = TSMakeRule(parser, path, filename); return Status::OK; } - Status GenerateGrpcCode(const Parser& parser, const std::string& path, - const std::string& filename) override { + Status GenerateGrpcCode(const Parser &parser, const std::string &path, + const std::string &filename) override { if (!GenerateTSGRPC(parser, path, filename)) { return Status::ERROR; } return Status::OK; } - Status GenerateRootFile(const Parser& parser, - const std::string& path) override { + Status GenerateRootFile(const Parser &parser, + const std::string &path) override { (void)parser; (void)path; return Status::NOT_IMPLEMENTED; diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 569c76762d3..caeca93d253 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -2809,7 +2809,8 @@ bool Parser::SupportsOptionalScalars() const { bool Parser::SupportsDefaultVectorsAndStrings() const { static FLATBUFFERS_CONSTEXPR unsigned long supported_langs = IDLOptions::kRust | IDLOptions::kSwift | IDLOptions::kNim | - IDLOptions::kCpp | IDLOptions::kBinary | IDLOptions::kJson; + IDLOptions::kCpp | IDLOptions::kBinary | IDLOptions::kJson | + IDLOptions::kTs; return !(opts.lang_to_generate & ~supported_langs); } diff --git a/tests/even_more_defaults.fbs b/tests/even_more_defaults.fbs new file mode 100644 index 00000000000..1b87ffa4c52 --- /dev/null +++ b/tests/even_more_defaults.fbs @@ -0,0 +1,23 @@ + +enum ABC: int { A, B, C } + +struct EvenMoreStruct { + g:[int64:2]; +} + +table EvenMoreDefaults { + str: EvenMoreStruct; + ints: [int] = []; + floats: [float] = [ ]; + float_optional: [float]; + bytes_optional: [ubyte]; + floats_required: [float] (required); + empty_string: string = ""; + some_string: string = "some"; + zero_string: string = "0"; + a_string:string (key); + required_string: string (required); + abcs: [ABC] = []; + bools: [bool] = []; + one_bool: bool = true; +} diff --git a/tests/ts/TypeScriptTest.py b/tests/ts/TypeScriptTest.py index 957c61288b9..9c1765f092f 100755 --- a/tests/ts/TypeScriptTest.py +++ b/tests/ts/TypeScriptTest.py @@ -87,6 +87,20 @@ def esbuild(input, output): ) esbuild("monster_test.ts", "monster_test_generated.cjs") +flatc( + options=[ + "--ts", + "--reflect-names", + "--gen-name-strings", + "--gen-mutable", + "--gen-object-api", + "--ts-entry-points", + "--ts-flat-files", + ], + schema="../even_more_defaults.fbs", + include="../include_test", +) + flatc( options=["--gen-object-api", "-b"], schema="../monster_test.fbs", diff --git a/tests/ts/my-game/example/monster.ts b/tests/ts/my-game/example/monster.ts index 04ca1ac73a8..c17bcc70200 100644 --- a/tests/ts/my-game/example/monster.ts +++ b/tests/ts/my-game/example/monster.ts @@ -83,9 +83,9 @@ mutate_hp(value:number):boolean { name():string|null name(optionalEncoding:flatbuffers.Encoding):string|Uint8Array|null -name(optionalEncoding?:any):string|Uint8Array|null { +name(optionalEncoding?:any):string|Uint8Array { const offset = this.bb!.__offset(this.bb_pos, 10); - return offset ? this.bb!.__string(this.bb_pos + offset, optionalEncoding) : null; + return this.bb!.__string(this.bb_pos + offset, optionalEncoding); } inventory(index: number):number|null {