mirror of
https://github.com/barsdeveloper/ueblueprint.git
synced 2026-06-11 21:35:17 +08:00
Grammar refactoring
* Grammar refactoring WIP * ISerializer.grammar * Fixing various bugs in the grammar * Small touch that improoves performance * Fix unknown values grammar * Various fixes * Serialization refactoring to drop suboject logic * Details fixed * Entity attributes initialization refactoring * JSDoc error fixed * Rename value key to default * Remove useless default * Revert string keys
This commit is contained in:
@@ -1,20 +1,19 @@
|
||||
import ComputedType from "./ComputedType.js"
|
||||
import SerializerFactory from "../serialization/SerializerFactory.js"
|
||||
import SubAttributesDeclaration from "./SubObject.js"
|
||||
import UnionType from "./UnionType.js"
|
||||
import Utility from "../Utility.js"
|
||||
|
||||
/**
|
||||
* @typedef {(entity: IEntity) => AnyValue} ValueSupplier
|
||||
* @typedef {(entity: IEntity) => AnyValueConstructor<AnyValue>} TypeSupplier
|
||||
* @typedef {IEntity | String | Number | BigInt | Boolean} AnySimpleValue
|
||||
* @typedef {AnySimpleValue | AnySimpleValue[]} AnyValue
|
||||
* @typedef {{
|
||||
* [key: String]: AttributeInformation | AnyValue | SubAttributesDeclaration
|
||||
* [key: String]: AttributeInformation
|
||||
* }} AttributeDeclarations
|
||||
* @typedef {typeof IEntity} EntityConstructor
|
||||
* @typedef {{
|
||||
* type?: AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | UnionType | TypeSupplier,
|
||||
* value?: AnyValue | ValueSupplier,
|
||||
* type?: AnyValueConstructor<AnyValue> | AnyValueConstructor<AnyValue>[] | UnionType | ComputedType,
|
||||
* default?: AnyValue | ValueSupplier,
|
||||
* showDefault?: Boolean,
|
||||
* nullable?: Boolean,
|
||||
* ignored?: Boolean,
|
||||
@@ -32,6 +31,7 @@ import Utility from "../Utility.js"
|
||||
|
||||
export default class IEntity {
|
||||
|
||||
static lookbehind = ""
|
||||
/** @type {AttributeDeclarations} */
|
||||
static attributes = {}
|
||||
static defaultAttribute = {
|
||||
@@ -43,138 +43,6 @@ export default class IEntity {
|
||||
}
|
||||
|
||||
constructor(values = {}, suppressWarns = false) {
|
||||
/**
|
||||
* @param {Object} target
|
||||
* @param {Object} attributes
|
||||
* @param {Object} values
|
||||
* @param {String} prefix
|
||||
*/
|
||||
const defineAllAttributes = (target, attributes, values = {}, prefix = "") => {
|
||||
const valuesNames = Object.keys(values)
|
||||
const attributesNames = Object.keys(attributes)
|
||||
const allAttributesNames = Utility.mergeArrays(attributesNames, valuesNames)
|
||||
for (let attributeName of allAttributesNames) {
|
||||
let value = Utility.objectGet(values, [attributeName])
|
||||
/** @type {AttributeInformation} */
|
||||
let attribute = attributes[attributeName]
|
||||
|
||||
if (attribute instanceof SubAttributesDeclaration) {
|
||||
target[attributeName] = {}
|
||||
defineAllAttributes(
|
||||
target[attributeName],
|
||||
attribute.attributes,
|
||||
values[attributeName],
|
||||
attributeName + "."
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!suppressWarns) {
|
||||
if (!(attributeName in attributes)) {
|
||||
console.warn(
|
||||
`UEBlueprint: Attribute ${prefix}${attributeName} in the serialized data is not defined in `
|
||||
+ `${this.constructor.name}.attributes`
|
||||
)
|
||||
} else if (
|
||||
valuesNames.length > 0
|
||||
&& !(attributeName in values)
|
||||
&& !(!attribute.showDefault || attribute.ignored)
|
||||
) {
|
||||
console.warn(
|
||||
`UEBlueprint: ${this.constructor.name} will add attribute ${prefix}${attributeName} not `
|
||||
+ "defined in the serialized data"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!attribute) {
|
||||
// Remember attributeName can come from the values and be not defined in the attributes
|
||||
target[attributeName] = value
|
||||
continue
|
||||
}
|
||||
|
||||
let defaultValue = attribute.value
|
||||
let defaultType = attribute.type
|
||||
if (attribute.serialized && defaultType instanceof Function) {
|
||||
// If the attribute is serialized, the type must contain a function providing the type
|
||||
defaultType = /** @type {TypeSupplier} */(defaultType)(this)
|
||||
}
|
||||
if (defaultType instanceof Array) {
|
||||
defaultType = Array
|
||||
}
|
||||
if (defaultValue instanceof Function) {
|
||||
defaultValue = defaultValue(this)
|
||||
}
|
||||
if (defaultType === undefined) {
|
||||
defaultType = Utility.getType(defaultValue)
|
||||
}
|
||||
const assignAttribute = !attribute.predicate
|
||||
? v => target[attributeName] = v
|
||||
: v => {
|
||||
Object.defineProperties(target, {
|
||||
["#" + attributeName]: {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
},
|
||||
[attributeName]: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this["#" + attributeName]
|
||||
},
|
||||
set(v) {
|
||||
if (!attribute.predicate?.(v)) {
|
||||
console.warn(
|
||||
`UEBlueprint: Tried to assign attribute ${prefix}${attributeName} to `
|
||||
+ `${this.constructor.name} not satisfying the predicate`
|
||||
)
|
||||
return
|
||||
}
|
||||
this["#" + attributeName] = v
|
||||
}
|
||||
},
|
||||
})
|
||||
this[attributeName] = v
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
// Remember value can still be null
|
||||
if (value?.constructor === String && attribute.serialized && defaultType !== String) {
|
||||
value = SerializerFactory
|
||||
.getSerializer(/** @type {AnyValueConstructor<*>} */(defaultType))
|
||||
.deserialize(/** @type {String} */(value))
|
||||
}
|
||||
assignAttribute(Utility.sanitize(value, /** @type {AnyValueConstructor<*>} */(defaultType)))
|
||||
continue // We have a value, need nothing more
|
||||
}
|
||||
if (defaultType instanceof UnionType) {
|
||||
if (defaultValue != undefined) {
|
||||
defaultType = defaultType.types.find(
|
||||
type => defaultValue instanceof type || defaultValue.constructor == type
|
||||
) ?? defaultType.getFirstType()
|
||||
} else {
|
||||
defaultType = defaultType.getFirstType()
|
||||
}
|
||||
}
|
||||
if (defaultValue === undefined) {
|
||||
defaultValue = Utility.sanitize(new /** @type {AnyValueConstructor<*>} */(defaultType)())
|
||||
}
|
||||
if (!attribute.showDefault) {
|
||||
assignAttribute(undefined) // Declare undefined to preserve the order of attributes
|
||||
continue
|
||||
}
|
||||
if (attribute.serialized) {
|
||||
if (defaultType !== String && defaultValue.constructor === String) {
|
||||
defaultValue = SerializerFactory
|
||||
.getSerializer(/** @type {AnyValueConstructor<*>} */(defaultType))
|
||||
.deserialize(defaultValue)
|
||||
}
|
||||
}
|
||||
assignAttribute(Utility.sanitize(
|
||||
/** @type {AnyValue} */(defaultValue),
|
||||
/** @type {AnyValueConstructor<AnyValue>} */(defaultType)
|
||||
))
|
||||
}
|
||||
}
|
||||
const attributes = /** @type {typeof IEntity} */(this.constructor).attributes
|
||||
if (values.constructor !== Object && Object.keys(attributes).length === 1) {
|
||||
// Where there is just one attribute, option can be the value of that attribute
|
||||
@@ -182,39 +50,141 @@ export default class IEntity {
|
||||
[Object.keys(attributes)[0]]: values
|
||||
}
|
||||
}
|
||||
defineAllAttributes(this, attributes, values)
|
||||
const valuesNames = Object.keys(values)
|
||||
const attributesNames = Object.keys(attributes)
|
||||
const allAttributesNames = Utility.mergeArrays(attributesNames, valuesNames)
|
||||
for (let attributeName of allAttributesNames) {
|
||||
let value = values[attributeName]
|
||||
let attribute = /** @type {AttributeInformation} */(attributes[attributeName])
|
||||
|
||||
if (!suppressWarns) {
|
||||
if (!(attributeName in attributes)) {
|
||||
console.warn(
|
||||
`UEBlueprint: Attribute ${attributeName} in the serialized data is not defined in `
|
||||
+ `${this.constructor.name}.attributes`
|
||||
)
|
||||
} else if (
|
||||
valuesNames.length > 0
|
||||
&& !(attributeName in values)
|
||||
&& !(!attribute.showDefault || attribute.ignored)
|
||||
) {
|
||||
console.warn(
|
||||
`UEBlueprint: ${this.constructor.name} will add attribute ${attributeName} not `
|
||||
+ "defined in the serialized data"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!attribute) {
|
||||
// Remember attributeName can come from the values and be not defined in the attributes
|
||||
// In that case just assign it and skip the rest
|
||||
this[attributeName] = value
|
||||
continue
|
||||
}
|
||||
|
||||
let defaultValue = attribute.default
|
||||
let defaultType = attribute.type
|
||||
if (defaultType instanceof ComputedType) {
|
||||
defaultType = defaultType.compute(this)
|
||||
}
|
||||
if (defaultType instanceof Array) {
|
||||
defaultType = Array
|
||||
}
|
||||
if (defaultValue instanceof Function) {
|
||||
defaultValue = defaultValue(this)
|
||||
}
|
||||
if (defaultType === undefined) {
|
||||
defaultType = Utility.getType(defaultValue)
|
||||
}
|
||||
const assignAttribute = !attribute.predicate
|
||||
? v => this[attributeName] = v
|
||||
: v => {
|
||||
Object.defineProperties(this, {
|
||||
["#" + attributeName]: {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
},
|
||||
[attributeName]: {
|
||||
enumerable: true,
|
||||
get() {
|
||||
return this["#" + attributeName]
|
||||
},
|
||||
set(v) {
|
||||
if (!attribute.predicate?.(v)) {
|
||||
console.warn(
|
||||
`UEBlueprint: Tried to assign attribute ${attributeName} to `
|
||||
+ `${this.constructor.name} not satisfying the predicate`
|
||||
)
|
||||
return
|
||||
}
|
||||
this["#" + attributeName] = v
|
||||
}
|
||||
},
|
||||
})
|
||||
this[attributeName] = v
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
// Remember value can still be null
|
||||
if (value?.constructor === String && attribute.serialized && defaultType !== String) {
|
||||
value = SerializerFactory
|
||||
.getSerializer(/** @type {AnyValueConstructor<*>} */(defaultType))
|
||||
.deserialize(/** @type {String} */(value))
|
||||
}
|
||||
assignAttribute(Utility.sanitize(value, /** @type {AnyValueConstructor<*>} */(defaultType)))
|
||||
continue // We have a value, need nothing more
|
||||
}
|
||||
if (defaultType instanceof UnionType) {
|
||||
if (defaultValue != undefined) {
|
||||
defaultType = defaultType.types.find(
|
||||
type => defaultValue instanceof type || defaultValue.constructor == type
|
||||
) ?? defaultType.getFirstType()
|
||||
} else {
|
||||
defaultType = defaultType.getFirstType()
|
||||
}
|
||||
}
|
||||
if (defaultValue === undefined) {
|
||||
defaultValue = Utility.sanitize(new /** @type {AnyValueConstructor<*>} */(defaultType)())
|
||||
}
|
||||
if (!attribute.showDefault) {
|
||||
assignAttribute(undefined) // Declare undefined to preserve the order of attributes
|
||||
continue
|
||||
}
|
||||
if (attribute.serialized) {
|
||||
if (defaultType !== String && defaultValue.constructor === String) {
|
||||
defaultValue = SerializerFactory
|
||||
.getSerializer(/** @type {AnyValueConstructor<*>} */(defaultType))
|
||||
.deserialize(defaultValue)
|
||||
}
|
||||
}
|
||||
assignAttribute(Utility.sanitize(
|
||||
/** @type {AnyValue} */(defaultValue),
|
||||
/** @type {AnyValueConstructor<AnyValue>} */(defaultType)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {AttributeDeclarations} attributes */
|
||||
static cleanupAttributes(attributes, prefix = "") {
|
||||
for (const attributeName in attributes) {
|
||||
if (attributes[attributeName] instanceof SubAttributesDeclaration) {
|
||||
this.cleanupAttributes(
|
||||
/** @type {SubAttributesDeclaration} */(attributes[attributeName]).attributes,
|
||||
prefix + "." + attributeName
|
||||
)
|
||||
continue
|
||||
}
|
||||
if (attributes[attributeName].constructor !== Object) {
|
||||
attributes[attributeName] = {
|
||||
value: attributes[attributeName],
|
||||
}
|
||||
}
|
||||
const attribute = /** @type {AttributeInformation} */(attributes[attributeName])
|
||||
if (attribute.type === undefined && !(attribute.value instanceof Function)) {
|
||||
attribute.type = Utility.getType(attribute.value)
|
||||
if (attribute.type === undefined && !(attribute.default instanceof Function)) {
|
||||
attribute.type = Utility.getType(attribute.default)
|
||||
}
|
||||
attributes[attributeName] = {
|
||||
...IEntity.defaultAttribute,
|
||||
...attribute,
|
||||
}
|
||||
if (attribute.value === undefined && attribute.type === undefined) {
|
||||
throw new Error(
|
||||
`UEBlueprint: Expected either "type" or "value" property in ${this.name} attribute ${prefix}`
|
||||
+ attributeName
|
||||
)
|
||||
if (attribute.default === undefined) {
|
||||
if (attribute.type === undefined) {
|
||||
throw new Error(
|
||||
`UEBlueprint: Expected either "type" or "value" property in ${this.name} attribute ${prefix}`
|
||||
+ attributeName
|
||||
)
|
||||
}
|
||||
attribute[attributeName] = Utility.sanitize(undefined, attribute.type)
|
||||
}
|
||||
if (attribute.value === null) {
|
||||
if (attribute.default === null) {
|
||||
attributes[attributeName].nullable = true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user