Property Module Peer Review & Feedback

I am working on a module that reads the properties of classes and instances, as well as other features.

I am wondering how to go about publishing, optimizing (if needed), and adding more features to the module.

Note: it composes of multiple module scripts:

> ClassInfo (ModuleScript)
    > ClassesModule (ModuleScript)
    > TweenModule (ModuleScript)

Here are the scripts:

ClassInfo
local tweenService = game:GetService("TweenService")

local repr = require(game.ServerStorage.Repr)

local ClassInfo = {}

ClassInfo.ClassModule = require(script.ClassesModule)

ClassInfo.TweenModule = require(script.TweenModule)


ClassInfo.tweenDifference = function(editingInstance : Instance, goalInstance : Instance, tweenInfo : TweenInfo, exeptionProperties : {string})
	local className = editingInstance.ClassName
	
	goalInstance = if typeof(goalInstance) == "Instance" then goalInstance else Instance.new(className)
	tweenInfo = if typeof(tweenInfo) == "TweenInfo" then tweenInfo else TweenInfo.new()
	exeptionProperties = if typeof(exeptionProperties) == "table" then exeptionProperties else {}
	
	local className2 = goalInstance.ClassName
	
	if className == className2 then
		local info = ClassInfo.ClassModule.getPropertiesDifferences(editingInstance, goalInstance, {Show_ReadOnly = false, Show_Hidden = false, Show_Deprecated = false, Show_Functions = false, Show_Events = false})
		
		if info then
			
			table.foreach(info, function(k, v)
				
				if (v.category == "Data" or v.category == "Surface") or not ClassInfo.TweenModule.canTweenValue(workspace.CoolPart[k]) or table.find(exeptionProperties, k) then
					info[k] = nil
				end
			end)
			
			local toTween = {}

			table.foreach(info, function(k, v)
				toTween[k] = workspace.Baseplate[k]
			end)

			local tween = tweenService:Create(editingInstance, tweenInfo, toTween)
			
			return tween 
		end
	end 
end


return ClassInfo
ClassesModule
local repr = require(game.ServerStorage.Repr)

local httpService = game:GetService("HttpService")

local propertiesVersion = httpService:GetAsync("http://setup.roproxy.com/versionQTStudio")
local jsonProperties = httpService:GetAsync("http://setup.roproxy.com/".. propertiesVersion .."-API-Dump.json")
local classesInfo = httpService:JSONDecode(jsonProperties)

local defaultDetailInfo = {
	["Show_NotReplicated"] = true,
	["Show_Deprecated"] = true, -- false
	["Show_ReadOnly"] = true,
	["Show_Hidden"] = true, -- false
	["Show_Functions"] = true,
	["Show_Events"] = true,
}

local allClasses : {string: string} = {}
local loadedAllClasses = false



local classModule = {}

classModule.API_Version = tonumber(propertiesVersion) or propertiesVersion



function classModule.getAllClassNames()
	if not loadedAllClasses then
		table.foreach(classesInfo.Classes, function(_, v)
			if v.Name and typeof(v.Name) == "string" then
				local className = v.Name
				allClasses[className] = className
			end 
		end)

		loadedAllClasses = true
	end
	return allClasses
end



function classModule.ChangeDefaultDetails(detailName : string, value : boolean)
	if typeof(detailName) == "string" then
		if defaultDetailInfo[detailName] then
			if typeof(value) == "boolean" then
				defaultDetailInfo[detailName] = value
			else
				warn(value, " is not a boolean!")
			end
		else
			warn(detailName .. " is not a valid value!")
		end
	else
		warn(detailName, " is not a string!")
	end
end




