[TS] Add single-file ts codegen & bazel rule for typescript (#7161)

The headline here is adding a flatbuffer_ts_library rule for generating
typescript code in bazel. This entails some non-trivial other changes,
but ideally none are user-visible.

In particular:
* Added a --ts-flat-file flag that generates a single *_generated.ts
  file instead of separate files for each typescript type. This makes
  bazel much happier.
* Import the bazel rules_nodejs stuff needed to support building
  typescript in bazel
* Move flatbuffers.ts to index.ts because I wasn't sure how to make
  bazel comprehend the "main" attribute of the package.json. Happy
  to take another stab at figuring that out if really needed.
* Fix another couple keyword escaping spots in typescript...
This commit is contained in:
James Kuszmaul
2022-03-10 10:08:13 -08:00
committed by GitHub
parent 2f84c60385
commit e5f331db99
19 changed files with 1549 additions and 39 deletions

View File

@@ -213,6 +213,8 @@ const static FlatCOption options[] = {
{ "", "json-nested-bytes", "",
"Allow a nested_flatbuffer field to be parsed as a vector of bytes"
"in JSON, which is unsafe unless checked by a verifier afterwards." },
{ "", "ts-flat-files", "",
"Only generated one typescript file per .fbs file." },
};
static void AppendTextWrappedString(std::stringstream &ss, std::string &text,
@@ -550,6 +552,8 @@ int FlatCompiler::Compile(int argc, const char **argv) {
opts.cs_global_alias = true;
} else if (arg == "--json-nested-bytes") {
opts.json_nested_legacy_flatbuffers = true;
} else if (arg == "--ts-flat-files") {
opts.ts_flat_file = true;
} else {
for (size_t i = 0; i < params_.num_generators; ++i) {
if (arg == "--" + params_.generators[i].option.long_opt ||
@@ -588,6 +592,10 @@ int FlatCompiler::Compile(int argc, const char **argv) {
"well.");
}
if (opts.ts_flat_file && opts.generate_all) {
Error("Combining --ts-flat-file and --gen-all is not supported.");
}
flatbuffers::Parser conform_parser;
if (!conform_to_schema.empty()) {
std::string contents;

View File

@@ -33,8 +33,8 @@ struct ImportDefinition {
std::string export_statement;
std::string bare_file_path;
std::string rel_file_path;
const Definition *dependent;
const Definition *dependency;
const Definition *dependent = nullptr;
const Definition *dependency = nullptr;
};
enum AnnotationType { kParam = 0, kType = 1, kReturns = 2 };
@@ -110,6 +110,10 @@ class TsGenerator : public BaseGenerator {
for (auto kw = keywords; *kw; kw++) keywords_.insert(*kw);
}
bool generate() {
if (parser_.opts.ts_flat_file && parser_.opts.generate_all) {
// Not implemented; warning message should have beem emitted by flatc.
return false;
}
generateEnums();
generateStructs();
generateEntry();
@@ -119,26 +123,38 @@ 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 &classcode,
import_set &imports, import_set &bare_imports) const {
import_set &imports, import_set &bare_imports) {
if (!classcode.length()) return true;
std::string code =
"// " + std::string(FlatBuffersGeneratedWarning()) + "\n\n";
std::string code;
for (auto it = bare_imports.begin(); it != bare_imports.end(); it++)
code += it->second.import_statement + "\n";
if (!bare_imports.empty()) code += "\n";
if (!parser_.opts.ts_flat_file) {
code += "// " + std::string(FlatBuffersGeneratedWarning()) + "\n\n";
for (auto it = imports.begin(); it != imports.end(); it++)
if (it->second.dependency != &definition) // do not import itself
for (auto it = bare_imports.begin(); it != bare_imports.end(); it++) {
code += it->second.import_statement + "\n";
if (!imports.empty()) code += "\n\n";
}
if (!bare_imports.empty()) code += "\n";
for (auto it = imports.begin(); it != imports.end(); it++) {
if (it->second.dependency != &definition) {
code += it->second.import_statement + "\n";
}
}
if (!imports.empty()) code += "\n\n";
}
code += classcode;
auto filename =
NamespaceDir(*definition.defined_namespace, true) +
ConvertCase(definition.name, Case::kDasher, Case::kUpperCamel) + ".ts";
return SaveFile(filename.c_str(), code, false);
if (parser_.opts.ts_flat_file) {
flat_file_ += code;
flat_file_definitions_.insert(&definition);
return true;
} else {
return SaveFile(filename.c_str(), code, false);
}
}
private:
@@ -150,6 +166,18 @@ class TsGenerator : public BaseGenerator {
import_set imports_all_;
// The following three members are used when generating typescript code into a
// single file rather than creating separate files for each type.
// flat_file_ contains the aggregated contents of the file prior to being
// written to disk.
std::string flat_file_;
// flat_file_definitions_ tracks which types have been written to flat_file_.
std::unordered_set<const Definition *> flat_file_definitions_;
// This maps from import names to types to import.
std::map<std::string, std::map<std::string, std::string>>
flat_file_import_declarations_;
// Generate code for all enums.
void generateEnums() {
for (auto it = parser_.enums_.vec.begin(); it != parser_.enums_.vec.end();
@@ -183,10 +211,48 @@ class TsGenerator : public BaseGenerator {
// Generate code for a single entry point module.
void generateEntry() {
std::string code;
for (auto it = imports_all_.begin(); it != imports_all_.end(); it++)
code += it->second.export_statement + "\n";
std::string path = "./" + path_ + file_name_ + ".ts";
SaveFile(path.c_str(), code, false);
if (parser_.opts.ts_flat_file) {
code += "import * as flatbuffers from 'flatbuffers';\n";
for (const auto &it : flat_file_import_declarations_) {
// Note that we do end up generating an import for ourselves, which
// should generally be harmless.
// TODO: Make it so we don't generate a self-import; this will also
// require modifying AddImport to ensure that we don't use
// namespace-prefixed names anywhere...
std::string file = it.first;
if (file.empty()) {
continue;
}
std::string noext = flatbuffers::StripExtension(file);
std::string basename = flatbuffers::StripPath(noext);
std::string include_file = GeneratedFileName(
parser_.opts.include_prefix,
parser_.opts.keep_include_path ? noext : basename, parser_.opts);
// TODO: what is the right behavior when different include flags are
// specified here? Should we always be adding the "./" for a relative
// path or turn it off if --include-prefix is specified, or something
// else?
std::string include_name = "./" + flatbuffers::StripExtension(include_file);
code += "import {";
for (const auto &pair : it.second) {
code += EscapeKeyword(pair.first) + " as " +
EscapeKeyword(pair.second) + ", ";
}
code.resize(code.size() - 2);
code += "} from '" + include_name + "';\n";
}
code += "\n\n";
code += flat_file_;
const std::string filename =
GeneratedFileName(path_, file_name_, parser_.opts);
SaveFile(filename.c_str(), code, false);
} else {
for (auto it = imports_all_.begin(); it != imports_all_.end(); it++) {
code += it->second.export_statement + "\n";
}
std::string path = "./" + path_ + file_name_ + ".ts";
SaveFile(path.c_str(), code, false);
}
}
// Generate a documentation comment, if available.
@@ -553,7 +619,9 @@ class TsGenerator : public BaseGenerator {
const StructDef &dependency) {
std::string ns;
const auto &depc_comps = dependency.defined_namespace->components;
for (auto it = depc_comps.begin(); it != depc_comps.end(); it++) ns += *it;
for (auto it = depc_comps.begin(); it != depc_comps.end(); it++) {
ns += *it;
}
std::string unique_name = ns + dependency.name;
std::string import_name = dependency.name;
std::string long_import_name;
@@ -565,6 +633,20 @@ class TsGenerator : public BaseGenerator {
break;
}
}
if (parser_.opts.ts_flat_file) {
std::string file = dependency.declaration_file == nullptr
? dependency.file
: dependency.declaration_file->substr(2);
file = RelativeToRootPath(StripFileName(AbsolutePath(dependent.file)),
dependency.file).substr(2);
long_import_name = ns + import_name;
flat_file_import_declarations_[file][import_name] = long_import_name;
if (parser_.opts.generate_object_based_api) {
flat_file_import_declarations_[file][import_name + "T"] = long_import_name + "T";
}
}
std::string import_statement;
std::string export_statement;
import_statement += "import { ";
@@ -616,18 +698,32 @@ class TsGenerator : public BaseGenerator {
const EnumDef &dependency) {
std::string ns;
const auto &depc_comps = dependency.defined_namespace->components;
for (auto it = depc_comps.begin(); it != depc_comps.end(); it++) ns += *it;
for (auto it = depc_comps.begin(); it != depc_comps.end(); it++) {
ns += *it;
}
std::string unique_name = ns + dependency.name;
std::string import_name = EscapeKeyword(dependency.name);
std::string long_import_name;
if (imports.find(unique_name) != imports.end())
if (imports.find(unique_name) != imports.end()) {
return imports.find(unique_name)->second.name;
}
for (auto it = imports.begin(); it != imports.end(); it++) {
if (it->second.name == import_name) {
long_import_name = ns + import_name;
break;
}
}
if (parser_.opts.ts_flat_file) {
std::string file = dependency.declaration_file == nullptr
? dependency.file
: dependency.declaration_file->substr(2);
file = RelativeToRootPath(StripFileName(AbsolutePath(dependent.file)),
dependency.file).substr(2);
long_import_name = ns + import_name;
flat_file_import_declarations_[file][import_name] = long_import_name;
}
std::string import_statement;
std::string export_statement;
import_statement += "import { ";
@@ -636,7 +732,8 @@ class TsGenerator : public BaseGenerator {
if (long_import_name.empty())
symbols_expression += import_name;
else
symbols_expression += dependency.name + " as " + long_import_name;
symbols_expression += EscapeKeyword(dependency.name) + " as " +
EscapeKeyword(long_import_name);
if (dependency.is_union) {
symbols_expression += ", unionTo" + import_name;
symbols_expression += ", unionListTo" + import_name;
@@ -966,7 +1063,8 @@ class TsGenerator : public BaseGenerator {
switch (field.value.type.base_type) {
case BASE_TYPE_STRUCT: {
const auto &sd = *field.value.type.struct_def;
field_type += GetObjApiClassName(sd, parser.opts);
field_type += GetObjApiClassName(AddImport(imports, struct_def, sd),
parser.opts);
const std::string field_accessor =
"this." + field_name_escaped + "()";
@@ -1008,12 +1106,15 @@ class TsGenerator : public BaseGenerator {
field_offset_decl =
"builder.createStructOffsetList(this." +
field_name_escaped + ", " +
AddImport(imports, struct_def, struct_def) + ".start" +
ConvertCase(field_name, Case::kUpperCamel) + "Vector)";
EscapeKeyword(
AddImport(imports, struct_def, struct_def)) +
".start" + ConvertCase(field_name, Case::kUpperCamel) +
"Vector)";
} else {
field_offset_decl =
AddImport(imports, struct_def, struct_def) + ".create" +
ConvertCase(field_name, Case::kUpperCamel) +
EscapeKeyword(
AddImport(imports, struct_def, struct_def)) +
".create" + ConvertCase(field_name, Case::kUpperCamel) +
"Vector(builder, builder.createObjectOffsetList(" +
"this." + field_name_escaped + "))";
}
@@ -1027,8 +1128,8 @@ class TsGenerator : public BaseGenerator {
field_binded_method + ", this." + field_name +
"Length())";
field_offset_decl =
AddImport(imports, struct_def, struct_def) + ".create" +
ConvertCase(field_name, Case::kUpperCamel) +
EscapeKeyword(AddImport(imports, struct_def, struct_def)) +
".create" + ConvertCase(field_name, Case::kUpperCamel) +
"Vector(builder, builder.createObjectOffsetList(" +
"this." + field_name_escaped + "))";
break;
@@ -1042,8 +1143,8 @@ class TsGenerator : public BaseGenerator {
GenUnionValTS(imports, field_name, vectortype, true);
field_offset_decl =
AddImport(imports, struct_def, struct_def) + ".create" +
ConvertCase(field_name, Case::kUpperCamel) +
EscapeKeyword(AddImport(imports, struct_def, struct_def)) +
".create" + ConvertCase(field_name, Case::kUpperCamel) +
"Vector(builder, builder.createObjectOffsetList(" +
"this." + field_name_escaped + "))";
@@ -1062,8 +1163,8 @@ class TsGenerator : public BaseGenerator {
"Length())";
field_offset_decl =
AddImport(imports, struct_def, struct_def) + ".create" +
ConvertCase(field_name, Case::kUpperCamel) +
EscapeKeyword(AddImport(imports, struct_def, struct_def)) +
".create" + ConvertCase(field_name, Case::kUpperCamel) +
"Vector(builder, this." + field_name_escaped + ")";
break;