From 812ed1becc90719d0c897583cf027e4549b55324 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:05:43 +0900 Subject: [PATCH 01/13] add array type support to TypeUtil --- lua/wikis/commons/TypeUtil.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/wikis/commons/TypeUtil.lua b/lua/wikis/commons/TypeUtil.lua index d5d8290da1f..99bcc0e6fda 100644 --- a/lua/wikis/commons/TypeUtil.lua +++ b/lua/wikis/commons/TypeUtil.lua @@ -172,6 +172,8 @@ function TypeUtil.valueIsTypeNoTable (value, typeSpec) elseif typeSpec == 'pagename' then -- A pagename is a string, with first letter capitalized and may not contains spaces return type(value) == 'string' and value:find('^%u') and not value:find(' ') + elseif typeSpec == 'array' then + return Array.isArray(value) elseif String.endsWith(typeSpec, '?') then return value == nil or TypeUtil.valueIsTypeNoTable(value, typeSpec:sub(1, -2)) elseif typeSpec == 'any' then From c22ea39369c79792da405f0826565822b359756a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:13:53 +0900 Subject: [PATCH 02/13] use typeutil --- lua/wikis/commons/Lpdb.lua | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/lua/wikis/commons/Lpdb.lua b/lua/wikis/commons/Lpdb.lua index aa4ca1e309a..1d920859e06 100644 --- a/lua/wikis/commons/Lpdb.lua +++ b/lua/wikis/commons/Lpdb.lua @@ -13,6 +13,7 @@ local FnUtil = Lua.import('Module:FnUtil') local Logic = Lua.import('Module:Logic') local Table = Lua.import('Module:Table') local TextSanitizer = Lua.import('Module:TextSanitizer') +local TypeUtil = Lua.import('Module:TypeUtil') local Variables = Lua.import('Module:Variables') local Lpdb = {} @@ -103,9 +104,9 @@ end --- LPDB Object-Relational Mapping ----@alias ModelColumnData {name: string, fieldType: string|any, default: any} +---@alias ModelColumnData {name: string, fieldType: TypeUtilType, default: any} ----@class Model +---@class Model: BaseClass ---@field tableName string ---@field tableColumns ModelColumnData[] local Model = Class.new(function(self, name, columns) @@ -113,7 +114,7 @@ local Model = Class.new(function(self, name, columns) self.tableColumns = columns end) ----@class ModelRow +---@class ModelRow: BaseClass ---@field private tableName string ---@field private tableColumns ModelColumnData[] ---@field private fields table @@ -232,8 +233,8 @@ Lpdb.Match2 = Model('match2', { {name = 'patch', fieldType = 'string', default = ''}, {name = 'date', fieldType = 'string', default = 0}, {name = 'dateexact', fieldType = 'number', default = 0}, - {name = 'stream', fieldType = 'struct', default = {}}, - {name = 'links', fieldType = 'struct', default = {}}, + {name = 'stream', fieldType = 'table', default = {}}, + {name = 'links', fieldType = 'table', default = {}}, {name = 'bestof', fieldType = 'number', default = 0}, {name = 'vod', fieldType = 'string', default = ''}, {name = 'tournament', fieldType = 'string', default = ''}, @@ -243,11 +244,11 @@ Lpdb.Match2 = Model('match2', { {name = 'series', fieldType = 'string', default = ''}, {name = 'icon', fieldType = 'string', default = ''}, {name = 'icondark', fieldType = 'string', default = ''}, - {name = 'liquipediatier', fieldType = 'string|number', default = ''}, + {name = 'liquipediatier', fieldType = TypeUtil.union('string', 'number'), default = ''}, {name = 'liquipediatiertype', fieldType = 'string', default = ''}, {name = 'publishertier', fieldType = 'string', default = ''}, - {name = 'extradata', fieldType = 'struct', default = {}}, - {name = 'match2bracketdata', fieldType = 'struct', default = {}}, + {name = 'extradata', fieldType = 'table', default = {}}, + {name = 'match2bracketdata', fieldType = 'table', default = {}}, {name = 'match2opponents', fieldType = 'array', default = {}}, {name = 'match2games', fieldType = 'array', default = {}}, }) @@ -268,21 +269,21 @@ Lpdb.Placement = Model('placement', { {name = 'weight', fieldType = 'number', default = 0}, {name = 'mode', fieldType = 'string', default = ''}, {name = 'type', fieldType = 'string', default = ''}, - {name = 'liquipediatier', fieldType = 'string|number', default = ''}, + {name = 'liquipediatier', fieldType = TypeUtil.union('string', 'number'), default = ''}, {name = 'liquipediatiertype', fieldType = 'string', default = ''}, {name = 'publishertier', fieldType = 'string', default = ''}, {name = 'icon', fieldType = 'string', default = ''}, {name = 'icondark', fieldType = 'string', default = ''}, {name = 'game', fieldType = 'string', default = ''}, - {name = 'lastvsdata', fieldType = 'struct', default = {}}, + {name = 'lastvsdata', fieldType = 'table', default = {}}, {name = 'opponentname', fieldType = 'string', default = ''}, {name = 'opponenttemplate', fieldType = 'string', default = ''}, {name = 'opponenttype', fieldType = 'string', default = ''}, - {name = 'opponentplayers', fieldType = 'struct', default = {}}, + {name = 'opponentplayers', fieldType = 'table', default = {}}, {name = 'qualifier', fieldType = 'string', default = ''}, - {name = 'qualifierpage', fieldType = 'string', default = ''}, + {name = 'qualifierpage', fieldType = 'pagename?', default = ''}, {name = 'qualifierurl', fieldType = 'string', default = ''}, - {name = 'extradata', fieldType = 'struct', default = {}}, + {name = 'extradata', fieldType = 'table', default = {}}, }) ---@class SquadPlayerModel:Model @@ -308,7 +309,7 @@ Lpdb.SquadPlayer = Model('squadplayer', { {name = 'joindate', fieldType = 'string', default = ''}, {name = 'leavedate', fieldType = 'string', default = ''}, {name = 'inactivedate', fieldType = 'string', default = ''}, - {name = 'extradata', fieldType = 'struct', default = {}}, + {name = 'extradata', fieldType = 'table', default = {}}, }) ---@class DataPoint:Model @@ -320,7 +321,7 @@ Lpdb.DataPoint = Model('datapoint', { {name = 'image', fieldType = 'string', default = ''}, {name = 'imagedark', fieldType = 'string', default = ''}, {name = 'date', fieldType = 'string', default = 0}, - {name = 'extradata', fieldType = 'struct', default = {}}, + {name = 'extradata', fieldType = 'table', default = {}}, }) return Lpdb From bc513b631d864fa7e692bb5408c12617b7f68845 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:28:20 +0900 Subject: [PATCH 03/13] add typeutil type for opponent types --- lua/wikis/commons/Lpdb.lua | 3 ++- lua/wikis/commons/Opponent.lua | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lua/wikis/commons/Lpdb.lua b/lua/wikis/commons/Lpdb.lua index 1d920859e06..c8b5bbde25a 100644 --- a/lua/wikis/commons/Lpdb.lua +++ b/lua/wikis/commons/Lpdb.lua @@ -11,6 +11,7 @@ local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') local FnUtil = Lua.import('Module:FnUtil') local Logic = Lua.import('Module:Logic') +local Opponent = Lua.import('Module:Opponent/Custom') local Table = Lua.import('Module:Table') local TextSanitizer = Lua.import('Module:TextSanitizer') local TypeUtil = Lua.import('Module:TypeUtil') @@ -278,7 +279,7 @@ Lpdb.Placement = Model('placement', { {name = 'lastvsdata', fieldType = 'table', default = {}}, {name = 'opponentname', fieldType = 'string', default = ''}, {name = 'opponenttemplate', fieldType = 'string', default = ''}, - {name = 'opponenttype', fieldType = 'string', default = ''}, + {name = 'opponenttype', fieldType = Opponent.types.OpponentType, default = ''}, {name = 'opponentplayers', fieldType = 'table', default = {}}, {name = 'qualifier', fieldType = 'string', default = ''}, {name = 'qualifierpage', fieldType = 'pagename?', default = ''}, diff --git a/lua/wikis/commons/Opponent.lua b/lua/wikis/commons/Opponent.lua index 82a21c2178e..bb975c08df0 100644 --- a/lua/wikis/commons/Opponent.lua +++ b/lua/wikis/commons/Opponent.lua @@ -100,6 +100,10 @@ Opponent.types.Opponent = TypeUtil.union( Opponent.types.LiteralOpponent ) +Opponent.types.OpponentType = TypeUtil.literalUnion( + Opponent.solo, Opponent.duo, Opponent.trio, Opponent.quad, Opponent.team, Opponent.literal +) + ---Checks if the provided opponent type is a party type ---@param type OpponentType? ---@return boolean @@ -206,7 +210,7 @@ end ---@param type string ---@return boolean function Opponent.isType(type) - return Table.includes(Opponent.types, type) + return #TypeUtil.checkValue(type, Opponent.types) == 0 end ---Reads an opponent type. @@ -214,13 +218,13 @@ end ---@param type string ---@return OpponentType? function Opponent.readType(type) - return Table.includes(Opponent.types, type) and type or nil + return Opponent.isType(type) and type or nil end ---Asserts that an arbitrary value is a valid representation of an opponent ---@param opponent any function Opponent.assertOpponent(opponent) - assert(Opponent.isOpponent(opponent), 'Invalid opponent') + TypeUtil.assertValue(opponent, Opponent.types.Opponent, {name = 'Opponent'}) end ---Validates that an arbitrary value is a valid representation of an opponent From 0a0fe98a8d74e986ca6faaf8f8ccb56ca2c8ace8 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:30:22 +0900 Subject: [PATCH 04/13] add typechecker --- lua/wikis/commons/Lpdb.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/Lpdb.lua b/lua/wikis/commons/Lpdb.lua index c8b5bbde25a..f84b978a34b 100644 --- a/lua/wikis/commons/Lpdb.lua +++ b/lua/wikis/commons/Lpdb.lua @@ -142,7 +142,10 @@ function ModelRow:_validateField(columnData) if not self.fields[columnData.name] then error(self.tableName .. ' expects ' .. columnData.name .. ' to be set') end - -- TODO: Verify types (at least when running tests) + -- Verify types (at least when running tests) + TypeUtil.assertValue( + self.fields[columnData.name], columnData.fieldType, {name = self.tableName .. '.' .. columnData.name} + ) end ---@private From db73f52dd1794588612ba03c3a3a59e02b0a2d1a Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:36:58 +0900 Subject: [PATCH 05/13] hide typechecker behind feature flag --- lua/wikis/commons/Lpdb.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/Lpdb.lua b/lua/wikis/commons/Lpdb.lua index f84b978a34b..ddccb3d2496 100644 --- a/lua/wikis/commons/Lpdb.lua +++ b/lua/wikis/commons/Lpdb.lua @@ -9,6 +9,7 @@ local Lua = require('Module:Lua') local Array = Lua.import('Module:Array') local Class = Lua.import('Module:Class') +local FeatureFlag = Lua.import('Module:FeatureFlag') local FnUtil = Lua.import('Module:FnUtil') local Logic = Lua.import('Module:Logic') local Opponent = Lua.import('Module:Opponent/Custom') @@ -142,7 +143,11 @@ function ModelRow:_validateField(columnData) if not self.fields[columnData.name] then error(self.tableName .. ' expects ' .. columnData.name .. ' to be set') end - -- Verify types (at least when running tests) + + local typeCheckFeature = FeatureFlag.get('force_type_check') + if not typeCheckFeature then + return + end TypeUtil.assertValue( self.fields[columnData.name], columnData.fieldType, {name = self.tableName .. '.' .. columnData.name} ) From e2678fad087d42d32bdf0febc5aaf926ea32a072 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Wed, 10 Jun 2026 09:37:40 +0900 Subject: [PATCH 06/13] enable typechecking in match2 test --- lua/spec/match2_spec.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lua/spec/match2_spec.lua b/lua/spec/match2_spec.lua index 3dd7e0c0e14..0e33e5d2a06 100644 --- a/lua/spec/match2_spec.lua +++ b/lua/spec/match2_spec.lua @@ -2,8 +2,11 @@ describe('match2', function() local tournamentData = require('test_assets.tournaments').dummy insulate('matchlist', function() + local FeatureFlag = require('Module:FeatureFlag') local Json = require('Module:Json') + before_each(function () + FeatureFlag.set('force_type_check', true) local dataSaved, dataSavedOpponent, dataSavedPlayer, dataSavedGame = {}, {}, {}, {} -- Mock the lpdb functions stub(mw.ext.LiquipediaDB, 'lpdb_match2', function (objName, data) From e7b7d67a8a162d5aab6447fd3aff946e71853eb7 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:22:09 +0900 Subject: [PATCH 07/13] remove unused import --- lua/wikis/commons/Opponent.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/wikis/commons/Opponent.lua b/lua/wikis/commons/Opponent.lua index bb975c08df0..c5e0cf5c844 100644 --- a/lua/wikis/commons/Opponent.lua +++ b/lua/wikis/commons/Opponent.lua @@ -15,7 +15,6 @@ local Logic = Lua.import('Module:Logic') local Page = Lua.import('Module:Page') local PlayerExt = Lua.import('Module:Player/Ext/Custom') local String = Lua.import('Module:StringUtils') -local Table = Lua.import('Module:Table') local TeamTemplate = Lua.import('Module:TeamTemplate') local TypeUtil = Lua.import('Module:TypeUtil') From 0cd8a1be1d0690b8cb90b63917b833aaa6107360 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:23:53 +0900 Subject: [PATCH 08/13] adjust Opponent.isType --- lua/wikis/commons/Opponent.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/Opponent.lua b/lua/wikis/commons/Opponent.lua index c5e0cf5c844..9ff61e7354a 100644 --- a/lua/wikis/commons/Opponent.lua +++ b/lua/wikis/commons/Opponent.lua @@ -209,7 +209,7 @@ end ---@param type string ---@return boolean function Opponent.isType(type) - return #TypeUtil.checkValue(type, Opponent.types) == 0 + return #TypeUtil.checkValue(type, Opponent.types.OpponentType) == 0 end ---Reads an opponent type. From 1226085138851e8aab4b8f9e1d26f217385223d6 Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:28:18 +0900 Subject: [PATCH 09/13] adjust date field type --- lua/wikis/commons/Lpdb.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Lpdb.lua b/lua/wikis/commons/Lpdb.lua index ddccb3d2496..75b3878ad98 100644 --- a/lua/wikis/commons/Lpdb.lua +++ b/lua/wikis/commons/Lpdb.lua @@ -240,7 +240,7 @@ Lpdb.Match2 = Model('match2', { {name = 'section', fieldType = 'string', default = ''}, {name = 'game', fieldType = 'string', default = ''}, {name = 'patch', fieldType = 'string', default = ''}, - {name = 'date', fieldType = 'string', default = 0}, + {name = 'date', fieldType = TypeUtil.union('string', 'integer'), default = 0}, {name = 'dateexact', fieldType = 'number', default = 0}, {name = 'stream', fieldType = 'table', default = {}}, {name = 'links', fieldType = 'table', default = {}}, @@ -270,7 +270,7 @@ Lpdb.Placement = Model('placement', { {name = 'parent', fieldType = 'pagename', default = ''}, {name = 'shortname', fieldType = 'string', default = ''}, {name = 'startdate', fieldType = 'string', default = 0}, - {name = 'date', fieldType = 'string', default = 0}, + {name = 'date', fieldType = TypeUtil.union('string', 'integer'), default = 0}, {name = 'placement', fieldType = 'string', default = ''}, {name = 'prizemoney', fieldType = 'number', default = 0}, {name = 'individualprizemoney', fieldType = 'number', default = 0}, From 73c97e72b26abd9ff66b790956079d9efcc6525b Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:35:55 +0900 Subject: [PATCH 10/13] adjust tests --- lua/spec/match2_spec.lua | 2 -- lua/spec/orm_spec.lua | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lua/spec/match2_spec.lua b/lua/spec/match2_spec.lua index 0e33e5d2a06..218835f98d1 100644 --- a/lua/spec/match2_spec.lua +++ b/lua/spec/match2_spec.lua @@ -2,11 +2,9 @@ describe('match2', function() local tournamentData = require('test_assets.tournaments').dummy insulate('matchlist', function() - local FeatureFlag = require('Module:FeatureFlag') local Json = require('Module:Json') before_each(function () - FeatureFlag.set('force_type_check', true) local dataSaved, dataSavedOpponent, dataSavedPlayer, dataSavedGame = {}, {}, {}, {} -- Mock the lpdb functions stub(mw.ext.LiquipediaDB, 'lpdb_match2', function (objName, data) diff --git a/lua/spec/orm_spec.lua b/lua/spec/orm_spec.lua index a39de6abd09..5a485614e7f 100644 --- a/lua/spec/orm_spec.lua +++ b/lua/spec/orm_spec.lua @@ -1,7 +1,16 @@ --- Triple Comment to Enable our LLS Plugin describe('LPDB Object-Relational Mapping', function() + local FeatureFlag = require('Module:FeatureFlag') local Lpdb = require('Module:Lpdb') + before_each(function () + FeatureFlag.set('force_type_check', true) + end) + + after_each(function () + FeatureFlag.set('force_type_check', false) + end) + describe('setting data', function() it('assign value on init', function() local match2 = Lpdb.Match2:new({bestof = 10}) From 09cd58bc1f2a59be26498a184e14bf494324738d Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:37:28 +0900 Subject: [PATCH 11/13] add dummy parent --- lua/spec/orm_spec.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/spec/orm_spec.lua b/lua/spec/orm_spec.lua index 5a485614e7f..298dc64128a 100644 --- a/lua/spec/orm_spec.lua +++ b/lua/spec/orm_spec.lua @@ -42,7 +42,7 @@ describe('LPDB Object-Relational Mapping', function() describe('saving data', function() it('saving', function() local stub = stub(mw.ext.LiquipediaDB, 'lpdb_match2') - Lpdb.Match2:new({match2id = 'Foo', match2bracketid = 'Bar', bestof = 3, game = 'r6s'}):save() + Lpdb.Match2:new({match2id = 'Foo', match2bracketid = 'Bar', bestof = 3, game = 'r6s', parent = 'DummyPage'}):save() assert.stub(stub).called_with('Foo', { bestof = 3, date = 0, @@ -61,7 +61,7 @@ describe('LPDB Object-Relational Mapping', function() match2id = 'Foo', match2opponents = {}, mode = '', - parent = '', + parent = 'DummyPage', patch = '', publishertier = '', section = '', From 04d53100037b174c09b00714d355bf2e5c03a9fc Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:46:59 +0900 Subject: [PATCH 12/13] make array type checks more strict --- lua/wikis/commons/TypeUtil.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/TypeUtil.lua b/lua/wikis/commons/TypeUtil.lua index 99bcc0e6fda..b2a32f823f7 100644 --- a/lua/wikis/commons/TypeUtil.lua +++ b/lua/wikis/commons/TypeUtil.lua @@ -136,8 +136,7 @@ function TypeUtil.table (keyType, valueType) end --[[ -Type for tables that are arrays. Not strict - arrays may have additional fields -besides numeric indexes, and may have gaps in indexes. +Type for tables that are arrays. ]] ---@param elemType TypeUtilType ---@return TypeUtilArrayType @@ -194,7 +193,9 @@ function TypeUtil.valueIsTypeNoTable (value, typeSpec) typeSpec.types, function(t) return TypeUtil.valueIsTypeNoTable(value, t) end ) - elseif typeSpec.op == 'table' or typeSpec.op == 'struct' or typeSpec.op == 'array' then + elseif typeSpec.op == 'array' then + return Array.isArray(value) + elseif typeSpec.op == 'table' or typeSpec.op == 'struct' then return type(value) == 'table' end end From 5bd46e797960e6c08a23ec492672368396029d8e Mon Sep 17 00:00:00 2001 From: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:47:51 +0900 Subject: [PATCH 13/13] adjust order --- lua/wikis/commons/TypeUtil.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/TypeUtil.lua b/lua/wikis/commons/TypeUtil.lua index b2a32f823f7..873ca70eba3 100644 --- a/lua/wikis/commons/TypeUtil.lua +++ b/lua/wikis/commons/TypeUtil.lua @@ -193,10 +193,10 @@ function TypeUtil.valueIsTypeNoTable (value, typeSpec) typeSpec.types, function(t) return TypeUtil.valueIsTypeNoTable(value, t) end ) - elseif typeSpec.op == 'array' then - return Array.isArray(value) elseif typeSpec.op == 'table' or typeSpec.op == 'struct' then return type(value) == 'table' + elseif typeSpec.op == 'array' then + return Array.isArray(value) end end return true