User:Pathoschild/Scripts/heuristic-script-update.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* global $, pathoschild */
/**
* Heuristically updates user scripts for recent MediaWiki changes based on the documentation
* at https://www.mediawiki.org/wiki/ResourceLoader/Migration_guide_(users).
*
* Warnings:
* • This *will* break scripts; it should always be paired with manual review and corrections.
* • I don't expect anyone else to use this, so it may change without warning. Do let me know
* if you use it.
*/
pathoschild.migrateLegacyScripts = function() {
/*********
** Properties
*********/
var self = {};
var _patterns = {
outdatedScripts: /user:pathoschild\/scripts|tools\.wmflabs\.org\/(?:meta|pathoschild)|global\.(?:css|js)/mig,
// derived from https://www.mediawiki.org/wiki/ResourceLoader/Legacy_JavaScript/list
deprecatedCalls: /sajax_debug_mode|sajax_request_type|sajax_debug|sajax_init_object|sajax_do_call|wfSupportsAjax|wgAjaxWatch|considerChangingExpiryFocus|updateBlockOptions|onNameChange|onNameChangeHook|currentFocused|addButton|mwSetupToolbar|([^\.]|^)insertTags|scrollEditBox|toggleVisibility|historyRadios|diffcheck|histrowinit|htmlforms|doneIETransform|doneIEAlphaFix|expandedURLs|hookit|relativeforfloats|setrelative|onbeforeprint|onafterprint|attachMetadataToggle|os_map|os_cache|os_cur_keypressed|os_keypressed_count|os_timer|os_mouse_pressed|os_mouse_num|os_mouse_moved|os_search_timeout|os_autoload_inputs|os_autoload_forms|os_is_stopped|os_max_lines_per_suggest|os_animation_steps|os_animation_min_step|os_animation_delay|os_container_max_width|os_animation_timer|os_use_datalist|os_Timer|os_Results|os_AnimationTimer|os_MWSuggestInit|os_initHandlers|os_hookEvent|os_eventKeyup|os_processKey|os_eventKeypress|os_eventKeydown|os_eventOnsubmit|os_hideResults|os_decodeValue|os_encodeQuery|os_updateResults|os_setupDatalist|os_getNamespaces|os_updateIfRelevant|os_delayedFetch|os_fetchResults|os_getTarget|os_isNumber|os_enableSuggestionsOn|os_disableSuggestionsOn|os_eventBlur|os_eventFocus|os_setupDiv|os_createResultTable|os_showResults|os_operaWidthFix|f_clientWidth|f_clientHeight|f_scrollLeft|f_scrollTop|f_filterResults|os_availableHeight|os_getElementPosition|os_createContainer|os_fitContainer|os_trimResultText|os_animateChangeWidth|os_changeHighlight|os_HighlightClass|os_updateSearchQuery|os_eventMouseover|os_getNumberSuffix|os_eventMousemove|os_eventMousedown|os_eventMouseup|os_createToggle|os_toggle|tabbedprefs|uncoversection|checkTimezone|timezoneSetup|fetchTimezone|guessTimezone|updateTimezoneSelection|doLivePreview|ProtectionForm|setupRightClickEdit|addRightClickEditHandler|mwSearchHeaderClick|mwToggleSearchCheckboxes|wgUploadWarningObj|wgUploadLicenseObj|licenseSelectorCheck|wgUploadSetup|toggleUploadInputs|fillDestFilename|toggleFilenameFiller|clientPC|is_gecko|is_safari|is_safari_win|is_chrome|is_chrome_mac|is_ff2|is_ff2_win|is_ff2_x11|webkit_match|ff2_bugs|ie6_bugs|doneOnloadHook|onloadFuncts|addOnloadHook|runOnloadHook|killEvt|loadedScripts|importScriptURI|importStylesheetURI|appendCSS|addHandler|addClickHandler|removeHandler|hookEvent|mwEditButtons|tooltipAccessKeyPrefix|tooltipAccessKeyRegexp|updateTooltipAccessKeys|akeytt|ta=\[\]|ta=\{\}|ta=new Array|ta=new Object|ta = \[\]|ta = \{\}|ta = new Array|ta = new Object|lastCheckbox|setupCheckboxShiftClick|addCheckboxClickHandlers|checkboxClickHandler|showTocToggle|toggleToc|changeText|getInnerText|escapeQuotes|escapeQuotesHTML|jsMsg|getElementsByClassName|redirectToFragment/mg
};
/*********
** Public methods
*********/
/**
* Apply all rules (including the least safe ones) to the page being edited.
* @param {object} editor The TemplateScript script helpers.
*/
self.applyAll = function(editor) {
// get text
var text = editor.get();
var hasDeprecatedCalls = !!text.match(_patterns.deprecatedCalls);
// apply changes
text = self.applySafeRules(text);
text = self.applyDangerousRules(text);
text = self.applyPathoschildRules(text);
self.detectMigrationIssues(text);
// save changes
editor
.set(text)
.setEditSummary('updated scripts');
if(hasDeprecatedCalls)
editor.appendEditSummary('migrated [[mw:ResourceLoader/Migration guide (users)|deprecated functions]]');
};
/**
* Apply the (relatively) safe migration rules.
* @param {string} The scripts to modify.
* @returns {string} The modified scripts.
*/
self.applySafeRules = function(text) {
/* migrate functions with a one-to-one mapping */
text = text.replace(/addOnloadHook/g, '$');
text = text.replace(/(^|[^\.])(addPortletLink|insertTags)/mg, '$1mw.util.$2');
/* migrate document.write → mw.loader.load */
var multilineDocumentWrite = /document\.write\(['"](.+)['"][\s\n]+\+\s*['"]/g;
while(text.match(multilineDocumentWrite))
text = text.replace(multilineDocumentWrite, 'document.write(\'$1');
text = text.replace(/document.write\(['"]<script.+? src=['"](.+?)['"]><\/script>['"]\);?/ig, 'mw.loader.load(\'$1\');');
/* migrate importScriptURI → mw.loader.load */
text = text.replace(/(?:mw\.loader\.load|importScriptURI)\(['"](?:https?:)?\/\/(.+?)['"]\);?/g, 'mw.loader.load(\'//$1\');');
/* migrate importStylesheetURI → mw.loader.load */
text = text.replace(/importStylesheetURI\(['"](?:https?:)?\/\/(.+?)['"]\);?/g, 'mw.loader.load(\'//$1\', \'text/css\');');
/* remove obsolete dontcountme/smaxage script parameters */
text = text.replace(/&dontcountme=s|&s?maxage=\d+/g, '');
/* update safe variables moved into mw.config */
text = text.replace(/\b(wgAction|wgArticleId|wgArticlePath|wgCanonicalNamespace|wgCanonicalSpecialPageName|wgContentLanguage|wgDBname|wgIsArticle|wgIsRedirect|wgNamespaceNumber|wgPageName|wgRevisionId|wgScript|wgScriptExtension|wgScriptPath|wgServer|wgServerName|wgSiteName|wgTitle|wgUserName|wgUserId|wgUserLanguage|wgUserNamewgVersion)\b(?!['"])/g, 'mw.config.get(\'$1\')');
/* trim trailing whitespace */
text = text.replace(/[ \t]+$/mg, '');
return text;
};
/**
* Apply migration rules that require significant review and followup adjustment.
* Don't run these unless you know exactly what you're doing and are familiar with all
* affected functions.
* @param {string} The scripts to modify.
* @returns {string} The modified scripts.
*/
self.applyDangerousRules = function(text) {
/* mwCustomEditButtons → wikiEditor */
/* This creates a separate function call for each edit button, which should be combined into one. */
text = text.replace(/mwCustomEditButtons *\[[^\]]*\] *= *\{[\s\S]+?\}(?:;|\n)/g, function(item) {
var image = (item.match(/['"]imageFile['"]: ['"](?:http:)?(.*?)['"](?:,|$)/m) || [])[1] || '';
var label = (item.match(/['"]speedTip['"]: ['"](.*?)['"](?:,|$)/m) || [])[1] || '';
var preText = (item.match(/['"]tagOpen['"]: ['"](.*?)['"](?:,|$)/m) || [])[1] || '';
var postText = (item.match(/['"]tagClose['"]: ['"](.*?)['"](?:,|$)/m) || [])[1] || '';
var sampleText = (item.match(/['"]sampleText['"]: ['"](.*?)['"](?:,|$)/m) || [])[1] || '';
var result = "$('#wpTextbox1').wikiEditor('addToToolbar', {\n section: 'main',\n group: 'format',\n\ttools: {\n\t\t'custom-" + label + "': {\n\t\t\tlabel: '" + label + "',\n\t\t\ttype: 'button',\n\t\t\ticon: '" + image + "',\n\t\t\taction: {\n\t\t\t\ttype: 'encapsulate',\n\t\t\t\toptions: {\n\t\t\t\t\tpre: '" + preText + "',\n\t\t\t\t\tpost: '" + postText + "',\n\t\t\t\t\tsampleText: '" + sampleText + "'\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});";
result = result.replace(/editSummary:'', /, '');
return result;
});
text = text.replace(/[\n\s]*\}[\n\s]*\}\);[\n\s]*\$\('#wpTextbox1'\)\.wikiEditor\('addToToolbar', \{[\n\s]*section: 'main',[\n\s]*group: 'format',[\n\s]*tools: \{\n*/mg, ',\n');
/* heuristically update variables moved into mw.config */
var deprecatedVariableKeys = Object.keys(mw.config.get()).filter(function(key) { return key in window; }).sort();
var deprecatedVariablePattern = new RegExp('\\b(' + deprecatedVariableKeys.join('|') + ')\\b(?![\'"])', 'g');
text = text.replace(deprecatedVariablePattern, 'mw.config.get(\'$1\')');
return text;
};
/**
* Apply migration rules for Pathoschild's user scripts. You should probably never run these unless you're Pathoschild.
* @param {string} The scripts to modify.
* @returns {string} The modified scripts.
*/
self.applyPathoschildRules = function(text) {
// warn about usage trackers disabled by <nowiki>/equivalent tags
var nowikiBlocks = text.match(/<(nowiki|pre|source)[^>]*>[\s\S]*?(?:<\/\1>|$)/g) || [];
var disabledTrackers = (nowikiBlocks.join('\n').match(/\[\[file:Pathoschild[^\]]+\]\]/ig) || '').toString();
if(disabledTrackers)
alert('These update trackers might be disabled by <nowiki>, <pre>, or <source>: ' + disabledTrackers);
// replace text
text = text.replace(/\/\*[\s\S]+?\*\/[\s\n]*/g, function(match) {
return match.match(/pathoschild/i) ? '' : match;
});
text = text.replace(/mw.loader.load\('[^']+?(?:Ajax_sysop|ajaxsysop)(?:\/experimental)?\.js[^']*'\);/, '/**\n * Ajax sysop\n * @see https://meta.wikimedia.org/wiki/Ajax_sysop\n * @update-token [[File:pathoschild/ajaxsysop.js]]\n */\nmw.loader.load(\'//tools-static.wmflabs.org/meta/scripts/pathoschild.ajaxsysop.js\');');
text = text.replace(/mw.loader.load\('[^']+?(?:Force_ltr|forceltr)\.js[^']*'\);/, '/**\n * Forces left-to-right layout and editing on RTL wikis.\n * @see https://meta.wikimedia.org/wiki/Force_ltr\n * @update-token [[File:pathoschild/forceltr.js]]\n */\nmw.loader.load(\'//tools-static.wmflabs.org/meta/scripts/pathoschild.forceltr.js\');');
text = text.replace(/mw.loader.load\('[^']+?[Ss]teward[Ss]cript\.js[^']*'\);/, '/**\n * StewardScript extends the user interface for Wikimedia stewards\' convenience.\n * @see https://meta.wikimedia.org/wiki/StewardScript\n * @update-token [[File:pathoschild/stewardscript.js]]\n */\nmw.loader.load(\'//tools-static.wmflabs.org/meta/scripts/pathoschild.stewardscript.js\');');
text = text.replace(/mw.loader.load\('[^']+?(?:[Tt]emplate[Ss]cript|[Rr]egex_menu_framework(?:\/experimental)?)\.js[^']*'\);/, '/**\n * TemplateScript adds configurable templates and scripts to the sidebar, and adds an example regex editor.\n * @see https://meta.wikimedia.org/wiki/TemplateScript\n * @update-token [[File:pathoschild/templatescript.js]]\n */\nmw.loader.load(\'//tools-static.wmflabs.org/meta/scripts/pathoschild.templatescript.js\');');
text = text.replace(/mw.loader.load\('[^']+?[Uu]sejs\.js[^']*'\);/, '/**\n * Imports JaveScript for the current page when the URL contains a &usejs parameter.\n * @see https://meta.wikimedia.org/wiki/UseJS\n * @update-token [[File:pathoschild/usejs.js]]\n */\nmw.loader.load(\'//tools-static.wmflabs.org/meta/scripts/pathoschild.usejs.js\');');
text = text.replace(/[ \t]*new_template\('(.*?)','(.*?)','(.*?)'(?:,'(.*?)')?\);/g, ' \{ name:\'$2\', template:\'$3\', editSummary:\'$4\', forActions:\'$1\' \},');
text = text.replace(/, forActions: *'edit'/g, '');
text = text.replace(/, editSummary: *''/g, '');
return text;
};
/**
* Detect code that may still need to be migrated manually and show an alert.
* @param {string} The scripts to modify.
*/
self.detectMigrationIssues = function(text) {
var scripts = text.match(_patterns.outdatedScripts);
if(scripts)
alert('This script might contain outdated scripts (found these triggers: ' + scripts + ')');
var deprecated = text.match(_patterns.deprecatedCalls);
if(deprecated)
alert('This script might contain obsolete function calls(found these triggers: ' + deprecated + ')');
};
/*********
** Private methods
*********/
return self;
};