Module:checkparams

From Wiktionary, the free dictionary
Jump to navigation Jump to search

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 export = {}

local debug_module = "Module:debug"
local maintenance_category_module = "Module:maintenance category"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local template_parser_module = "Module:template parser"
local utilities_module = "Module:utilities"

local concat = table.concat
local get_current_title = mw.title.getCurrentTitle
local html_create = mw.html.create
local match = string.match
local new_title = mw.title.new
local next = next
local pairs = pairs
local require = require
local select = select
local sort = table.sort
local tostring = tostring
local type = type

--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
	local function find_parameters(...)
		find_parameters = require(template_parser_module).find_parameters
		return find_parameters(...)
	end
	
	local function format_categories(...)
		format_categories = require(utilities_module).format_categories
		return format_categories(...)
	end
	
	local function formatted_error(...)
		formatted_error = require(debug_module).formatted_error
		return formatted_error(...)
	end
	
	local function gsplit(...)
		gsplit = require(string_utilities_module).gsplit
		return gsplit(...)
	end
	
	local function process_params(...)
		process_params = require(parameters_module).process
		return process_params(...)
	end
	
	local function scribunto_param_key(...)
		scribunto_param_key = require(string_utilities_module).scribunto_param_key
		return scribunto_param_key(...)
	end
	
	local function uses_hidden_category(...)
		uses_hidden_category = require(maintenance_category_module).uses_hidden_category
		return uses_hidden_category(...)
	end

-- Returns a table of all arguments in `template_args` which are not supported
-- by `template_title` or listed in `additional`.
local function get_invalid_args(template_title, template_args, additional)
	local content = template_title:getContent()
	if not content then
		-- This should only be possible if the input frame has been tampered with.
		error("Could not retrieve the page content of \"" .. template_title.prefixedText .. "\".")
	end
	local allowed_params, seen = {}, {}
	-- Detect all params used by the parent template. param:get_name() takes the
	-- parent frame arg table as an argument so that preprocessing will take
	-- them into account, since it will matter if the name contains another
	-- parameter (e.g. the outer param in "{{{foo{{{bar}}}baz}}}" will change
	-- depending on the value for bar=). `seen` memoizes results based on the
	-- raw parameter text (which is stored as a string in the parameter object),
	-- which avoids unnecessary param:get_name() calls, which are non-trivial.
	for param in find_parameters(content) do
		local raw = param.raw
		if not seen[raw] then
			allowed_params[param:get_name(template_args)] = true
			seen[raw] = true
		end
	end
	
	-- If frame.args[1] contains a comma separated list of param names, add
	-- those as well.
	if additional then
		for param in gsplit(additional, ",", true) do
			-- scribunto_param_key normalizes the param into the form returned
			-- by param:get_name() (i.e. trimmed and converted to a number if
			-- appropriate).
			allowed_params[scribunto_param_key(param)] = true
		end
	end
	
	local invalid_args = select(2, process_params(
		template_args,
		allowed_params,
		"return unknown"
	))
	
	if not next(invalid_args) then
		return invalid_args
	end
	
	-- 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
	-- (e.g. {{foo|foo||bar}}). Detect and remove any empty positional
	-- placeholder 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, arg in pairs(invalid_args) do
		if (
			type(param) == "number" and
			param >= 1 and
			param < max_pos and
			-- Ignore if arg is empty, or only contains chars trimmed by
			-- MediaWiki when handling named parameters.
			not match(arg, "[^%z\t-\v\r ]")
		) then
			invalid_args[param] = nil
		end
	end
	
	return invalid_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

-- Convert `args` into an array of sorted PARAM=ARG strings, using the parameter
-- name as the sortkey, with numbered params sorted before strings.
local function args_to_sorted_tuples(args)
	local msg, i = {}, 0
	for k, v in pairs(args) do
		i = i + 1
		msg[i] = {k, v}
	end
	sort(msg, compare_params)
	for j = 1, i do
		msg[j] = concat(msg[j], "=")
	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, close
	if no_link then
		open, close = "", ""
	else
		open, close = "[[", "]]"
	end
	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

-- Called by non-Lua templates using "{{#invoke:checkparams|warn}}". `frame`
-- is checked for the following params:
-- `1=` (optional) a comma separated list of additional allowed parameters
-- `nowarn=` (optional) do not include preview warning in warning_text
-- `noattn=` (optional) do not include attention seeking span in in warning_text
function export.warn(frame)
	local parent, frame_args = frame:getParent(), frame.args
	local template_name = parent:getTitle()
	local template_title = new_title(template_name)
	
	local invalid_args = get_invalid_args(template_title, parent.args, frame_args[1])
	
	-- If there are no invalid template args, return.
	if not next(invalid_args) then
		return ""
	end
	
	-- Otherwise, generate "Invalid params" warning to be inserted onto the
	-- wiki page.
	local warn, attn, cat
	invalid_args = args_to_sorted_tuples(invalid_args)
	
	-- Show warning in previewer.
	if not frame_args.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 frame_args.noattn then
		attn = tostring(html_create("span")
			:addClass("attentionseeking")
			:attr("title", make_message(template_name, concat(invalid_args, "; ") .. ".", "no_link"))
			:allDone())
	end
	
	-- Categorize if neither the current page nor the template would go in a hidden maintenance category.
	if not (uses_hidden_category(get_current_title()) or uses_hidden_category(template_title)) then
		cat = format_categories({"Pages using invalid parameters when calling " .. template_name}, nil, "-", nil, "force_output")
	end
	
	return (warn or "") .. (attn or "") .. (cat or "")
end

-- Called by non-Lua templates using "{{#invoke:checkparams|error}}". `frame`
-- is checked for the following params:
-- `1=` (optional) a comma separated list of additional allowed parameters
function export.error(frame)
	local parent = frame:getParent()
	local template_name = parent:getTitle()
	
	local invalid_args = get_invalid_args(new_title(template_name), parent.args, frame.args[1])
	
	-- Use formatted_error, so that we can use <pre> tags in error messages:
	-- 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
		return formatted_error(make_message(
			template_name,
			apply_pre_tag(frame, args_to_sorted_tuples(invalid_args))
		))
	end
end

return export