Module:checkparams: difference between revisions

From Wiktionary, the free dictionary
Jump to navigation Jump to search
Content deleted Content added
0 and negative numbers shouldn't be allowed as placeholders.
Give warnings/errors which format the parameter list in <pre> tags, because otherwise erroneous whitespace in inputs is much harder to debug.
Line 3: Line 3:
local find_arguments = require("Module:template parser").findArguments
local find_arguments = require("Module:template parser").findArguments
local format_categories = require("Module:utilities").format_categories
local format_categories = require("Module:utilities").format_categories
local gsub = string.gsub
local html_create = mw.html.create
local html_create = mw.html.create
local insert = table.insert
local insert = table.insert
Line 25: Line 26:
local function get_template_content(frame)
local function get_template_content(frame)
return new_title(get_template_title(frame)):getContent()
return new_title(get_template_title(frame)):getContent()
end

local function sort_args(a, b)
-- sort a table of parameters in the form {k, v}, sorting numbers before strings
a, b = a[1], b[1]
local type_a = type(a)
if type_a == type(b) then
return a < b
end
return type_a == "number"
end

local function params_to_string(invalid_args)
-- join a table of key, values pairs into a semicolon separated list
local msg = {}
for k, v in pairs(invalid_args) do
insert(msg, {k, v})
end
sort(msg, sort_args)
for i = 1, #msg do
msg[i] = concat(msg[i], "=")
end
return concat(msg, "; ")
end
end


Line 74: Line 52:
end
end


local function make_message(template_name, invalid_args, no_link, no_escape)
local function compare_params(a, b)
a, b = a[1], b[1]
local invalid_args_string = params_to_string(invalid_args)
local type_a = type(a)
if not no_escape then
if type_a == type(b) then
invalid_args_string = nowiki(invalid_args_string)
return a < b
end
end
return type_a == "number"
end

local function sort_params(invalid_args)
-- sort a table of parameters in the form {k, v}, sorting numbers before strings
local msg = {}
for k, v in pairs(invalid_args) do
insert(msg, {k, v})
end
sort(msg, compare_params)
for i = 1, #msg do
msg[i] = concat(msg[i], "=")
end
return msg
end

local function apply_pre_tag(frame, invalid_args)
return frame:extensionTag("pre", concat(invalid_args, "\n"))
end

local function make_message(template_name, invalid_args, no_link)
local open = no_link and "" or "[["
local open = no_link and "" or "[["
local close = no_link and "" or "]]"
local close = no_link and "" or "]]"
return "The template " .. open .. template_name .. close .. " does not use the parameter(s): " .. invalid_args_string .. ". Please see " .. open .. "Module:checkparams" .. close .. " for help with this warning."
return "The template " .. open .. template_name .. close .. " does not use the parameter(s): " .. invalid_args .. " Please see " .. open .. "Module:checkparams" .. close .. " for help with this warning."
end
end


local function make_warning_text(template_name, invalid_args, nowarn, noattn, nocat)
local function make_warning_text(frame, template_name, invalid_args, nowarn, noattn, nocat)
-- generate "Invalid params" warning to be inserted into wiki page
-- generate "Invalid params" warning to be inserted into wiki page
-- template_name (required) name of the template with invalid params
-- template_name (required) name of the template with invalid params
Line 100: Line 99:
:addClass("previewonly")
:addClass("previewonly")
:tag("small")
:tag("small")
:wikitext(make_message(template_name, invalid_args))
:wikitext(make_message(template_name, apply_pre_tag(frame, invalid_args)))
:allDone())
:allDone())
end
end
-- add attentionseeking message
-- add attentionseeking message
-- <pre> tags don't work in HTML attributes, so use semicolons as delimiters
if not noattn then
if not noattn then
attn = tostring(html_create("span")
attn = tostring(html_create("span")
:addClass("attentionseeking")
:addClass("attentionseeking")
:attr("title", make_message(template_name, invalid_args, "no_link"))
:attr("title", make_message(template_name, concat(invalid_args, "; ") .. ".", "no_link"))
:allDone())
:allDone())
end
end
Line 169: Line 169:
if next(invalid_args) then
if next(invalid_args) then
local template_name = frame:getTitle()
local template_name = frame:getTitle()
return valid_args, invalid_args, make_warning_text(template_name, invalid_args, nowarn, noattn, nocat)
return valid_args, invalid_args, make_warning_text(frame, template_name, sort_params(invalid_args), nowarn, noattn, nocat)
end
end
Line 207: Line 207:
end
end
-- Throw an error via callParserFunction, which generates a real error with traceback, automatic categorization in [[CAT:E]] etc., but the error message is returned as a string. Then, replace the placeholder error message with an error containing <pre> tags.
-- This is necessary because <pre> tags don't work in conventional error messages, so whitespace-related issues can't be shown accurately. Any whitespace which isn't trimmed is treated as literal, so errors caused by double-spaces or erroneous newlines in inputs need to be displayed accurately.
if next(invalid_args) then
if next(invalid_args) then
local template_name = get_template_title(frame)
local message = make_message(get_template_title(frame), apply_pre_tag(frame, sort_params(invalid_args)))
local err = frame:callParserFunction("#invoke", "checkparams", "throw_error")
error(make_message(template_name, invalid_args, "no_link", "no_escape"))
return (gsub(err, "_message_", message))
end
end
end

