Skip to content

Commit

Permalink
[TS] Add support for fixed length arrays on Typescript (#5864) (#7021)
Browse files Browse the repository at this point in the history
* Typescript / Javascript don't have fixed arrays but it is important to support these languages for compatibility.

* Generated TS code checks the length of the given array and do truncating / padding to conform to the schema.

* Supports the both standard API and Object Based API.

* Added a test.

Co-authored-by: Mehmet Baker <mehmet.baker@zerodensity.tv>
Signed-off-by: Bulent Vural <bulent.vural@zerodensity.tv>
  • Loading branch information
bulentv and mehmetb committed Oct 21, 2022
1 parent 44ffb84 commit 37f3058
Show file tree
Hide file tree
Showing 14 changed files with 1,664 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -150,3 +150,4 @@ flatbuffers.pc
**/latex/**
# https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_BASE_DIR
_deps/
tests/ts/arrays_test_complex/arrays_test_complex.bfbs
313 changes: 302 additions & 11 deletions src/idl_gen_ts.cpp

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/idl_parser.cpp
Expand Up @@ -2581,7 +2581,7 @@ bool Parser::SupportsAdvancedArrayFeatures() const {
return (opts.lang_to_generate &
~(IDLOptions::kCpp | IDLOptions::kPython | IDLOptions::kJava |
IDLOptions::kCSharp | IDLOptions::kJsonSchema | IDLOptions::kJson |
IDLOptions::kBinary | IDLOptions::kRust)) == 0;
IDLOptions::kBinary | IDLOptions::kRust | IDLOptions::kTs)) == 0;
}

Namespace *Parser::UniqueNamespace(Namespace *ns) {
Expand Down
129 changes: 129 additions & 0 deletions tests/ts/JavaScriptComplexArraysTest.js
@@ -0,0 +1,129 @@
/* global BigInt */

import assert from 'assert';
import { readFileSync, writeFileSync } from 'fs';
import * as flatbuffers from 'flatbuffers';
import {
ArrayStructT,
ArrayTable,
ArrayTableT,
InnerStructT,
NestedStructT,
OuterStructT,
TestEnum,
} from './arrays_test_complex/arrays_test_complex_generated.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
BigInt.prototype.toJSON = function () {
return this.toString();
};
function fbObjToObj(fbObj) {
const ret = {};
for (const propName of Object.keys(fbObj)) {
const key = propName;
const prop = fbObj[key];
if (prop.valueOf) {
ret[key] = prop.valueOf();
} else if (typeof prop === 'object') {
ret[key] = fbObjToObj(prop);
}
}
return ret;
}
function testBuild(monFile, jsFile) {
const arrayTable = new ArrayTableT(
'Complex Array Test',
new ArrayStructT(
221.139008,
[-700, -600, -500, -400, -300, -200, -100, 0, 100, 200, 300, 400, 500, 600, 700],
13,
[
new NestedStructT(
[233, -123],
TestEnum.B,
[TestEnum.A, TestEnum.C],
[
new OuterStructT(
false,
123.456,
new InnerStructT(
123456792.0,
[13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
91,
BigInt('9007199254740999')
),
[
new InnerStructT(
-987654321.9876,
[255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243],
123,
BigInt('9007199254741000')
),
new InnerStructT(
123000987.9876,
[101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113],
-123,
BigInt('9007199254741000')
),
],
new InnerStructT(
987654321.9876,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
19,
BigInt('9007199254741000')
),
[111000111.222, 222000222.111, 333000333.333, 444000444.444]
),
]
),
],
-123456789
)
);
const builder = new flatbuffers.Builder();
builder.finish(arrayTable.pack(builder));
if (jsFile) {
const obj = fbObjToObj(arrayTable);
writeFileSync(jsFile, `export default ${JSON.stringify(obj, null, 2)}`);
}
if (monFile) {
writeFileSync(monFile, builder.asUint8Array());
}
return builder.asUint8Array();
}
function testParse(monFile, jsFile, buffer) {
if (!buffer) {
if (!monFile) {
console.log(`Please specify mon file read the buffer from.`);
process.exit(1);
}
buffer = readFileSync(monFile);
}
const byteBuffer = new flatbuffers.ByteBuffer(new Uint8Array(buffer));
const arrayTable = ArrayTable.getRootAsArrayTable(byteBuffer).unpack();
const json = JSON.stringify(arrayTable, null, 2);
if (jsFile) {
writeFileSync(jsFile, `export default ${json}`);
}
return arrayTable;
}
if (process.argv[2] === 'build') {
testBuild(process.argv[3], process.argv[4]);
} else if (process.argv[2] === 'parse') {
testParse(process.argv[3], process.argv[4], null);
} else {
const arr = testBuild(null, null);
const parsed = testParse(null, null, Buffer.from(arr));
assert.strictEqual(parsed.a, 'Complex Array Test', 'String Test');
assert.strictEqual(parsed?.cUnderscore?.aUnderscore, 221.13900756835938, 'Float Test');
assert.deepEqual(parsed?.cUnderscore?.bUnderscore, [-700, -600, -500, -400, -300, -200, -100, 0, 100, 200, 300, 400, 500, 600, 700], 'Array of signed integers');
assert.strictEqual(parsed?.cUnderscore.d?.[0].dOuter[0].d[1].a, 123000987.9876, 'Float in deep');
assert.deepEqual(parsed?.cUnderscore?.d[0].dOuter?.[0]?.e, {
a: 987654321.9876,
b: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
c: 19,
dUnderscore: '9007199254741000',
}, 'Object in deep');
assert.deepEqual(parsed?.cUnderscore.g, ['0', '0'], 'Last object');

console.log('Arrays test: completed successfully');
}
15 changes: 14 additions & 1 deletion tests/ts/TypeScriptTest.py
Expand Up @@ -95,6 +95,18 @@ def flatc(options, schema, prefix=None, include=None, data=None, cwd=tests_path)
include="../../",
)

flatc(
options=["-b", "--schema"],
schema="arrays_test_complex/arrays_test_complex.fbs",
prefix="arrays_test_complex"
)

flatc(
options=["--ts", "--reflect-names", "--ts-flat-files", "--gen-name-strings", "--gen-object-api"],
schema="arrays_test_complex/arrays_test_complex.bfbs",
prefix="arrays_test_complex"
)

flatc(
options=[
"--ts",
Expand All @@ -121,4 +133,5 @@ def flatc(options, schema, prefix=None, include=None, data=None, cwd=tests_path)
print("Running TypeScript Tests...")
check_call(NODE_CMD + ["JavaScriptTest"])
check_call(NODE_CMD + ["JavaScriptUnionVectorTest"])
check_call(NODE_CMD + ["JavaScriptFlexBuffersTest"])
check_call(NODE_CMD + ["JavaScriptFlexBuffersTest"])
check_call(NODE_CMD + ["JavaScriptComplexArraysTest"])
52 changes: 52 additions & 0 deletions tests/ts/arrays_test_complex/arrays_test_complex.fbs
@@ -0,0 +1,52 @@
namespace MyGame.Example;

// it appears that the library has already a problem with Enums
// when generating ts file with '--ts-flat-files' from a fbs.
// bfbs is fine.
// workaround is to generate bfbs from fbs first, and then
// generate flat .ts from bfbs if you have enum(s) in your chema

enum TestEnum : byte { A, B, C }

struct InnerStruct {
a:float64;
b:[ubyte:13];
c:int8;
d_underscore:int64;
}

struct OuterStruct {
a:bool;
b:double;
c_underscore:InnerStruct;
d:[InnerStruct:3];
e:InnerStruct;
f:[float64:4];
}

struct NestedStruct{
a:[int:2];
b:TestEnum;
c_underscore:[TestEnum:2];
d_outer:[OuterStruct:5];
e:[int64:2];
}

struct ArrayStruct{
a_underscore:float;
b_underscore:[int:0xF];
c:byte;
d:[NestedStruct:2];
e:int32;
f:[OuterStruct:2];
g:[int64:2];
}

table ArrayTable{
a:string;
c_underscore:ArrayStruct;
}

root_type ArrayTable;
file_identifier "RHUB";
file_extension "mon";

0 comments on commit 37f3058

Please sign in to comment.