From 68c3d2d2afd7f00dcbe1ae64bc9dd4723dbff91a Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Mon, 15 Apr 2024 10:53:28 +0200 Subject: [PATCH 1/2] init refactor to better typescript support --- src/Layout.ts | 270 ++++++++++++++++++++++++++------------------------ 1 file changed, 138 insertions(+), 132 deletions(-) diff --git a/src/Layout.ts b/src/Layout.ts index 6490716..f3e9a9d 100644 --- a/src/Layout.ts +++ b/src/Layout.ts @@ -134,13 +134,6 @@ import { Buffer } from 'buffer'; -/* Convenience type alias for objects. - * - * @ignore */ -export interface LayoutObject { - [key: string]: any; -} - /* Check if a value is a Uint8Array. * * @ignore */ @@ -174,12 +167,12 @@ export function uint8ArrayToBuffer(b: Uint8Array): Buffer { * * @abstract */ -export abstract class Layout { +export abstract class Layout { span: number; - property?: string; + property?: P; boundConstructor_?: any; - constructor(span: number, property?: string) { + constructor(span: number, property?: P) { if (!Number.isInteger(span)) { throw new TypeError('span must be an integer'); } @@ -221,8 +214,8 @@ export abstract class Layout { * * See {@link bindConstructorLayout}. */ - makeDestinationObject(): LayoutObject { - return {}; + makeDestinationObject(): T { + return {} as T; // it should be implemented properly in the derived class } /** @@ -302,7 +295,7 @@ export abstract class Layout { * @returns {Layout} - the copy with {@link Layout#property|property} * set to `property`. */ - replicate(property: string): this { + replicate(property: P): this { const rv = Object.create(this.constructor.prototype) as this; Object.assign(rv, this); rv.property = property; @@ -329,7 +322,7 @@ export abstract class Layout { * * @return {(Object|undefined)} */ - fromArray(values: any[]): LayoutObject | undefined { + fromArray(values: any[]): T | undefined { return undefined; } } @@ -426,7 +419,7 @@ export function bindConstructorLayout(Class: any, layout: Layout): void { * @abstract * @augments {Layout} */ -export abstract class ExternalLayout extends Layout { +export abstract class ExternalLayout

extends Layout { /** * Return `true` iff the external layout decodes to an unsigned * integer layout. @@ -458,10 +451,10 @@ export abstract class ExternalLayout extends Layout { * * @augments {ExternalLayout} */ -export class GreedyCount extends ExternalLayout { +export class GreedyCount

extends ExternalLayout

{ elementSpan: number; - constructor(elementSpan = 1, property?: string) { + constructor(elementSpan = 1, property?: P) { if ((!Number.isInteger(elementSpan)) || (0 >= elementSpan)) { throw new TypeError('elementSpan must be a (positive) integer'); } @@ -511,10 +504,10 @@ export class GreedyCount extends ExternalLayout { * * @augments {Layout} */ -export class OffsetLayout extends ExternalLayout { - layout: Layout; +export class OffsetLayout

extends ExternalLayout

{ + layout: Layout; offset: number; - constructor(layout: Layout, offset = 0, property?: string) { + constructor(layout: Layout, offset = 0, property?: P) { if (!(layout instanceof Layout)) { throw new TypeError('layout must be a Layout'); } @@ -570,8 +563,8 @@ export class OffsetLayout extends ExternalLayout { * * @augments {Layout} */ -export class UInt extends Layout { - constructor(span: number, property?: string) { +export class UInt

extends Layout { + constructor(span: number, property?: P) { super(span, property); if (6 < this.span) { throw new RangeError('span must not exceed 6 bytes'); @@ -606,8 +599,8 @@ export class UInt extends Layout { * * @augments {Layout} */ -export class UIntBE extends Layout { - constructor(span: number, property?: string) { +export class UIntBE

extends Layout { + constructor(span: number, property?: P) { super(span, property); if (6 < this.span) { throw new RangeError('span must not exceed 6 bytes'); @@ -642,8 +635,8 @@ export class UIntBE extends Layout { * * @augments {Layout} */ -export class Int extends Layout { - constructor(span: number, property?: string) { +export class Int

extends Layout { + constructor(span: number, property?: P) { super(span, property); if (6 < this.span) { throw new RangeError('span must not exceed 6 bytes'); @@ -678,8 +671,8 @@ export class Int extends Layout { * * @augments {Layout} */ -export class IntBE extends Layout { - constructor(span: number, property?: string) { +export class IntBE

extends Layout { + constructor(span: number, property?: P) { super(span, property); if (6 < this.span) { throw new RangeError('span must not exceed 6 bytes'); @@ -723,8 +716,8 @@ function roundedInt64(hi32: number, lo32: number): number { * * @augments {Layout} */ -export class NearUInt64 extends Layout { - constructor(property?: string) { +export class NearUInt64

extends Layout { + constructor(property?: P) { super(8, property); } @@ -757,8 +750,8 @@ export class NearUInt64 extends Layout { * * @augments {Layout} */ -export class NearUInt64BE extends Layout { - constructor(property?: string) { +export class NearUInt64BE

extends Layout { + constructor(property?: P) { super(8, property); } @@ -791,8 +784,8 @@ export class NearUInt64BE extends Layout { * * @augments {Layout} */ -export class NearInt64 extends Layout { - constructor(property?: string) { +export class NearInt64

extends Layout { + constructor(property?: P) { super(8, property); } @@ -825,8 +818,8 @@ export class NearInt64 extends Layout { * * @augments {Layout} */ -export class NearInt64BE extends Layout { - constructor(property?: string) { +export class NearInt64BE

extends Layout { + constructor(property?: P) { super(8, property); } @@ -858,8 +851,8 @@ export class NearInt64BE extends Layout { * * @augments {Layout} */ -export class Float extends Layout { - constructor(property?: string) { +export class Float

extends Layout { + constructor(property?: P) { super(4, property); } @@ -885,8 +878,8 @@ export class Float extends Layout { * * @augments {Layout} */ -export class FloatBE extends Layout { - constructor(property?: string) { +export class FloatBE

extends Layout { + constructor(property?: P) { super(4, property); } @@ -912,8 +905,8 @@ export class FloatBE extends Layout { * * @augments {Layout} */ -export class Double extends Layout { - constructor(property?: string) { +export class Double

extends Layout { + constructor(property?: P) { super(8, property); } @@ -939,8 +932,8 @@ export class Double extends Layout { * * @augments {Layout} */ -export class DoubleBE extends Layout { - constructor(property?: string) { +export class DoubleBE

extends Layout { + constructor(property?: P) { super(8, property); } @@ -973,11 +966,11 @@ export class DoubleBE extends Layout { * * @augments {Layout} */ -export class Sequence extends Layout { - elementLayout: Layout; - count: number | ExternalLayout; +export class Sequence extends Layout { + elementLayout: Layout; + count: number | ExternalLayout; - constructor(elementLayout: Layout, count: number | ExternalLayout, property?: string) { + constructor(elementLayout: Layout, count: number | ExternalLayout, property?: P) { if (!(elementLayout instanceof Layout)) { throw new TypeError('elementLayout must be a Layout'); } @@ -1065,6 +1058,16 @@ export class Sequence extends Layout { } } +type StructObj = T extends Layout[] + ? { + [K in Exclude, ''>]: Extract< + T[number], + Layout + > extends Layout + ? V + : any; + } + : any /** * Represent a contiguous sequence of arbitrary layout elements as an * Object. @@ -1097,13 +1100,14 @@ export class Sequence extends Layout { * * @augments {Layout} */ -export class Structure extends Layout { - fields: Layout[]; +export class Structure[], P extends string = ''> extends Layout> { + fields: T; decodePrefixes: boolean; - constructor(fields: Layout[], property?: string, decodePrefixes?: boolean) { + constructor(fields: T, property?: P, decodePrefixes?: boolean) { if (!(Array.isArray(fields) - && fields.reduce((acc, v) => acc && (v instanceof Layout), true))) { + && fields + .reduce((acc: boolean, v: Layout | any) => acc && (v instanceof Layout), true))) { throw new TypeError('fields must be array of Layout instances'); } if (('boolean' === typeof property) @@ -1171,12 +1175,12 @@ export class Structure extends Layout { } /** @override */ - decode(b: Uint8Array, offset = 0): T { + decode(b: Uint8Array, offset = 0): StructObj { checkUint8Array(b); - const dest = this.makeDestinationObject() as T; + const dest = this.makeDestinationObject() as StructObj; for (const fd of this.fields) { if (undefined !== fd.property) { - dest[fd.property as keyof T] = fd.decode(b, offset); + dest[fd.property as keyof StructObj] = fd.decode(b, offset); } offset += fd.getSpan(b, offset); if (this.decodePrefixes @@ -1192,7 +1196,7 @@ export class Structure extends Layout { * If `src` is missing a property for a member with a defined {@link * Layout#property|property} the corresponding region of the buffer is * left unmodified. */ - encode(src: T, b: Uint8Array, offset = 0): number { + encode(src: StructObj, b: Uint8Array, offset = 0): number { const firstOffset = offset; let lastOffset = 0; let lastWrote = 0; @@ -1200,7 +1204,7 @@ export class Structure extends Layout { let span = fd.span; lastWrote = (0 < span) ? span : 0; if (undefined !== fd.property) { - const fv = src[fd.property as keyof T]; + const fv = src[fd.property as keyof StructObj]; if (undefined !== fv) { lastWrote = fd.encode(fv, b, offset); if (0 > span) { @@ -1221,12 +1225,12 @@ export class Structure extends Layout { } /** @override */ - fromArray(values: any[]): LayoutObject { + fromArray(values: any[]): StructObj { const dest = this.makeDestinationObject(); for (const fd of this.fields) { if ((undefined !== fd.property) && (0 < values.length)) { - dest[fd.property] = values.shift(); + dest[fd.property as keyof StructObj] = values.shift(); } } return dest; @@ -1240,7 +1244,7 @@ export class Structure extends Layout { * @return {Layout} - the layout associated with `property`, or * undefined if there is no such property. */ - layoutFor(property: string): Layout | undefined { + layoutFor(property: string): Layout> | undefined { if ('string' !== typeof property) { throw new TypeError('property must be string'); } @@ -1296,9 +1300,9 @@ export class Structure extends Layout { * * @abstract */ -export class UnionDiscriminator { - property: string; - constructor(property: string) { +export class UnionDiscriminator { + property: P; + constructor(property: P) { /** The {@link Layout#property|property} to be used when the * discriminator is referenced in isolation (generally when {@link * Union#decode|Union decode} cannot delegate to a specific @@ -1340,9 +1344,9 @@ export class UnionDiscriminator { * * @augments {UnionDiscriminator} */ -export class UnionLayoutDiscriminator extends UnionDiscriminator { - layout: ExternalLayout; - constructor(layout: ExternalLayout, property?: string) { +export class UnionLayoutDiscriminator

extends UnionDiscriminator { + layout: ExternalLayout; + constructor(layout: ExternalLayout, property?: P) { if (!((layout instanceof ExternalLayout) && layout.isCount())) { throw new TypeError('layout must be an unsigned integer ExternalLayout'); @@ -1425,10 +1429,10 @@ export class UnionLayoutDiscriminator extends UnionDiscriminator { * * @augments {Layout} */ -export class Union extends Layout { +export class Union

extends Layout { // `property` is assigned in the Layout constructor // @ts-ignore - property: string; + property: P; discriminator: UnionDiscriminator; usesPrefixDiscriminator: boolean; defaultLayout: Layout | null; @@ -1438,9 +1442,9 @@ export class Union extends Layout { configGetSourceVariant: (getSourceVariant: (src: LayoutObject) => VariantLayout | undefined) => void; constructor( - discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, + discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, defaultLayout?: Layout | null, - property?: string + property?: P ) { let discriminator: UnionDiscriminator; if ((discr instanceof UInt) @@ -1480,7 +1484,7 @@ export class Union extends Layout { span = defaultLayout.span; if ((0 <= span) && ((discr instanceof UInt) || (discr instanceof UIntBE))) { - span += (discriminator as UnionLayoutDiscriminator).layout.span; + span += (discriminator as UnionLayoutDiscriminator).layout.span; } } super(span, property); @@ -1927,15 +1931,15 @@ function fixBitwiseResult(v: number): number { * * @augments {Layout} */ -export class BitStructure extends Layout { +export class BitStructure

extends Layout { fields: BitField[]; - word: UInt | UIntBE; + word: UInt | UIntBE; msb: boolean; _packedSetValue: (v: number) => this; _packedGetValue: () => number; - constructor(word: UInt | UIntBE, msb: boolean | string, property?: string) { + constructor(word: UInt | UIntBE, msb: boolean | P, property?: P) { if (!((word instanceof UInt) || (word instanceof UIntBE))) { throw new TypeError('word must be a UInt or UIntBE layout'); @@ -2026,7 +2030,7 @@ export class BitStructure extends Layout { * Layout#property|property}. * * @return {BitField} */ - addField(bits: number, property: string): BitField { + addField(bits: number, property: Prop): BitField { const bf = new BitField(this, bits, property); this.fields.push(bf); return bf; @@ -2041,7 +2045,7 @@ export class BitStructure extends Layout { * @return {Boolean} */ // `Boolean` conflicts with the native primitive type // eslint-disable-next-line @typescript-eslint/ban-types - addBoolean(property: string): Boolean { + addBoolean(property: Prop): Boolean { // This is my Boolean, not the Javascript one. const bf = new Boolean(this, property); this.fields.push(bf); @@ -2089,14 +2093,14 @@ export class BitStructure extends Layout { * @param {string} [property] - initializer for {@link * Layout#property|property}. */ -export class BitField { - container: BitStructure; +export class BitField

{ + container: BitStructure; bits: number; valueMask: number; start: number; wordMask: number; - property: string; - constructor(container: BitStructure, bits: number, property: string) { + property: P; + constructor(container: BitStructure, bits: number, property: P) { if (!(container instanceof BitStructure)) { throw new TypeError('container must be a BitStructure'); } @@ -2198,8 +2202,8 @@ export class BitField { * @augments {BitField} */ /* eslint-disable no-extend-native */ -export class Boolean extends BitField { - constructor(container: BitStructure, property: string) { +export class Boolean

extends BitField

{ + constructor(container: BitStructure

, property: P) { super(container, 1, property); } @@ -2235,9 +2239,9 @@ export class Boolean extends BitField { * * @augments {Layout} */ -export class Blob extends Layout { - length: number | ExternalLayout; - constructor(length: number | ExternalLayout, property?: string) { +export class Blob

extends Layout { + length: number | ExternalLayout; + constructor(length: number | ExternalLayout, property?: P) { if (!(((length instanceof ExternalLayout) && length.isCount()) || (Number.isInteger(length) && (0 <= length)))) { throw new TypeError('length must be positive integer ' @@ -2262,7 +2266,7 @@ export class Blob extends Layout { getSpan(b: Uint8Array, offset?: number): number { let span = this.span; if (0 > span) { - span = (this.length as ExternalLayout).decode(b, offset); + span = (this.length as ExternalLayout).decode(b, offset); } return span; } @@ -2271,7 +2275,7 @@ export class Blob extends Layout { decode(b: Uint8Array, offset = 0): Uint8Array { let span = this.span; if (0 > span) { - span = (this.length as ExternalLayout).decode(b, offset); + span = (this.length as ExternalLayout).decode(b, offset); } return uint8ArrayToBuffer(b).slice(offset, offset + span); } @@ -2315,8 +2319,8 @@ export class Blob extends Layout { * * @augments {Layout} */ -export class CString extends Layout { - constructor(property?: string) { +export class CString

extends Layout { + constructor(property?: P) { super(-1, property); } @@ -2375,9 +2379,9 @@ export class CString extends Layout { * * @augments {Layout} */ -export class UTF8 extends Layout { +export class UTF8

extends Layout { maxSpan: number; - constructor(maxSpan?: number | string, property?: string) { + constructor(maxSpan?: number | P, property?: P) { if (('string' === typeof maxSpan) && (undefined === property)) { property = maxSpan; maxSpan = undefined; @@ -2418,7 +2422,7 @@ export class UTF8 extends Layout { } /** @override */ - encode(src: string | LayoutObject, b: Uint8Array, offset = 0): number { + encode(src: string , b: Uint8Array, offset = 0): number { /* Must force this to a string, lest it be a number and the * "utf8-encoding" below actually allocate a buffer of length * src */ @@ -2458,9 +2462,9 @@ export class UTF8 extends Layout { * * @augments {Layout} */ -export class Constant extends Layout { +export class Constant extends Layout { value: T; - constructor(value: T, property?: string) { + constructor(value: T, property?: P) { super(0, property); /** The value produced by this constant when the layout is {@link @@ -2488,134 +2492,136 @@ export class Constant extends Layout { } /** Factory for {@link GreedyCount}. */ -export const greedy = ((elementSpan: number, property?: string): GreedyCount => new GreedyCount(elementSpan, property)); +export const greedy =

(elementSpan: number, property?: P): GreedyCount

=> + new GreedyCount(elementSpan, property); /** Factory for {@link OffsetLayout}. */ -export const offset = ((layout: Layout, offset?: number, property?: string): OffsetLayout => - new OffsetLayout(layout, offset, property)); +export const offset =

(layout: Layout, offset?: number, property?: P): OffsetLayout

=> + new OffsetLayout

(layout, offset, property); /** Factory for {@link UInt|unsigned int layouts} spanning one * byte. */ -export const u8 = ((property?: string): UInt => new UInt(1, property)); +export const u8 =

(property?: P): UInt

=> new UInt(1, property); /** Factory for {@link UInt|little-endian unsigned int layouts} * spanning two bytes. */ -export const u16 = ((property?: string): UInt => new UInt(2, property)); +export const u16 =

(property?: P): UInt

=> new UInt(2, property); /** Factory for {@link UInt|little-endian unsigned int layouts} * spanning three bytes. */ -export const u24 = ((property?: string): UInt => new UInt(3, property)); +export const u24 =

(property?: P): UInt

=> new UInt(3, property); /** Factory for {@link UInt|little-endian unsigned int layouts} * spanning four bytes. */ -export const u32 = ((property?: string): UInt => new UInt(4, property)); +export const u32 =

(property?: P): UInt

=> new UInt(4, property); /** Factory for {@link UInt|little-endian unsigned int layouts} * spanning five bytes. */ -export const u40 = ((property?: string): UInt => new UInt(5, property)); +export const u40 =

(property?: P): UInt

=> new UInt(5, property); /** Factory for {@link UInt|little-endian unsigned int layouts} * spanning six bytes. */ -export const u48 = ((property?: string): UInt => new UInt(6, property)); +export const u48 =

(property?: P): UInt

=> new UInt(6, property); /** Factory for {@link NearUInt64|little-endian unsigned int * layouts} interpreted as Numbers. */ -export const nu64 = ((property?: string): NearUInt64 => new NearUInt64(property)); +export const nu64 =

(property?: P): NearUInt64

=> new NearUInt64(property); /** Factory for {@link UInt|big-endian unsigned int layouts} * spanning two bytes. */ -export const u16be = ((property?: string): UIntBE => new UIntBE(2, property)); +export const u16be =

(property?: P): UIntBE

=> new UIntBE(2, property); /** Factory for {@link UInt|big-endian unsigned int layouts} * spanning three bytes. */ -export const u24be = ((property?: string): UIntBE => new UIntBE(3, property)); +export const u24be =

(property?: P): UIntBE

=> new UIntBE(3, property); /** Factory for {@link UInt|big-endian unsigned int layouts} * spanning four bytes. */ -export const u32be = ((property?: string): UIntBE => new UIntBE(4, property)); +export const u32be =

(property?: P): UIntBE

=> new UIntBE(4, property); /** Factory for {@link UInt|big-endian unsigned int layouts} * spanning five bytes. */ -export const u40be = ((property?: string): UIntBE => new UIntBE(5, property)); +export const u40be =

(property?: P): UIntBE

=> new UIntBE(5, property); /** Factory for {@link UInt|big-endian unsigned int layouts} * spanning six bytes. */ -export const u48be = ((property?: string): UIntBE => new UIntBE(6, property)); +export const u48be =

(property?: P): UIntBE

=> new UIntBE(6, property); /** Factory for {@link NearUInt64BE|big-endian unsigned int * layouts} interpreted as Numbers. */ -export const nu64be = ((property?: string): NearUInt64BE => new NearUInt64BE(property)); +export const nu64be =

(property?: P): NearUInt64BE

=> new NearUInt64BE(property); /** Factory for {@link Int|signed int layouts} spanning one * byte. */ -export const s8 = ((property?: string): Int => new Int(1, property)); +export const s8 =

(property?: P): Int

=> new Int(1, property); /** Factory for {@link Int|little-endian signed int layouts} * spanning two bytes. */ -export const s16 = ((property?: string): Int => new Int(2, property)); +export const s16 =

(property?: P): Int

=> new Int(2, property); /** Factory for {@link Int|little-endian signed int layouts} * spanning three bytes. */ -export const s24 = ((property?: string): Int => new Int(3, property)); +export const s24 =

(property?: P): Int

=> new Int(3, property); /** Factory for {@link Int|little-endian signed int layouts} * spanning four bytes. */ -export const s32 = ((property?: string): Int => new Int(4, property)); +export const s32 =

(property?: P): Int

=> new Int(4, property); /** Factory for {@link Int|little-endian signed int layouts} * spanning five bytes. */ -export const s40 = ((property?: string): Int => new Int(5, property)); +export const s40 =

(property?: P): Int

=> new Int(5, property); /** Factory for {@link Int|little-endian signed int layouts} * spanning six bytes. */ -export const s48 = ((property?: string): Int => new Int(6, property)); +export const s48 =

(property?: P): Int

=> new Int(6, property); /** Factory for {@link NearInt64|little-endian signed int layouts} * interpreted as Numbers. */ -export const ns64 = ((property?: string): NearInt64 => new NearInt64(property)); +export const ns64 =

(property?: P): NearInt64

=> new NearInt64(property); /** Factory for {@link Int|big-endian signed int layouts} * spanning two bytes. */ -export const s16be = ((property?: string): IntBE => new IntBE(2, property)); +export const s16be =

(property?: P): IntBE

=> new IntBE(2, property); /** Factory for {@link Int|big-endian signed int layouts} * spanning three bytes. */ -export const s24be = ((property?: string): IntBE => new IntBE(3, property)); +export const s24be =

(property?: P): IntBE

=> new IntBE(3, property); /** Factory for {@link Int|big-endian signed int layouts} * spanning four bytes. */ -export const s32be = ((property?: string): IntBE => new IntBE(4, property)); +export const s32be =

(property?: P): IntBE

=> new IntBE(4, property); /** Factory for {@link Int|big-endian signed int layouts} * spanning five bytes. */ -export const s40be = ((property?: string): IntBE => new IntBE(5, property)); +export const s40be =

(property?: P): IntBE

=> new IntBE(5, property); /** Factory for {@link Int|big-endian signed int layouts} * spanning six bytes. */ -export const s48be = ((property?: string): IntBE => new IntBE(6, property)); +export const s48be =

(property?: P): IntBE

=> new IntBE(6, property); /** Factory for {@link NearInt64BE|big-endian signed int layouts} * interpreted as Numbers. */ -export const ns64be = ((property?: string): NearInt64BE => new NearInt64BE(property)); +export const ns64be =

(property?: P): NearInt64BE

=> new NearInt64BE(property); /** Factory for {@link Float|little-endian 32-bit floating point} values. */ -export const f32 = ((property?: string): Float => new Float(property)); +export const f32 =

(property?: P): Float

=> new Float(property); /** Factory for {@link FloatBE|big-endian 32-bit floating point} values. */ -export const f32be = ((property?: string): FloatBE => new FloatBE(property)); +export const f32be =

(property?: P): FloatBE

=> new FloatBE(property); /** Factory for {@link Double|little-endian 64-bit floating point} values. */ -export const f64 = ((property?: string): Double => new Double(property)); +export const f64 =

(property?: P): Double

=> new Double(property); /** Factory for {@link DoubleBE|big-endian 64-bit floating point} values. */ -export const f64be = ((property?: string): DoubleBE => new DoubleBE(property)); +export const f64be =

(property?: P): DoubleBE

=> new DoubleBE(property); /** Factory for {@link Structure} values. */ -export const struct = ((fields: Layout[], property?: string, decodePrefixes?: boolean): Structure => - new Structure(fields, property, decodePrefixes)); +export const struct = ([], Property extends string> + (fields: T, property?: Property, decodePrefixes?: boolean): Structure => + new Structure(fields, property, decodePrefixes)); /** Factory for {@link BitStructure} values. */ -export const bits = ((word: UInt | UIntBE, msb: boolean | string, property?: string): BitStructure => +export const bits = (

(word: UInt

| UIntBE

, msb: boolean | string, property?: P): BitStructure

=> new BitStructure(word, msb, property)); /** Factory for {@link Sequence} values. */ @@ -2623,7 +2629,7 @@ export const seq = ((elementLayout: Layout, count: number | ExternalLayout new Sequence(elementLayout, count, property)); /** Factory for {@link Union} values. */ -export const union = ((discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, +export const union = (

(discr: UInt

| UIntBE

| ExternalLayout

| UnionDiscriminator, defaultLayout?: Layout | null, property?: string): Union => new Union(discr, defaultLayout, property)); @@ -2635,7 +2641,7 @@ export const unionLayoutDiscriminator = ((layout: ExternalLayout, property?: str export const blob = ((length: number | ExternalLayout, property?: string): Blob => new Blob(length, property)); /** Factory for {@link CString} values. */ -export const cstr = ((property?: string): CString => new CString(property)); +export const cstr =

(property?: P): CString

=> new CString(property); /** Factory for {@link UTF8} values. */ export const utf8 = ((maxSpan: number, property?: string): UTF8 => new UTF8(maxSpan, property)); From 1a0050ba3f2208608a26967ab1a425c42402e374 Mon Sep 17 00:00:00 2001 From: Andrea Baccega Date: Tue, 16 Apr 2024 14:21:14 +0200 Subject: [PATCH 2/2] finish refactor for TypeScript --- src/Layout.ts | 166 +++++++++++++++++++++++++++----------------------- 1 file changed, 91 insertions(+), 75 deletions(-) diff --git a/src/Layout.ts b/src/Layout.ts index f3e9a9d..1afc2c3 100644 --- a/src/Layout.ts +++ b/src/Layout.ts @@ -295,8 +295,8 @@ export abstract class Layout { * @returns {Layout} - the copy with {@link Layout#property|property} * set to `property`. */ - replicate(property: P): this { - const rv = Object.create(this.constructor.prototype) as this; + replicate(property: NEWP): Layout { + const rv = Object.create(this.constructor.prototype) as Layout; Object.assign(rv, this); rv.property = property; return rv; @@ -1429,24 +1429,22 @@ export class UnionLayoutDiscriminator

extends UnionDiscriminat * * @augments {Layout} */ -export class Union

extends Layout { - // `property` is assigned in the Layout constructor - // @ts-ignore - property: P; - discriminator: UnionDiscriminator; +export class Union

extends Layout { + property!: P; + discriminator: UnionDiscriminator; usesPrefixDiscriminator: boolean; - defaultLayout: Layout | null; - registry: {[key: number]: VariantLayout}; + defaultLayout: Layout | Layout | null; + registry: {[key: number]: VariantLayout}; - getSourceVariant: (src: LayoutObject) => VariantLayout | undefined; - configGetSourceVariant: (getSourceVariant: (src: LayoutObject) => VariantLayout | undefined) => void; + getSourceVariant: (src: any) => VariantLayout | undefined; + configGetSourceVariant: (getSourceVariant: (src: any) => VariantLayout | undefined) => void; constructor( - discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, - defaultLayout?: Layout | null, + discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, + defaultLayout?: Layout | Layout | null, property?: P ) { - let discriminator: UnionDiscriminator; + let discriminator: UnionDiscriminator; if ((discr instanceof UInt) || (discr instanceof UIntBE)) { discriminator = new UnionLayoutDiscriminator(new OffsetLayout(discr)); @@ -1610,7 +1608,7 @@ export class Union

extends Layout { * @throws {Error} - if `src` cannot be associated with a default or * registered variant. */ - defaultGetSourceVariant(src: LayoutObject): VariantLayout | undefined { + defaultGetSourceVariant(src: any): VariantLayout | undefined { if (Object.prototype.hasOwnProperty.call(src, this.discriminator.property)) { if (this.defaultLayout && this.defaultLayout.property && Object.prototype.hasOwnProperty.call(src, this.defaultLayout.property)) { @@ -1639,8 +1637,8 @@ export class Union

extends Layout { * value is an instance of that variant, with no explicit * discriminator. Otherwise the {@link Union#defaultLayout|default * layout} is used to decode the content. */ - decode(b: Uint8Array, offset = 0): LayoutObject { - let dest: LayoutObject; + decode(b: Uint8Array, offset = 0): any { + let dest: any; const dlo = this.discriminator; const discr = dlo.decode(b, offset); const clo = this.registry[discr]; @@ -1648,7 +1646,7 @@ export class Union

extends Layout { const defaultLayout = this.defaultLayout; let contentOffset = 0; if (this.usesPrefixDiscriminator) { - contentOffset = (dlo as UnionLayoutDiscriminator).layout.span; + contentOffset = (dlo as UnionLayoutDiscriminator).layout.span; } dest = this.makeDestinationObject(); dest[dlo.property] = discr; @@ -1667,7 +1665,7 @@ export class Union

extends Layout { * {@link Union#defaultLayout|default layout}. To encode variants * use the appropriate variant-specific {@link VariantLayout#encode} * method. */ - encode(src: LayoutObject, b: Uint8Array, offset = 0): number { + encode(src: any, b: Uint8Array, offset = 0): number { const vlo = this.getSourceVariant(src); if (undefined === vlo) { const dlo = this.discriminator; @@ -1676,7 +1674,7 @@ export class Union

extends Layout { const clo = this.defaultLayout!; let contentOffset = 0; if (this.usesPrefixDiscriminator) { - contentOffset = (dlo as UnionLayoutDiscriminator).layout.span; + contentOffset = (dlo as UnionLayoutDiscriminator).layout.span; } dlo.encode(src[dlo.property], b, offset); // clo.property is not undefined when vlo is undefined @@ -1699,8 +1697,8 @@ export class Union

extends Layout { * Layout#property|property}. * * @return {VariantLayout} */ - addVariant(variant: number, layout: Layout, property: string): VariantLayout { - const rv = new VariantLayout(this, variant, layout, property); + addVariant>(variant: number, layout: Layout, property: P): VariantLayout { + const rv = new VariantLayout(this, variant, layout, property); this.registry[variant] = rv; return rv; } @@ -1719,7 +1717,7 @@ export class Union

extends Layout { * * @return {({VariantLayout}|undefined)} */ - getVariant(vb: Uint8Array | number, offset = 0): VariantLayout | undefined { + getVariant(vb: Uint8Array | number, offset = 0): VariantLayout | undefined { let variant: number; if (vb instanceof Uint8Array) { variant = this.discriminator.decode(vb, offset); @@ -1759,14 +1757,12 @@ export class Union

extends Layout { * * @augments {Layout} */ -export class VariantLayout extends Layout { - // `property` is assigned in the Layout constructor - // @ts-ignore - property: string; - union: Union; +export class VariantLayout, P extends string> extends Layout { + // property!: P; + union: Union

; variant: number; - layout: Layout | null; - constructor(union: Union, variant: number, layout: Layout | null, property: string) { + layout: Layout | null; + constructor(union: Union

, variant: number, layout: Layout | null, property: P) { if (!(union instanceof Union)) { throw new TypeError('union must be a Union'); } @@ -1795,7 +1791,7 @@ export class VariantLayout extends Layout { if (0 > union.span) { span = layout ? layout.span : 0; if ((0 <= span) && union.usesPrefixDiscriminator) { - span += (union.discriminator as UnionLayoutDiscriminator).layout.span; + span += (union.discriminator as UnionLayoutDiscriminator).layout.span; } } super(span, property); @@ -1824,7 +1820,7 @@ export class VariantLayout extends Layout { } let contentOffset = 0; if (this.union.usesPrefixDiscriminator) { - contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; + contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; } /* Span is defined solely by the variant (and prefix discriminator) */ let span = 0; @@ -1834,40 +1830,39 @@ export class VariantLayout extends Layout { return contentOffset + span; } - /** @override */ - decode(b: Uint8Array, offset = 0): LayoutObject { + decode(b: Uint8Array, offset = 0): T { const dest = this.makeDestinationObject(); if (this !== this.union.getVariant(b, offset)) { throw new Error('variant mismatch'); } let contentOffset = 0; if (this.union.usesPrefixDiscriminator) { - contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; + contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; } if (this.layout) { - dest[this.property] = this.layout.decode(b, offset + contentOffset); + dest[this.property!] = this.layout.decode(b, offset + contentOffset); } else if (this.property) { - dest[this.property] = true; + dest[this.property!] = true as unknown as any; } else if (this.union.usesPrefixDiscriminator) { - dest[this.union.discriminator.property] = this.variant; + dest[this.union.discriminator.property] = this.variant as unknown as any; } return dest; } /** @override */ - encode(src: LayoutObject, b: Uint8Array, offset = 0): number { + encode(src: T, b: Uint8Array, offset = 0): number { let contentOffset = 0; if (this.union.usesPrefixDiscriminator) { - contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; + contentOffset = (this.union.discriminator as UnionLayoutDiscriminator).layout.span; } if (this.layout - && (!Object.prototype.hasOwnProperty.call(src, this.property))) { + && (!Object.prototype.hasOwnProperty.call(src, this.property!))) { throw new TypeError('variant lacks property ' + this.property); } this.union.discriminator.encode(this.variant, b, offset); let span = contentOffset; if (this.layout) { - this.layout.encode(src[this.property], b, offset + contentOffset); + this.layout.encode(src[this.property!], b, offset + contentOffset); span += this.layout.getSpan(b, offset + contentOffset); if ((0 <= this.union.span) && (span > this.union.span)) { @@ -1879,7 +1874,7 @@ export class VariantLayout extends Layout { /** Delegate {@link Layout#fromArray|fromArray} to {@link * VariantLayout#layout|layout}. */ - fromArray(values: any[]): LayoutObject | undefined { + fromArray(values: any[]): T | undefined { if (this.layout) { return this.layout.fromArray(values); } @@ -1899,7 +1894,16 @@ function fixBitwiseResult(v: number): number { } return v; } - +export type BitStructObj = T extends BitField[] + ? { + [K in Exclude, ''>]: Extract< + T[number], + BitField + > extends BitField + ? number + : any; + } + : any /** * Contain a sequence of bit fields as an unsigned integer. * @@ -1931,8 +1935,8 @@ function fixBitwiseResult(v: number): number { * * @augments {Layout} */ -export class BitStructure

extends Layout { - fields: BitField[]; +export class BitStructure[], P extends string> extends Layout, P> { + fields: T; word: UInt | UIntBE; msb: boolean; @@ -1974,7 +1978,7 @@ export class BitStructure

extends Layout { * **NOTE** The array remains mutable to allow fields to be {@link * BitStructure#addField|added} after construction. Users should * not manipulate the content of this property.*/ - this.fields = []; + this.fields = [] as unknown as T; /* Storage for the value. Capture a variable instead of using an * instance property because we don't want anything to change the @@ -1990,13 +1994,13 @@ export class BitStructure

extends Layout { } /** @override */ - decode(b: Uint8Array, offset = 0): LayoutObject { + decode(b: Uint8Array, offset = 0): BitStructObj { const dest = this.makeDestinationObject(); const value = this.word.decode(b, offset); this._packedSetValue(value); for (const fd of this.fields) { if (undefined !== fd.property) { - dest[fd.property] = fd.decode(b); + dest[fd.property as unknown as keyof BitStructObj] = fd.decode(b); } } return dest; @@ -2007,7 +2011,7 @@ export class BitStructure

extends Layout { * If `src` is missing a property for a member with a defined {@link * Layout#property|property} the corresponding region of the packed * value is left unmodified. Unused bits are also left unmodified. */ - encode(src: LayoutObject, b: Uint8Array, offset = 0): number { + encode(src: BitStructObj, b: Uint8Array, offset = 0): number { const value = this.word.decode(b, offset); this._packedSetValue(value); for (const fd of this.fields) { @@ -2060,7 +2064,7 @@ export class BitStructure

extends Layout { * @return {BitField} - the field associated with `property`, or * undefined if there is no such property. */ - fieldFor(property: string): BitField | undefined { + fieldFor(property: X): BitField | undefined { if ('string' !== typeof property) { throw new TypeError('property must be string'); } @@ -2093,14 +2097,14 @@ export class BitStructure

extends Layout { * @param {string} [property] - initializer for {@link * Layout#property|property}. */ -export class BitField

{ - container: BitStructure; +export class BitField

{ + container: BitStructure; bits: number; valueMask: number; start: number; wordMask: number; property: P; - constructor(container: BitStructure, bits: number, property: P) { + constructor(container: BitStructure[], any>, bits: number, property: P) { if (!(container instanceof BitStructure)) { throw new TypeError('container must be a BitStructure'); } @@ -2156,10 +2160,13 @@ export class BitField

{ * decoded Object. */ this.property = property; } - /** Store a value into the corresponding subsequence of the containing * bit field. */ - decode(b?: Uint8Array, offset?: number): unknown { + decode(b?: Uint8Array, offset?: number): Type { + return this._decode(b, offset) as unknown as Type + } + + protected _decode(b?: Uint8Array, offset?: number): number { const word = this.container._packedGetValue(); const wordValue = fixBitwiseResult(word & this.wordMask); const value = wordValue >>> this.start; @@ -2171,7 +2178,11 @@ export class BitField

{ * * **NOTE** This is not a specialization of {@link * Layout#encode|Layout.encode} and there is no return value. */ - encode(value: unknown): void { + encode(value: Type): void { + return this._encode(value as unknown as number); + } + + protected _encode(value: number): void { if ('number' !== typeof value || !Number.isInteger(value) || (value !== fixBitwiseResult(value & this.valueMask))) { @@ -2202,8 +2213,8 @@ export class BitField

{ * @augments {BitField} */ /* eslint-disable no-extend-native */ -export class Boolean

extends BitField

{ - constructor(container: BitStructure

, property: P) { +export class Boolean

extends BitField { + constructor(container: BitStructure[], any>, property: P) { super(container, 1, property); } @@ -2211,7 +2222,7 @@ export class Boolean

extends BitField

{ * * @returns {boolean} */ decode(b?: Uint8Array, offset?: number): boolean { - return !!super.decode(b, offset); + return !!super._decode(b, offset); } /** @override */ @@ -2220,7 +2231,7 @@ export class Boolean

extends BitField

{ // BitField requires integer values value = +value; } - super.encode(value); + super._encode(value); } } /* eslint-enable no-extend-native */ @@ -2616,35 +2627,40 @@ export const f64 =

(property?: P): Double

=> new Double(pro export const f64be =

(property?: P): DoubleBE

=> new DoubleBE(property); /** Factory for {@link Structure} values. */ -export const struct = ([], Property extends string> +export const struct = [], Property extends string> (fields: T, property?: Property, decodePrefixes?: boolean): Structure => - new Structure(fields, property, decodePrefixes)); + new Structure(fields, property, decodePrefixes); /** Factory for {@link BitStructure} values. */ -export const bits = (

(word: UInt

| UIntBE

, msb: boolean | string, property?: P): BitStructure

=> - new BitStructure(word, msb, property)); +export const bits = [], P extends string> + (word: UInt

| UIntBE

, msb: boolean | P, property?: P): BitStructure => + new BitStructure(word, msb, property); /** Factory for {@link Sequence} values. */ -export const seq = ((elementLayout: Layout, count: number | ExternalLayout, property?: string): Sequence => - new Sequence(elementLayout, count, property)); +export const seq = (elementLayout: Layout, count: number | ExternalLayout, property?: P) + : Sequence => new Sequence(elementLayout, count, property); /** Factory for {@link Union} values. */ -export const union = (

(discr: UInt

| UIntBE

| ExternalLayout

| UnionDiscriminator, - defaultLayout?: Layout | null, property?: string): Union => - new Union(discr, defaultLayout, property)); +// eslint-disable-next-line max-len +export const union =

(discr: UInt | UIntBE | ExternalLayout | UnionDiscriminator, + defaultLayout?: Layout | null, property?: P): Union

=> { + return new Union(discr, defaultLayout, property); +}; /** Factory for {@link UnionLayoutDiscriminator} values. */ -export const unionLayoutDiscriminator = ((layout: ExternalLayout, property?: string): UnionLayoutDiscriminator => - new UnionLayoutDiscriminator(layout, property)); +export const unionLayoutDiscriminator =

(layout: ExternalLayout, property?: P) + : UnionLayoutDiscriminator

=> new UnionLayoutDiscriminator(layout, property); /** Factory for {@link Blob} values. */ -export const blob = ((length: number | ExternalLayout, property?: string): Blob => new Blob(length, property)); +export const blob =

(length: number | ExternalLayout, property?: P) + : Blob

=> new Blob(length, property); /** Factory for {@link CString} values. */ export const cstr =

(property?: P): CString

=> new CString(property); /** Factory for {@link UTF8} values. */ -export const utf8 = ((maxSpan: number, property?: string): UTF8 => new UTF8(maxSpan, property)); +export const utf8 =

(maxSpan: number, property?: P): UTF8

=> new UTF8(maxSpan, property); /** Factory for {@link Constant} values. */ -export const constant = ((value: T, property?: string): Constant => new Constant(value, property)); +export const constant = (value: T, property?: P) + : Constant => new Constant(value, property);