function export.throw_error(frame)
-- A dummy function that throws an error with a placeholder, which will be replaced with the actual error message
error("_message_", 0)
end
end



Revision as of 18:33, 5 April 2024


Editor info

If you're seen a warning that "The template does not use the parameter(s)" while editing a page, the error was generated by this module. This module can detect all of the parameters that are used by a template and will generate a warning when a template is called with an unexpected parameter. In many cases, the error might be generated by a typo in the parameter name (eg, "pge" instead of "page") and can simply be corrected by consulting the template's documentation.

If you're absolutely certain that a template should allow the parameter even though it's not going to actually use the parameter, you can override the automatic parameter detection by adding | plus a comma separated list of additional parameters that should be accepted to the {{#invoke:checkparams|warn}} or the {{#invoke:checkparams|error}} line at the top of the template.

For example, to allow parameters "3" and "comment" in the following template

{{#invoke:checkparams|warn}}<!-- Validate template parameters
-->Written by {{{1}}} and {{{2}}} in {{{year}}}

change it to:

{{#invoke:checkparams|warn|3,comment}}<!-- Validate template parameters
The parameters '3' and 'comment' are not used, but are explicitly allowed because ....
-->Written by {{{1}}} and {{{2}}} in {{{year}}}

Developer information

This module generates warnings when passing invalid parameters to a template. It adds a user-visible warning to the preview, an {{attention}} warning to the page, and categorizes the page into [[Category:Pages using invalid parameters when calling templates]].

Parameters

|1=
A comma separated list of additional allowed parameters, in addition to those automatically detected by the module.
|nocat= (optional)
Suppress categorization.

local capturing_split = require("Module:string/capturing split")
local concat = table.concat
local find_arguments = require("Module:template parser").findArguments
local format_categories = require("Module:utilities").format_categories
local gsub = string.gsub
local html_create = mw.html.create
local insert = table.insert
local match = string.match
local new_title = mw.title.new
local next = next
local nowiki = require("Module:string/nowiki")
local pairs = pairs
local process = require("Module:parameters").process
local scribunto_param_key = require("Module:utilities/scribunto parameter key")
local select = select
local sort = table.sort
local tostring = tostring
local type = type

local export = {}

local function get_template_title(frame)
	return frame:getParent():getTitle()
end

local function get_template_content(frame)
	return new_title(get_template_title(frame)):getContent()
end

local function get_allowed_args(frame)
	-- returns a table of allowed args
	
	local args = {}
	-- first, detect all args used by the parent template
	-- find_arguments takes the parent argument table as the second parameter so that argument names can be preprocessed, since these can vary based on the input if an argument name contains another argument (e.g. {{{foo{{{bar}}}baz}}})
	for arg in find_arguments(get_template_content(frame), frame:getParent().args) do
		args[arg] = {}
	end
	
	-- if frame.args[1] contains a comma separated list of param names, add those as well
	local additional = frame.args[1]
	if additional then
		additional = capturing_split(additional, ",")
		for i = 1, #additional do
			-- scribunto_param_key normalizes the arg into the form returned by find_arguments (i.e. trimmed and converted to a number if appropriate)
			local arg = scribunto_param_key(additional[i])
			args[arg] = args[arg] or {}
		end
	end
	
	return args
end

local function compare_params(a, b)
	a, b = a[1], b[1]
	local type_a = type(a)
	if type_a == type(b) then
		return a < b
	end
	return type_a == "number"
end

local function sort_params(invalid_args)
	-- sort a table of parameters in the form {k, v}, sorting numbers before strings
	local msg = {}
	for k, v in pairs(invalid_args) do
		insert(msg, {k, v})
	end
	sort(msg, compare_params)
	for i = 1, #msg do
		msg[i] = concat(msg[i], "=")
	end
	return msg
end

local function apply_pre_tag(frame, invalid_args)
	return frame:extensionTag("pre", concat(invalid_args, "\n"))
end

local function make_message(template_name, invalid_args, no_link)	
	local open = no_link and "" or "[["
	local close = no_link and "" or "]]"
	return "The template " .. open .. template_name .. close .. " does not use the parameter(s): " .. invalid_args .. " Please see " .. open .. "Module:checkparams" .. close .. " for help with this warning."
end

local function make_warning_text(frame, template_name, invalid_args, nowarn, noattn, nocat)
	-- generate "Invalid params" warning to be inserted into wiki page
	-- template_name (required) name of the template with invalid params
	-- invalid_args (required) table of invalid args
	-- nocat= (optional) do not included category in warning_text
	-- noattn= (optional) do not include attention seeking span in in warning_text
	-- nowarn= (optional) do not include preview warning in warning_text
	local warn, attn, cat
	
	-- show warning in previewer
	if not nowarn then
		warn = tostring(html_create("sup")
			:addClass("error")
			:addClass("previewonly")
			:tag("small")
				:wikitext(make_message(template_name, apply_pre_tag(frame, invalid_args)))
			:allDone())
	end
	
	-- add attentionseeking message
	-- <pre> tags don't work in HTML attributes, so use semicolons as delimiters
	if not noattn then
		attn = tostring(html_create("span")
			:addClass("attentionseeking")
			:attr("title", make_message(template_name, concat(invalid_args, "; ") .. ".", "no_link"))
			:allDone())
	end
	
	if not nocat then
		cat = format_categories(
			{"Pages using bad params when calling " .. template_name},
			nil,
			"-"
		)
	end
	
	return (warn or "") .. (attn or "") .. (cat or "") 
end

local function remove_placeholder_args(allowed_params, invalid_args)
	-- some templates use params 1 and 3 without using 2,
	-- which means that 2 will be in the list of invalid args
	-- when used as an empty placeholder like in {{foo|1||3}}
	-- this function will detect and remove any empty positionaly placeholder args
	-- from a list of invalid_args
	local max_pos = 0
	for param in pairs(allowed_params) do
		if type(param) == "number" and param > max_pos then
			max_pos = param
		end
	end
	
	for param in pairs(invalid_args) do
		if (
			type(param) == "number" and
			param >= 1 and
			param < max_pos and
			not match(invalid_args[param], "[^%z\t-\v\r ]") -- charset used by MediaWiki to trim parameters
		) then
			invalid_args[param] = nil
		end
	end
end


function export.process(frame, allowed_params, nowarn, noattn, nocat)
	-- This is desgined to be called by other Lua modules instead of calling Module:parameters.process()
	-- frame - the frame containing the arguments to be checked
	-- allowed_params - a table of valid arguments
	-- nocat - if specified, will not included category in warning_text
	-- noattn - if specified, will not include attention seeking span in in warning_text
	-- nowarn - if specified, will not include preview warning in warning_text
	-- returns valid_args, invalid_args, warning_text
	local valid_args, invalid_args = process(
		frame.args,
		allowed_params,
		"return unknown"
	)
	
	if next(invalid_args) then
		remove_placeholder_args(allowed_params, invalid_args)
	end
	
	if next(invalid_args) then
		local template_name = frame:getTitle()
		return valid_args, invalid_args, make_warning_text(frame, template_name, sort_params(invalid_args), nowarn, noattn, nocat)
	end
	
	return valid_args, invalid_args, ""
end

function export.warn(frame)
	-- This is designed to be called by non-Lua templates using "{{#invoke:checkparams|warn}}"
	-- the passed frame is checked for the following params:
	-- 1= (optional) a comma separated list of allowed parameters - if not specified, allows all parameters used as variables inside the template
	-- nowarn= (optional) do not include preview warning in warning_text
	-- noattn= (optional) do not include attention seeking span in in warning_text
	-- nocat= (optional) do not included category in warning_text
	local args = frame.args
	return select(3, export.process(
		frame:getParent(),
		get_allowed_args(frame),
		args.nowarn or false,
		args.noattn or false,
		args.nocat or false
	))
end

function export.error(frame)
	-- This is designed to be called by non-Lua templates using "{{#invoke:checkparams|error}}"
	-- the passed frame is checked for the following params:
	-- 1= (optional) a comma separated list of allowed parameters - if not specified, allows all parameters used as variables inside the template
	local allowed_args = get_allowed_args(frame)
	local invalid_args = select(2, process(
		frame:getParent().args,
		allowed_args,
		"return unknown"
	))
	
	if next(invalid_args) then
		remove_placeholder_args(allowed_args, invalid_args)
	end
	
	-- Throw an error via callParserFunction, which generates a real error with traceback, automatic categorization in [[CAT:E]] etc., but the error message is returned as a string. Then, replace the placeholder error message with an error containing <pre> tags.
	-- This is necessary because <pre> tags don't work in conventional error messages, so whitespace-related issues can't be shown accurately. Any whitespace which isn't trimmed is treated as literal, so errors caused by double-spaces or erroneous newlines in inputs need to be displayed accurately.
	if next(invalid_args) then
		local message = make_message(get_template_title(frame), apply_pre_tag(frame, sort_params(invalid_args)))
		local err = frame:callParserFunction("#invoke", "checkparams", "throw_error")
		return (gsub(err, "_message_", message))
	end
	
end

function export.throw_error(frame)
	-- A dummy function that throws an error with a placeholder, which will be replaced with the actual error message
	error("_message_", 0)
end

return export