Module:Validate gadgets

local MessageBox = require('Module:Message box')

local Gadgets = require('Module:Gadgets')

local p = {}

local function arr_contains(array, val)

for _, value in ipairs(array) do

if value == val then

return true

end

end

return false

end

-- Lists of valid options for things that aren't exposed to lua

-- (unlike namespaces that can be accessed from mw.site.namespaces)

local VALID_CONTENT_MODELS = {'wikitext', 'javascript', 'css', 'json', 'MassMessageListContent', 'Scribunto', 'sanitized-css'}

p.validate = function (frame)

local text = mw.title.new('MediaWiki:Gadgets-definition'):getContent()

local lines = mw.text.split(text, '\n', false)

local repo = {}

local allWarnings = {}

-- A bit of parsing is reimplemented here as Module:Gadgets doesn't raise warnings

-- for invalid lines

for _, line in ipairs(lines) do

if line:sub(1, 1) == '*' then

local name, options, pages = Gadgets.parse_line(line)

if not name or #pages == 0 then

table.insert(allWarnings, '* Invalid definition: '..line)

else

repo[name] = { options = options, pages = pages }

end

end

end

for name, conf in pairs(repo) do

local warnings = p.create_warnings(name, conf.options, conf.pages, repo)

for _, warning in ipairs(warnings) do

table.insert(allWarnings, '*'..name..': '..warning)

end

end

if #allWarnings ~= 0 then

return MessageBox.main('ombox', {

text = 'Issues in gadget definitions:\n' .. table.concat(allWarnings, '\n'),

type = 'delete',

class = 'gadgets-validation'

})

elseif require('Module:If preview/configuration').preview then

return MessageBox.main('ombox', {

text = 'Issues in gadget definitions: No issues found!',

type = 'notice',

image = '30px',

class = 'gadgets-validation'

})

else

return ''

end

end

p.create_warnings = function(name, options, pages, repo)

local warnings = {}

-- RL module name (ext.gadget.) should not exceed 255 bytes

-- so a limit of 255 - 11 = 244 bytes for gadget name

if string.len(name) > 244 then

table.insert(warnings, 'Gadget name must not exceed 244 bytes')

end

-- Per ResourceLoader::isValidModuleName

if name:gsub('[|,!]', '') ~= name then

table.insert(warnings, 'Gadget name must not contain pipes (|), commas (,) or exclamation marks (!)')

end

-- Pattern per MediaWikiGadgetDefinitionsRepo::newFromDefinition

if not string.match(name, "^[a-zA-Z][-_:%.%w ]*[a-zA-Z0-9]?$") then

table.insert(warnings, 'Gadget name is used as part of the name of a form field, and must follow the rules defined in https://www.w3.org/TR/html4/types.html#type-cdata')

end

if options.type ~= nil and options.type ~= 'general' and options.type ~= 'styles' then

table.insert(warnings, 'Allowed values for type are: general, styles')

end

if options.targets ~= nil then

table.insert(warnings, 'Setting targets in gadget defintion is deprecated and no longer has any effect')

end

if options.namespaces ~= nil then

for _, id in ipairs(mw.text.split(options.namespaces, ',', false)) do

if not string.match(id, '^-?%d+$') then

table.insert(warnings, 'Invalid namespace id: '..id..' - must be numeric')

elseif mw.site.namespaces[tonumber(id)] == nil then

table.insert(warnings, 'Namespace id '..id..' is invalid')

end

end

end

if options.actions ~= nil then

for _, action in ipairs(mw.text.split(options.actions, ',', false)) do

if not mw.message.new('action-' .. action):exists() then

table.insert(warnings, 'Action '..action..' is unrecognised')

end

end

end

if options.contentModels ~= nil then

for _, model in ipairs(mw.text.split(options.contentModels, ',', false)) do

if not arr_contains(VALID_CONTENT_MODELS, model) then

table.insert(warnings, 'Content model '..model..' is unrecognised')

end

end

end

if options.skins ~= nil then

for _, skin in ipairs(mw.text.split(options.skins, ',', false)) do

if not mw.message.new('skinname-' .. skin):exists() then

table.insert(warnings, 'Skin '..skin..' is not available')

end

end

end

if options.rights ~= nil then

for _, right in ipairs(mw.text.split(options.rights, ',', false)) do

if not mw.message.new('right-' .. right):exists() then

table.insert(warnings, 'User right '..right..' does not exist')

end

end

end

local scripts = {}

local styles = {}

local jsons = {}

for _, page in ipairs(pages) do

page = 'MediaWiki:Gadget-' .. page

local title = mw.title.new(page)

if title == nil or not title.exists then

table.insert(warnings, 'Page '..page..' does not exist')

else

local ext = title.text:match("%.([^%.]+)$")

if ext == 'js' then

if title.contentModel ~= 'javascript' then

table.insert(warnings, 'Page '..page..' is not of JavaScript content model')

else

table.insert(scripts, page)

end

elseif ext == 'css' then

if title.contentModel ~= 'css' then

table.insert(warnings, 'Page '..page..' is not of CSS content model')

else

table.insert(styles, page)

end

elseif ext == 'json' then

if title.contentModel ~= 'json' then

table.insert(warnings, 'Page '..page..' is not of JSON content model')

else

table.insert(jsons, page)

end

else

table.insert(warnings, 'Page '..page..' is not JS/CSS/JSON, will be ignored')

end

end

end

if not options.hidden then

local description_page = mw.title.new('MediaWiki:Gadget-'..name)

if description_page == nil or not description_page.exists then

table.insert(warnings, 'Description '..description_page.fullText..' for use in Special:Preferences does not exist')

end

end

if options.package == nil and #jsons > 0 then

table.insert(warnings, 'JSON pages cannot be used in non-package gadgets')

end

if options.requiresES6 ~= nil and options.default ~= nil then

table.insert(warnings, 'Default gadget cannot use requiresES6 flag')

end

if options.type == 'styles' and #scripts > 0 then

table.insert(warnings, 'JS pages will be ignored as gadget sets type=styles')

end

if options.type == 'styles' and options.peers ~= nil then

table.insert(warnings, 'Styles-only gadget cannot have peers')

end

if options.type == 'styles' and options.dependencies ~= nil then

table.insert(warnings, 'Styles-only gadget cannot have dependencies')

end

if options.package ~= nil and #scripts == 0 then

table.insert(warnings, 'Package gadget must have at least one JS page')

end

if options.ResourceLoader == nil and #scripts > 0 then

table.insert(warnings, 'ResourceLoader option must be set')

end

-- Causes warnings on styles-only gadgets using skins param

-- if options.hidden ~= nil and (options.namespaces ~= nil or options.actions ~= nil or options.rights ~= nil or options.contentModels ~= nil or options.skins ~= nil) then

-- table.insert(warnings, 'Conditional load options are not applicable for hidden gadget')

-- end

if options.peers ~= nil then

for _, peer in ipairs(mw.text.split(options.peers, ',', false)) do

if repo[peer] == nil then

table.insert(warnings, 'Peer gadget '..peer..' is not defined')

elseif Gadgets.get_type(repo[peer]) == 'general' then

table.insert(warnings, 'Peer gadget '..peer..' must be styles-only gadget')

end

end

end

if options.dependencies ~= nil then

for _, dep in ipairs(mw.text.split(options.dependencies, ',', false)) do

if dep:sub(1, 11) == 'ext.gadget.' then

local dep_gadget = dep:sub(12)

if repo[dep_gadget] == nil then

table.insert(warnings, 'Dependency gadget '..dep_gadget..' is not defined')

end

end

end

end

return warnings

end

return p