User:Aidan9382/scripts/fixlint.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Aidan9382/scripts/fixlint. |
/*
General fixer for simple lint errors
Currently attempts to fix:
- Font tags (converts all parameters to their style= versions)
- Strike tags (replaces them with <s>) and tt tags (replaces them with <kbd>)
- Center tags (replaces with the center template for text and style="margin:1em auto" for wikitables)
Nothing else beyond some obselete tags is fixed as of now
A "Fix Lint" button will be added to the More tab - use this to apply fixes
For a variety of reasons, all edits should be supervised and checked after the script has ran
I don't often use JS so expect a wild number of bad practices
Inspired by, and built with help from, [[User:ಮಲ್ನಾಡಾಚ್ ಕೊಂಕ್ಣೊ/center.js]]
*/
// <nowiki>
var ISDEV = (getURIArg('fixlintdev') == 'true');
var fontColours = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgrey', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'grey', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgrey', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']; //Compiled from https://www.w3schools.com/colors/colors_names.asp
function getURIArg(arg) {
var re = RegExp('[&?]' + arg + '=([^&]*)');
var matches = re.exec(document.location);
if (matches) {
try {
return decodeURI(matches[1]);
} catch (e) { }
}
return null;
}
function ProcessTag(tag) { //Turns a tag into a more usable table, hopefully
var tagData = {};
var tagArgs = {};
tagData.args = tagArgs;
var fspace = tag.search(" ");
if (fspace == -1)
tagData.name = tag.substring(1,tag.length-1);
else
tagData.name = tag.substring(1,fspace);
var tempData = tag;
while (true) {
var argName;
var argData;
var tagArg = RegExp("(\\w+) ?= ?(?:'([^']*)'|\"([^\"]*)\")").exec(tempData);
if (!tagArg) {
tagArg = RegExp("(\\w+) ?= ?([^ >]+)").exec(tempData);
if (!tagArg) {
break;
} else {
argName = tagArg[1].toLowerCase();
argData = tagArg[2];
}
} else {
argName = tagArg[1].toLowerCase();
argData = tagArg[2] || tagArg[3];
}
if (ISDEV)
console.log("tagArg",tempData,tagArg);
tagArgs[argName] = argData;
tempData = tempData.replace(tagArg[0],"");
}
return tagData;
}
function StringifyTag(tagData) { //The inverse of the above
var final = "<" + tagData.name;
for (var argName in tagData.args) {
var argData = tagData.args[argName];
if (argData != null) {
final = final + " " + argName + "=\"" + argData + "\"";
}
}
return final + ">";
}
//Add a tab to activate
if(mw.config.get('wgArticleId') != 0 ) {
section = getURIArg("section");
mw.util.addPortletLink(
'p-cactions',
mw.util.getUrl(null,{action:'edit',section:section,fixlint:true}),
'Fix Lint',
'ca-fixlint',
'Fix basic Lint Errors'
);
}
function notify(title,message,type,autohide,autohidetime) {
if (mw.notification) {
console.log("notify",title,message);
autohide = (autohide==null && true) || autohide;
autohidetime = (autohidetime==null && "short") || autohidetime;
if (!mw.notification.autoHideSeconds[autohidetime])
mw.notification.autoHideSeconds[autohidetime] = autohidetime; //Enable custom arbitrary lengths
mw.notification.notify(message,{autoHide:autohide,autoHideSeconds:autohidetime,title:title,type:type});
}
}
function modifyPageContent(notifyOfEvents) {
function notifyIfAllowed(title,message,type,autohide,autohidetime) {
if (notifyOfEvents) {
notify(title,message,type,autohide,autohidetime);
}
}
var myContent = document.getElementById('wpTextbox1').value;
var noIssues = true;
//Remove unprocessed content (content inside nowiki or syntaxhighlight)
//This content is never actively displayed and we should therefore never judge it
var removedText = {};
var tag;
var i;
var unprocessedTags = ["[Ss][Yy][Nn][Tt][Aa][Xx][Hh][Ii][Gg][Hh][Ll][Ii][Gg][Hh][Tt]","[Nn][Oo][Ww][Ii][Kk][Ii]","[Pp][Rr][Ee]"];
for (tag in unprocessedTags) {
tag = unprocessedTags[tag];
i = Object.keys(removedText).length;
removedText[i] = [];
if (ISDEV)
console.log("Handling",tag,i,removedText[i]);
while (true) {
var foundTag = RegExp('<'+tag+'[^>]*>[\\s\\S]+?</'+tag+'>').exec(myContent);
if (!foundTag) {
break;
} else {
foundTag = foundTag[0];
removedText[i][removedText[i].length] = foundTag;
myContent = myContent.replace(foundTag,"REMOVED_TAG_"+i+"_"+(removedText[i].length-1));
}
}
}
if (ISDEV)
console.log("removed content:",removedText);
// Fix font tags - START
var fontTagBalance = myContent.split(/<font/gi).length-myContent.split(/<\/font/gi).length;
if (fontTagBalance > 0) {
notifyIfAllowed("Lint Fixer","There were "+fontTagBalance+" too many opening font tags","warn",false);
noIssues = false;
} else if (fontTagBalance < 0) {
notifyIfAllowed("Lint Fixer","There were "+(-fontTagBalance)+" too many closing font tags","warn",false);
noIssues = false;
}
while (true) {
var fontTag = RegExp('<[Ff][Oo][Nn][Tt][^>]*>').exec(myContent);
if (!fontTag) { //Out of font tags, we are done here
break;
} else {
fontTag = fontTag[0];
}
if (ISDEV)
console.log("fontTag",fontTag);
var tagData = ProcessTag(fontTag);
var style;
var fontColour = tagData.args.color;
if (fontColour) {
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
if (fontColour.substring(0,1) != "#" && !isNaN(parseInt(fontColour,16)) && !fontColours.includes(fontColour.toLowerCase())) {
fontColour = "#" + fontColour;
}
style = style + "color:" + fontColour;
tagData.args.style = style;
tagData.args.color = null;
}
var fontFace = tagData.args.face;
if (fontFace) {
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
style = style + "font-family:" + fontFace;
tagData.args.style = style;
tagData.args.face = null;
}
var fontSize = tagData.args.size;
if (fontSize) { //This requires manual conversion, as the metric is a little different
//Logic is based off of the exact px provided by [[mw:Help:Lint errors/obsolete-tag]]
//Reinforced by the behaviour seen in [[Special:Permalink/1118333555]]
var sizes = {1:"x-small",2:"small",3:"medium",4:"large",5:"x-large",6:"xx-large",7:"xxx-large"};
style = tagData.args.style || "";
if (style.length > 0 && style.substring(style.length-1) != ";") {
style = style + ";";
}
if (fontSize.substring(fontSize.length-2) == "px") {
fontSize = fontSize.substring(0,fontSize.length-2);
}
if (fontSize.substring(0,1) == "+") {
fontSize = 3 + (parseInt(fontSize.substring(1)));
} else if (fontSize.substring(0,1) == "-") {
fontSize = 3 - (parseInt(fontSize.substring(1)));
}
fontSize = Math.max(1,Math.min(7,fontSize)); //Limit 1 -> 7
if (!sizes[fontSize]) {
notifyIfAllowed("Lint Fixer","Unable to recognise font size of "+fontSize+" ("+tagData.args.size+")","warn",false);
noIssues = false;
} else {
style = style + "font-size:" + sizes[fontSize];
tagData.args.style = style;
tagData.args.size = null;
}
}
tagData.name = "span";
myContent = myContent.replace(fontTag,StringifyTag(tagData));
}
myContent = myContent.replaceAll(/<\/font>/gi,"</span>"); //Don't overengineer, this'll do
// Fix font tags - END
// Fix strike tags - START
myContent = myContent.replaceAll(/<strike>/gi,"<s>"); //Simple approach does the job
myContent = myContent.replaceAll(/<\/strike>/gi,"</s>");
// Fix strike tags - END
// Fix tt tags - START
myContent = myContent.replaceAll(/<tt>/gi,"<kbd>");
myContent = myContent.replaceAll(/<\/tt>/gi,"</kbd>");
// Fix tt tags - END
// Fix center tags - START
while (true) {
var centerTag = RegExp('<[Cc][Ee][Nn][Tt][Ee][Rr]>([\\s\\S]+?)</[Cc][Ee][Nn][Tt][Ee][Rr]>').exec(myContent);
var centerContent;
if (!centerTag) {
break;
} else {
centerContent = centerTag[1];
centerTag = centerTag[0];
}
if (centerContent.search("{\\|") > -1) {
wikitableStyle = RegExp('(\n{\\|.*?)\n').exec(centerContent)[1];
styleTag = RegExp("(style=['\"].*?);?['\"]").exec(wikitableStyle);
if (styleTag) { //These are messy lines, but it's supervised so it's good enough
centerContent = centerContent.replace(wikitableStyle,wikitableStyle.replace(styleTag[0],styleTag[1]+";margin:1em auto\""));
} else {
centerContent = centerContent.replace(wikitableStyle,wikitableStyle+" style=\"margin:1em auto\"");
}
if (centerContent.substring(0,1) == "\n") {
centerContent = centerContent.substring(1);
}
if (centerContent.substring(centerContent.length-1) == "\n") {
centerContent = centerContent.substring(0,centerContent.length-1);
}
myContent = myContent.replace(centerTag,centerContent);
} else {
if (centerContent.search("=") > -1 && centerContent.search("{") == -1) { //Definite fix needed
myContent = myContent.replace(centerTag,"{{center|1="+centerContent+"}}");
} else { //May still need fix, but who knows :)
myContent = myContent.replace(centerTag,"{{center|"+centerContent+"}}");
}
}
}
// Fix center tags - END
//Bring back unprocessed content in the reverse order to avoid bad nesting fails
for (i=Object.keys(removedText).length-1; i>=0; i--) {
var tags = removedText[i];
for (i2=0; i2<tags.length; i2++) {
myContent = myContent.replace("REMOVED_TAG_"+i+"_"+i2,tags[i2]);
}
}
return {myContent:myContent, wasChanged:document.getElementById('wpTextbox1').value != myContent, noIssues:noIssues};
}
if(mw.config.get('wgAction') == 'edit') {
if (getURIArg('fixlint') == 'true' || ISDEV) {
data = modifyPageContent(true);
document.getElementById('wpTextbox1').value = data.myContent;
if (data.wasChanged) {
document.getElementById('wpSummary').value = '[[User:Aidan9382/scripts/fixlint.js|→]]Fix [[WP:Linter|Lint]] Errors';
document.getElementById('wpMinoredit').checked = true;
//document.getElementById('wpWatchthis').checked = true;
//document.getElementById('wpWatchlistExpiry').selectedIndex = 2;
if (!ISDEV && data.noIssues)
document.forms.editform.wpDiff.click();
else if (!data.noIssues)
notify("Lint Fixer","There were some potential issues found during fixing","warn",false);
} else {
notify("Lint Fixer","No lint errors were found","success");
}
} else {
data = modifyPageContent(false);
if (data.wasChanged) {
if (data.noIssues) {
notify("Lint Fixer","Fixable lint errors were found on this page");
} else {
notify("Lint Fixer","Fixable lint errors were found on this page (supervision required)","warn");
}
} else {
//notify("Lint Fixer","No lint errors were found","success");
}
}
}
// </nowiki>