Edge Rewrite
// HTMLRewriter · presentation

This page was redesigned at the edge.

Cloudflare fetched the original article and streamed it through HTMLRewriter to apply an entirely new visual system without rebuilding the source page.

Jump to content

Module:DYK queue formatting check

From Wikipedia, the free encyclopedia

-- This module started out verifying punctuation rules unique to [[WP:DYKMOS]]
-- Then it expanded to include the remaining non-subjective part of
-- [[WP:DYKMOS]] and [[MOS:CQ]] to keep formatting errors from [[WP:MP/E]]

-- If there's a false positive with a hook and the warning is too annoying,
-- the current method is to just disableAll.

-- This module doesn't show up on the main page or prevent promotion,
-- but if a problem arises:
local disableAll = false
-- This module is not [[WP:EXPENSIVE]], but if performance becomes a problem:
local disableTransclusionBasedChecks = false
-- People don't seem to care about [[MOS:SOB]]. [[Special:Diff/1282220073]]
-- could be edited but [[Special:Diff/1281992817]] might be intentional
local disableSeaOfBlueCheck = true

local p = {}
local plainText = require("Module:Plain text")._main
local isDisambiguation = require('Module:Disambiguation').isDisambiguation
local getTargetFromText = require('Module:Redirect').getTargetFromText
local decode = require('Module:DecodeEncode')._decode

-- Checks that look at the destination page of a wikilink
local function check_link(page)
	if disableTransclusionBasedChecks then
		return
	end
	local title = mw.title.new(page)
	local content = title.content
	if not content then
		return "[[WP:DYKMOS]]: The hook must not contain redlinks: " .. page
	end
	local target = getTargetFromText(content)
	if target then
		return "[[WP:DYKMOS]]/[[WP:MPNOREDIRECT]]: The hook must not " ..
		"contain redirects: [[" .. page .. "]] → [[" .. target .. "]]"
	end
	if isDisambiguation(content) then
		return "[[WP:DYKMOS]]: The hook must not contain links to " ..
		"disambiguation pages: " .. page
	end
end

-- Per-hook checks that may have occasional exceptions
local function check_hook(wikitextLine, expandedLine, decodedLine, hookNum)
	if ((expandedLine:find("{{") and not wikitextLine:find("{{%("))
		or (expandedLine:find("}}") and not wikitextLine:find("{{%)"))
		or (expandedLine:find("%[%[") and not wikitextLine:find("{{!"))
		or (expandedLine:find("%]%]") and not wikitextLine:find("!}}"))) then
	    -- "code" is not ignored because some hooks use the word "codename"
	    -- Use templates or HTML alternatives from
	    -- [[Template:Escape template list]] if there's actually code in a hook.
	    return "Potentially broken wikitext"
	end
	local byteCount = -1 -- Counting ends at the question mark
	if wikitextLine:find("''%([^)]+%)''") then
		-- The eleven characters in a ''(pictured)'' tag do not count
		byteCount = byteCount - 11
	end
	local boldLinkFound = false
	for wikilink in wikitextLine:gmatch("'''%[%[([^]]+)%]%]") do
		-- Text in boldlinks after the first do not count toward the limit
		if boldLinkFound then
			wikilink = wikilink:gsub("[^|]+|", "", 1)
			byteCount = byteCount - #wikilink
		end
		boldLinkFound = true
	end
	byteCount = byteCount + #decodedLine
	if byteCount > 200 then
		return "[[WP:DYK200]]: The hook cannot exceed 200 prose characters. " ..
		"It is currently " .. byteCount .. " characters"
	elseif byteCount < 1 then
		if hookNum == 1 then
			return 'Empty first hooks are usually written as "... that ... ' ..
			"''(pictured)'' ...\""
		else
			return 'Empty hooks are usually written as "... that ..."'
		end
	end
	if hookNum > 1 then
		if wikitextLine:find("''%([^)]+%)''") then
			return "[[WP:DYKIMG]]/[[WP:DYKMOS]]: Extra ''(pictured)'' or " ..
			"alternative"
		end
	elseif hookNum < 0 then
		-- First hook might have been skipped by opt-out
	else
		if not decodedLine:find("%([^)]+%)") then
			return "[[WP:DYKIMG]]/[[WP:DYKMOS]]: Missing ''(pictured)'' or " ..
			"alternative"
		end
		-- Parentheses that are not the media marker are
		-- allowed if "absolutely unavoidable".
		-- Italic markup is not tested for because it might be complicated by
		-- adjacent bold markup.
	end
	if not disableSeaOfBlueCheck then
		if (string.find(wikitextLine, "%]%] *%[%[")
			or string.find(wikitextLine, "%]%] *'''+ *'''+ *%[%[")) then
			return "[[WP:DYKMOS]]/[[MOS:SEAOFBLUE]]: Two non-boldlinks or " ..
			"two boldlinks must be kept separate"
		end
	end
	if decodedLine:byte(-1) ~= 46 then -- Not an empty hook in prep area
		if not boldLinkFound then
			return "[[WP:DYKMOS]]: Every eligible article in the hook " ..
			"should be linked and wrapped in bold markup"
		end
		if decodedLine:byte(-1) ~= 63 then
			return "[[WP:DYKMOS]]: A hook should end in a question mark"
		end
	end
	if mw.ustring.find(decodedLine, ("[“”]")) then
		return '[[MOS:CURLY]]: Use "straight" quotation marks, not “curly” ones'
	end
	if mw.ustring.find(decodedLine, "[‘’]") then
		return "[[MOS:APOSTROPHE]]: Use straight apostrophes ('), " ..
		"not curly apostrophes (‘ or ’)"
	end
	-- Only humans can check if {{lang}} and {{transl}} are needed i.e. if
	-- the "non-English and transliterated text" is in "common English usage"
	-- [[WP:DYKMOS]] says that only ''most'' hooks begin with "that"
	-- [[MOS:CONTRACTION]] doesn't apply to quotes
