Module:Validate gadgets
Appearance
This Lua module is used in system messages. Changes to it can cause immediate changes to the Wikipedia user interface. To avoid major disruption, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Please discuss changes on the talk page before implementing them. |
This module depends on the following other modules: |
{{#invoke:Validate gadgets|validate}}
This module checks the gadget definitions in MediaWiki:Gadgets-definition for errors and other issues.
No output is produced if there are no warnings. But during previews, a message with a green check will be shown.
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 = '<b>Issues in gadget definitions:</b>\n' .. table.concat(allWarnings, '\n'),
type = 'delete',
class = 'gadgets-validation'
})
elseif require('Module:If preview/configuration').preview then
return MessageBox.main('ombox', {
text = '<b>Issues in gadget definitions:</b> <i>No issues found!</i>',
type = 'notice',
image = '[[File:Check-green.svg|30px]]',
class = 'gadgets-validation'
})
else
return ''
end
end
p.create_warnings = function(name, options, pages, repo)
local warnings = {}
-- RL module name (ext.gadget.<name>) 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