function classModule.getInfo(
	className : string, 
	details : {
		["Show_NotReplicated"] : boolean,
		["Show_Deprecated"] : boolean,
		["Show_ReadOnly"] : boolean,
		["Show_Hidden"] : boolean,
		["Show_Functions"] : boolean,
		["Show_Events"] : boolean
	}
)
	local didFind = false

	local members = {} -- properties

	local checkedClasses = {}
	local classLoops = {{name = className, isInherited = false}} -- classes
	local currIndex = 1

	for i, passedClass in ipairs(classLoops) do
		-- passedClass = .name (class name), .isInherited (is it inherited)

		local passedClassName = passedClass.name
		local isInherited = passedClass.isInherited

		table.foreach(classesInfo.Classes, function(_, v) 
			-- v = classInfo

			if v.Members and v.Name and v.Name == passedClassName then
				didFind = true

				local Superclass : string = v.Superclass
				local ClassName : string = v.Name
				local MemoryCategory : string = v.MemoryCategory

				if not table.find(checkedClasses, passedClassName) then
					table.insert(classLoops, {name = Superclass, isInherited = true})
				end

				table.insert(checkedClasses, passedClassName)



				local NotReplicated = if typeof(details.Show_NotReplicated) == "boolean" then details.Show_NotReplicated else defaultDetailInfo.Show_NotReplicated
				local Deprecated = if typeof(details.Show_Deprecated) == "boolean" then details.Show_Deprecated else defaultDetailInfo.Show_Deprecated
				local ReadOnly = if typeof(details.Show_ReadOnly) == "boolean" then details.Show_ReadOnly else defaultDetailInfo.Show_ReadOnly
				local Hidden = if typeof(details.Show_Hidden) == "boolean" then details.Show_Hidden else defaultDetailInfo.Show_Hidden
				local Functions = if typeof(details.Show_Functions) == "boolean" then details.Show_Functions else defaultDetailInfo.Show_Functions
				local Events = if typeof(details.Show_Events) == "boolean" then details.Show_Events else defaultDetailInfo.Show_Events
				
				table.foreach(v.Members, function(_, attributeInfo)
					-- attributeInfo = property/attribute info

					local canShow = true

					local this_notReplicated = false
					local this_deprecated = false
					local this_readOnly = false
					local this_hidden = false
					local this_function = attributeInfo.MemberType == "Function"
					local this_event = attributeInfo.MemberType == "Event"

					if attributeInfo.Tags then
						this_notReplicated = if table.find(attributeInfo.Tags, "NotReplicated") then true else false
						this_deprecated = if table.find(attributeInfo.Tags, "Deprecated") then true else false
						this_readOnly = if table.find(attributeInfo.Tags, "ReadOnly") then true else false
						this_hidden = if table.find(attributeInfo.Tags, "Hidden") then true else false
					end

					canShow = canShow and (NotReplicated or not this_notReplicated)
					canShow = canShow and (Deprecated or not this_deprecated)
					canShow = canShow and (ReadOnly or not this_readOnly)
					canShow = canShow and (Hidden or not this_hidden)
					canShow = canShow and (Functions or not this_function)
					canShow = canShow and (Events or not this_event)
					
					
					if canShow then
						local allValues = {}
						
						-- Basic Info --

						local dataCategory : string = attributeInfo.Category 
						-- the category seen from studio
						local memberType : string = attributeInfo.MemberType 
						-- usually "Property"
						local dataName : string = attributeInfo.Name 
						-- the name of property


						-- Formally/JSON "Tags" Functionally/params "Details" --

						local isNotReplicated : boolean = this_notReplicated
						local isDeprecated : boolean = this_deprecated
						local isReadOnly : boolean = this_readOnly
						local isHidden : boolean = this_hidden


						-- security (TO-DO) --

						local canRead = attributeInfo.Security.Read -- no idea
						local canWrite = attributeInfo.Security.Write -- no idea

						-- "None", 
						-- "RobloxScriptSecurity", 
						-- "LocalUserSecurity", 
						-- "PluginSecurity"


						-- serialization --

						local canLoad : boolean
						local canSave : boolean

						if attributeInfo.Serialization then
							canLoad = attributeInfo.Serialization.CanLoad
							canSave = attributeInfo.Serialization.CanSave
							-- what do these mean?
						end

						-- Thread Safety --

						local threadSafetyValue : string = attributeInfo.ThreadSafety

						local threadSafety = {} -- see docs for def.

						threadSafety.Value = threadSafetyValue
						threadSafety.IsUnsafe = threadSafetyValue == "Unsafe"
						threadSafety.IsReadParallel = threadSafetyValue == "ReadSafe"
						threadSafety.IsSafe = threadSafetyValue == "Safe"

						--threadSafety.IsLocalSafe = threadSafetyValue == ""
						-- need this ^ (doesn't exist for some reason)


						-- Value info --
						local valueType : string
						local valueTypeCategory : string

						if attributeInfo.ValueType then
							valueType = attributeInfo.ValueType.Name
							-- the type, from typeof()
							valueTypeCategory = attributeInfo.ValueType.Category
							-- Always primitive
						end

						-- Function parameters --
						local parameters : {} = nil
						local returnType : string = nil
						local returnCategory : string = nil
						if memberType == "Function" then  
							parameters = attributeInfo.Parameters

							if attributeInfo.ReturnType.Name ~= "void" then
								returnType = attributeInfo.ReturnType.Name
								returnCategory = attributeInfo.ReturnType.Category
							end
						end

						allValues.category = dataCategory
						allValues.memberType = memberType
						allValues.name = dataName

						allValues.isNotReplicated = isNotReplicated
						allValues.isDeprecated = isDeprecated
						allValues.isReadOnly = isReadOnly
						allValues.isHidden = isHidden

						allValues.canRead = canRead
						allValues.canWrite = canWrite
						
						allValues.serialization_canLoad = canLoad or nil
						allValues.serialization_canSave = canSave or nil

						allValues.threadSafety = threadSafety

						allValues.valueType = valueType or nil
						allValues.valueTypeCategory = valueTypeCategory or nil

						allValues.isInherited = isInherited
						allValues.inheritedFrom = if isInherited then passedClassName else nil

						allValues.parameters = parameters
						allValues.returnType = returnType
						allValues.returnCategory = returnCategory

						members[dataName] = allValues
					end
				end)
			end
		end)
	end

	if not didFind then
		warn("getInfo fail! (Check parameters)")
		return nil
	end

	return members
end



function classModule.getInstanceProperties(
	passedInstance : Instance, 
	details : {
		["Show_NotReplicated"] : boolean,
		["Show_Deprecated"] : boolean,
		["Show_ReadOnly"] : boolean,
		["Show_Hidden"] : boolean,
		["Show_Functions"] : boolean,
		["Show_Events"] : boolean
	}
)
	local class = passedInstance.ClassName

	--[[ revise
	if details == nil then
		details = {
			Show_NotReplicated = true,
			Show_Hidden = true,
			Show_ReadOnly = true,
			Show_Deprecated = true
		}
	end
	--]]

	if class then
		local info = classModule.getInfo(class, details)

		if info then
			local newTable : {string : string} = {}


			table.foreach(info, function(k : string, v)
				if string.gsub(k, "%s", "") == k and v.canWrite ~= "PluginSecurity" and v.canWrite ~= "RobloxScriptSecurity" and v.canWrite ~= "LocalUserSecurity" then
					
					if v.memberType == "Property" then
						if k == "Origin" then
						--if v.valueTypeCategory == "DataType" then
							--warn(repr(v, {pretty = true})) 
						end
						
						if k ~= "Origin" then
							if k == "RobloxLocked" then
								--warn(repr(v, {pretty = true})) 
							end
							
							newTable[k] = passedInstance[k] 
						end
					elseif v.memberType == "Function" then
						newTable[k] = function(self, ...)
							passedInstance[k](self, ...)
						end
					elseif v.memberType == "Event" then

					else
						warn(k, " has type ", v.memberType)
					end 
				end
			end)

			return newTable 
		end
	end 
end



function classModule.getPropertiesDifferences(
	instance1 : Instance, 
	instance2 : Instance,
	details : {
		["Show_NotReplicated"] : boolean,
		["Show_Deprecated"] : boolean,
		["Show_ReadOnly"] : boolean,
		["Show_Hidden"] : boolean,
		["Show_Functions"] : boolean,
		["Show_Events"] : boolean
	}
)
	if instance1.ClassName == instance2.ClassName then
		local info1 = classModule.getInstanceProperties(instance1, details)
		local info2 = classModule.getInstanceProperties(instance2, details)
		
		local classInfo = classModule.getInfo(instance1.ClassName, details)
		
		local differences = {}
		
		table.foreach(info1, function(k, _)
			local val1 = info1[k]
			local val2 = info2[k]
			
			if val1 ~= val2 and type(val1 or val2) ~= "function" then
				--table.insert(differences, k)
				differences[k] = classInfo[k]
			end
		end)
		
		return differences
	else
		warn(instance1, instance2, " are not the same class!")
	end
end

return classModule
TweenModule
local tweenModule = {}

local canTweenValues = {
	"number",
	"boolean",
	"CFrame",
	"Rect",
	"Color3",
	"UDim",
	"UDim2",
	"Vector2",
	"Vector2int16",
	"Vector3",
	"EnumItem",
}

tweenModule.tweenableTypes = {}

for i, type_ in ipairs(canTweenValues) do
	table.insert(tweenModule.tweenableTypes, type_)
end

function tweenModule.canTweenValue(value) 
	if table.find(canTweenValues, typeof(value)) then
		return true
	else
		return nil
	end
end

return tweenModule

Any help and comments are appreciated!

Current Features

ClassInfo:

  • tweenDifference: - Tweens one instance to have the same properties of the second instance (assuming they have the same class)
  • returns the other two modules

ClassesModule:

  • API_Version - the version of the API used
  • getAllClassNames - returns the name of all the classes from the API
  • ChangeDefaultDetails - edits the default value used for getting properties
  • getInfo - returns the info/attributes/properties of the given class
  • getInstanceProperties - returns the info/attributes/properties of the given instance
  • getPropertiesDifferences - returns the property names that are different between the two instances (assuming they have the same class name)

TweenModule:

  • canTweenValue - returns a boolean dictating if the given datatype can be tweened