end

-- General checks that should be uncontroversial for the entire page
local function general_checks(content)
	if content:find("[^[]%[//") or content:find("https?://") then
		-- Assuming nobody inserts a [[mw:Help:Links|mailto]] link
		return "[[WP:DYKMOS]]: The hook must not contain external links"
	end
	if content:find("|''[^]]+''%]") then
		return "[[WP:DYKMOS]]: Markup should go on the outside of the link " ..
		"if possible"
	end
	if content:find("%(''[^)]*pictured[^)]*''%)") then
		-- Media markers should be ''(pictured)'' like at
		-- [[Special:Diff/1287459353]] or similar,
		-- usually just with an extra word.
		-- But this shouldn't affect species, like
		-- (''Sitta pusilla'') at [[Special:Diff/1290843555]]
		return "[[WP:DYKMOS]]: Note that the italics should sit outside the "
		.. "parentheses"
	end
	if content:find("%(disambiguation%)") then
		return "[[WP:DYKMOS]]: The hook must not contain links to " ..
		"disambiguation pages"
	end
	local delinkedContent = content:gsub("%]", "")
	if (delinkedContent:find("[^']''%?")
		or delinkedContent:find("''''%?")
		or delinkedContent:find("[ ;?]%?")) then
		return "[[WP:DYKMOS]]: There should not be a space before the " ..
		"question mark, but if the text directly preceding it is " ..
		"italicized, the {{-?}} tag can offset it."
	end
	if delinkedContent:find("[^']'''{{%-%?}}") then
		return "[[WP:DYKMOS]]: (Only) if the text directly preceding it is " ..
		"italicized, the {{-?}} tag can offset it."
	end
	if delinkedContent:find("''s ") then
		return "[[WP:DYKMOS]]: Keep the bold / italic (or bold italic) text " ..
		"and the apostrophe distinct using {{`s}}/{{'s}} respectively"
	end
	if (delinkedContent:find("''''{{`") -- Use {{'}} with bold italic
		or delinkedContent:find("[^']''{{`") -- Use {{'}} with italic
		or delinkedContent:find("[^']'''{{'")) then -- Use {{`}} with bold
		-- Adapted from [[Template:Quotation mark templates]]
		return "[[WP:DYKMOS]]: {{`}} (or {{`s}}) is for adjacent bold " ..
		"markup; {{'}} (or {{'s}}) is for adjacent italic (or bold italic)"
	end
	if (delinkedContent:find("[^'] *{{'s}}")) then
		return "[[WP:DYKMOS]]: {{'s}} is only for italics"
	end
	if content:find("\n%*%.%.%.") then
		return "[[WP:DYKMOS]]: The three dots should be preceded by a space"
	end
	if content:find("\n%* %.%.%.that") then
		return "[[WP:DYKMOS]]: The three dots should be followed by a space"
	end
end

-- Checks to use for queues and complete prep areas
local function check_queue(content)
	if content:find("example%-serious%.jpg") then
		return "Image is still \"example-serious.jpg\""
	end
	if content:find("Caption goes here") then
		return "Caption is still \"Caption goes here\""
	end
	for article in content:gmatch("\n%* *%{%{DYK[^\n|]+|([^\n|]+)|") do
		article = article:lower()
		if article == "example" or article == "articlename" then
			return "[[WP:DYKPBI]]: DYKmake or DYKnom article is still " ..
			'"Example"/"ArticleName"'
		end
	end
end

-- Run all checks
-- Useful for the debug console:
-- =p._check("\n* ... that '''''[[Main Page]]''''' test ''(pictured)'' test?\n")
function p._check(content)
	local generalProblem = general_checks(content)
	if generalProblem then
		return generalProblem
	end

	local hookNum = 1
	local frame = mw.getCurrentFrame()
	-- Counting starts from after the space following the three dots
	for wikitextLine in content:gmatch("\n%* *%.%.%. *([^\n]*)") do
		wikitextLine = wikitextLine:gsub(" *%[%[File:Symbol confirmed%.svg.+$",
			"") -- {{subst:dyktick}}
		local expandedLine = frame:preprocess(wikitextLine)
		expandedLine = plainText(expandedLine, false)
		local decodedLine = decode(expandedLine)
		local hookProblem = check_hook(wikitextLine, expandedLine,
									   decodedLine, hookNum)
		if hookProblem then
			return hookProblem .. ": " .. decodedLine
		end

		for page in wikitextLine:gmatch("%[%[([^]|]+)|?[^]]*%]%]") do
			local linkProblem = check_link(page)
			if linkProblem then
				return linkProblem
			end
		end

		hookNum = hookNum + 1
	end

	-- No empty hooks ("... that ...") = queue or completed prep area
	if not content:find("\n%* %.%.%. that[^\n]*%.%.%.\n") then
		local queueProblem = check_queue(content)
		if queueProblem then
			return queueProblem
		end
	end
end

function p.main(frame)
	if disableAll then
		return
	end

	local message = p._check(mw.title.getCurrentTitle().content)
	if message then
		return frame:expandTemplate{title = "Ombox", args = {type = "style",
			text = '<p style="color:var(--color-error,#bf3c2c);font-weight:' ..
				'bold">' .. message .. "</p>" .. frame:expandTemplate{
					title = "Navbar",
					args = {
						"Module:DYK queue formatting check",
						text = "This warning module:"
					}
				}
		}}
	end
end

return p