From 0732594215cc1c09d9a80f3b370e3d5ad5086c4c Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Thu, 3 Sep 2015 09:50:58 -0700 Subject: [PATCH] Fix issues spotted by jshint 2.9.0 And workaround . Bug: T111380 Change-Id: Ie478813fb8c39b4abfc46a92ed2d4e83af5abc88 --- resources/src/jquery/jquery.placeholder.js | 185 +++++++++-------- .../jquery/jquery.qunit.completenessTest.js | 3 + resources/src/jquery/jquery.tablesorter.js | 42 ++-- .../mediawiki.action.view.postEdit.js | 26 +-- .../mediawiki.language.numbers.js | 40 ++-- resources/src/mediawiki.legacy/wikibits.js | 20 +- .../mediawiki.page.image.pagination.js | 1 + .../mediawiki.special.upload.js | 35 ++-- resources/src/mediawiki/mediawiki.Title.js | 1 + resources/src/mediawiki/mediawiki.Uri.js | 3 + resources/src/mediawiki/mediawiki.js | 191 +++++++++--------- .../mediawiki/mediawiki.jqueryMsg.test.js | 1 + 12 files changed, 279 insertions(+), 269 deletions(-) diff --git a/resources/src/jquery/jquery.placeholder.js b/resources/src/jquery/jquery.placeholder.js index d50422e2c7..3d25a93c25 100644 --- a/resources/src/jquery/jquery.placeholder.js +++ b/resources/src/jquery/jquery.placeholder.js @@ -23,6 +23,98 @@ hooks, placeholder; + function safeActiveElement() { + // Avoid IE9 `document.activeElement` of death + // https://github.com/mathiasbynens/jquery-placeholder/pull/99 + try { + return document.activeElement; + } catch (err) {} + } + + function args(elem) { + // Return an object of element attributes + var newAttrs = {}, + rinlinejQuery = /^jQuery\d+$/; + $.each(elem.attributes, function (i, attr) { + if (attr.specified && !rinlinejQuery.test(attr.name)) { + newAttrs[attr.name] = attr.value; + } + }); + return newAttrs; + } + + function clearPlaceholder(event, value) { + var input = this, + $input = $(input); + if (input.value === $input.attr('placeholder') && $input.hasClass('placeholder')) { + if ($input.data('placeholder-password')) { + $input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id')); + // If `clearPlaceholder` was called from `$.valHooks.input.set` + if (event === true) { + $input[0].value = value; + return value; + } + $input.focus(); + } else { + input.value = ''; + $input.removeClass('placeholder'); + if (input === safeActiveElement()) { + input.select(); + } + } + } + } + + function setPlaceholder() { + var $replacement, + input = this, + $input = $(input), + id = this.id; + if (!input.value) { + if (input.type === 'password') { + if (!$input.data('placeholder-textinput')) { + try { + $replacement = $input.clone().attr({ 'type': 'text' }); + } catch (e) { + $replacement = $('').attr($.extend(args(this), { 'type': 'text' })); + } + $replacement + .removeAttr('name') + .data({ + 'placeholder-password': $input, + 'placeholder-id': id + }) + .bind('focus.placeholder drop.placeholder', clearPlaceholder); + $input + .data({ + 'placeholder-textinput': $replacement, + 'placeholder-id': id + }) + .before($replacement); + } + $input = $input.removeAttr('id').hide().prev().attr('id', id).show(); + // Note: `$input[0] != input` now! + } + $input.addClass('placeholder'); + $input[0].value = $input.attr('placeholder'); + } else { + $input.removeClass('placeholder'); + } + } + + function changePlaceholder(text) { + var hasArgs = arguments.length, + $input = this; + if (hasArgs) { + if ($input.attr('placeholder') !== text) { + $input.prop('placeholder', text); + if ($input.hasClass('placeholder')) { + $input[0].value = text; + } + } + } + } + if (isInputSupported && isTextareaSupported) { placeholder = prototype.placeholder = function (text) { @@ -133,97 +225,4 @@ }); } - - function args(elem) { - // Return an object of element attributes - var newAttrs = {}, - rinlinejQuery = /^jQuery\d+$/; - $.each(elem.attributes, function (i, attr) { - if (attr.specified && !rinlinejQuery.test(attr.name)) { - newAttrs[attr.name] = attr.value; - } - }); - return newAttrs; - } - - function clearPlaceholder(event, value) { - var input = this, - $input = $(input); - if (input.value === $input.attr('placeholder') && $input.hasClass('placeholder')) { - if ($input.data('placeholder-password')) { - $input = $input.hide().next().show().attr('id', $input.removeAttr('id').data('placeholder-id')); - // If `clearPlaceholder` was called from `$.valHooks.input.set` - if (event === true) { - $input[0].value = value; - return value; - } - $input.focus(); - } else { - input.value = ''; - $input.removeClass('placeholder'); - if (input === safeActiveElement()) { - input.select(); - } - } - } - } - - function setPlaceholder() { - var $replacement, - input = this, - $input = $(input), - id = this.id; - if (!input.value) { - if (input.type === 'password') { - if (!$input.data('placeholder-textinput')) { - try { - $replacement = $input.clone().attr({ 'type': 'text' }); - } catch (e) { - $replacement = $('').attr($.extend(args(this), { 'type': 'text' })); - } - $replacement - .removeAttr('name') - .data({ - 'placeholder-password': $input, - 'placeholder-id': id - }) - .bind('focus.placeholder drop.placeholder', clearPlaceholder); - $input - .data({ - 'placeholder-textinput': $replacement, - 'placeholder-id': id - }) - .before($replacement); - } - $input = $input.removeAttr('id').hide().prev().attr('id', id).show(); - // Note: `$input[0] != input` now! - } - $input.addClass('placeholder'); - $input[0].value = $input.attr('placeholder'); - } else { - $input.removeClass('placeholder'); - } - } - - function safeActiveElement() { - // Avoid IE9 `document.activeElement` of death - // https://github.com/mathiasbynens/jquery-placeholder/pull/99 - try { - return document.activeElement; - } catch (err) {} - } - - function changePlaceholder(text) { - var hasArgs = arguments.length, - $input = this; - if (hasArgs) { - if ($input.attr('placeholder') !== text) { - $input.prop('placeholder', text); - if ($input.hasClass('placeholder')) { - $input[0].value = text; - } - } - } - } - }(jQuery)); diff --git a/resources/src/jquery/jquery.qunit.completenessTest.js b/resources/src/jquery/jquery.qunit.completenessTest.js index 556bf8c72c..8908067c68 100644 --- a/resources/src/jquery/jquery.qunit.completenessTest.js +++ b/resources/src/jquery/jquery.qunit.completenessTest.js @@ -291,8 +291,11 @@ // Make the spy inherit from the original so that its static methods are also // visible in the spy (e.g. when we inject a check into mw.log, mw.log.warn // must remain accessible). + // XXX: https://github.com/jshint/jshint/issues/2656 + /*jshint ignore:start */ /*jshint proto:true */ spy.__proto__ = val; + /*jshint ignore:end */ // Objects are by reference, members (unless objects) are not. obj[ key ] = spy; diff --git a/resources/src/jquery/jquery.tablesorter.js b/resources/src/jquery/jquery.tablesorter.js index 8efbb1ce8f..00fb480ebe 100644 --- a/resources/src/jquery/jquery.tablesorter.js +++ b/resources/src/jquery/jquery.tablesorter.js @@ -278,6 +278,16 @@ } } + function uniqueElements( array ) { + var uniques = []; + $.each( array, function ( index, elem ) { + if ( elem !== undefined && $.inArray( elem, uniques ) === -1 ) { + uniques.push( elem ); + } + } ); + return uniques; + } + function buildHeaders( table, msg ) { var config = $( table ).data( 'tablesorter' ).config, maxSeen = 0, @@ -377,6 +387,17 @@ } + function isValueInArray( v, a ) { + var i, + len = a.length; + for ( i = 0; i < len; i++ ) { + if ( a[i][0] === v ) { + return true; + } + } + return false; + } + /** * Sets the sort count of the columns that are not affected by the sorting to have them sorted * in default (ascending) order when their header cell is clicked the next time. @@ -416,27 +437,6 @@ } ); } - function isValueInArray( v, a ) { - var i, - len = a.length; - for ( i = 0; i < len; i++ ) { - if ( a[i][0] === v ) { - return true; - } - } - return false; - } - - function uniqueElements( array ) { - var uniques = []; - $.each( array, function ( index, elem ) { - if ( elem !== undefined && $.inArray( elem, uniques ) === -1 ) { - uniques.push( elem ); - } - } ); - return uniques; - } - function setHeadersCss( table, $headers, list, css, msg, columnToHeader ) { // Remove all header information and reset titles to default message $headers.removeClass( css[0] ).removeClass( css[1] ).attr( 'title', msg[1] ); diff --git a/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js b/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js index c008dfd80f..31250c9e2d 100644 --- a/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js +++ b/resources/src/mediawiki.action/mediawiki.action.view.postEdit.js @@ -24,6 +24,19 @@ cookieVal = mw.cookie.get( cookieKey ), $div, id; + function removeConfirmation() { + $div.remove(); + mw.hook( 'postEdit.afterRemoval' ).fire(); + } + + function fadeOutConfirmation() { + clearTimeout( id ); + $div.find( '.postedit' ).addClass( 'postedit postedit-faded' ); + setTimeout( removeConfirmation, 500 ); + + return false; + } + function showConfirmation( data ) { data = data || {}; if ( data.message === undefined ) { @@ -45,19 +58,6 @@ id = setTimeout( fadeOutConfirmation, 3000 ); } - function fadeOutConfirmation() { - clearTimeout( id ); - $div.find( '.postedit' ).addClass( 'postedit postedit-faded' ); - setTimeout( removeConfirmation, 500 ); - - return false; - } - - function removeConfirmation() { - $div.remove(); - mw.hook( 'postEdit.afterRemoval' ).fire(); - } - mw.hook( 'postEdit' ).add( showConfirmation ); if ( config.wgAction === 'view' && cookieVal ) { diff --git a/resources/src/mediawiki.language/mediawiki.language.numbers.js b/resources/src/mediawiki.language/mediawiki.language.numbers.js index 3c13055b7f..1481d2aafd 100644 --- a/resources/src/mediawiki.language/mediawiki.language.numbers.js +++ b/resources/src/mediawiki.language/mediawiki.language.numbers.js @@ -6,6 +6,26 @@ * @class mw.language */ + /** + * Replicate a string 'n' times. + * + * @private + * @param {string} str The string to replicate + * @param {number} num Number of times to replicate the string + * @return {string} + */ + function replicate( str, num ) { + if ( num <= 0 || !str ) { + return ''; + } + + var buf = []; + while ( num-- ) { + buf.push( str ); + } + return buf.join( '' ); + } + /** * Pad a string to guarantee that it is at least `size` length by * filling with the character `ch` at either the start or end of the @@ -33,26 +53,6 @@ return end ? out + padStr : padStr + out; } - /** - * Replicate a string 'n' times. - * - * @private - * @param {string} str The string to replicate - * @param {number} num Number of times to replicate the string - * @return {string} - */ - function replicate( str, num ) { - if ( num <= 0 || !str ) { - return ''; - } - - var buf = []; - while ( num-- ) { - buf.push( str ); - } - return buf.join( '' ); - } - /** * Apply numeric pattern to absolute value using options. Gives no * consideration to local customs. diff --git a/resources/src/mediawiki.legacy/wikibits.js b/resources/src/mediawiki.legacy/wikibits.js index b5720a4464..2dcc772f99 100644 --- a/resources/src/mediawiki.legacy/wikibits.js +++ b/resources/src/mediawiki.legacy/wikibits.js @@ -164,13 +164,6 @@ * See https://www.mediawiki.org/wiki/ResourceLoader/Legacy_JavaScript#wikibits.js */ - function importScript( page ) { - var uri = mw.config.get( 'wgScript' ) + '?title=' + - mw.util.wikiUrlencode( page ) + - '&action=raw&ctype=text/javascript'; - return importScriptURI( uri ); - } - /** * @deprecated since 1.17 Use mw.loader instead. Warnings added in 1.25. */ @@ -186,11 +179,11 @@ return s; } - function importStylesheet( page ) { + function importScript( page ) { var uri = mw.config.get( 'wgScript' ) + '?title=' + mw.util.wikiUrlencode( page ) + - '&action=raw&ctype=text/css'; - return importStylesheetURI( uri ); + '&action=raw&ctype=text/javascript'; + return importScriptURI( uri ); } /** @@ -207,6 +200,13 @@ return l; } + function importStylesheet( page ) { + var uri = mw.config.get( 'wgScript' ) + '?title=' + + mw.util.wikiUrlencode( page ) + + '&action=raw&ctype=text/css'; + return importStylesheetURI( uri ); + } + msg = 'Use mw.loader instead.'; mw.log.deprecate( win, 'loadedScripts', loadedScripts, msg ); mw.log.deprecate( win, 'importScriptURI', importScriptURI, msg ); diff --git a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js index 9ad9c30a79..7daf386f9f 100644 --- a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js +++ b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js @@ -2,6 +2,7 @@ * Implement AJAX navigation for multi-page images so the user may browse without a full page reload. */ ( function ( mw, $ ) { + /*jshint latedef:false */ var jqXhr, $multipageimage, $spinner, cache = {}, cacheOrder = []; diff --git a/resources/src/mediawiki.special/mediawiki.special.upload.js b/resources/src/mediawiki.special/mediawiki.special.upload.js index 8f72d632b6..2bacabe000 100644 --- a/resources/src/mediawiki.special/mediawiki.special.upload.js +++ b/resources/src/mediawiki.special/mediawiki.special.upload.js @@ -6,6 +6,7 @@ * @singleton */ ( function ( mw, $ ) { + /*jshint latedef:false */ var uploadWarning, uploadLicense, ajaxUploadDestCheck = mw.config.get( 'wgAjaxUploadDestCheck' ), $license = $( '#wpLicense' ); @@ -273,6 +274,23 @@ return ( $.inArray( file.type, known ) !== -1 ) && file.size > 0 && file.size < tooHuge; } + /** + * Format a file size attractively. + * + * TODO: Match numeric formatting + * + * @param {number} s + * @return {string} + */ + function prettySize( s ) { + var sizeMsgs = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes']; + while ( s >= 1024 && sizeMsgs.length > 1 ) { + s /= 1024; + sizeMsgs = sizeMsgs.slice( 1 ); + } + return mw.msg( sizeMsgs[0], Math.round( s ) ); + } + /** * Show a thumbnail preview of PNG, JPEG, GIF, and SVG files prior to upload * in browsers supporting HTML5 FileAPI. @@ -454,23 +472,6 @@ } } - /** - * Format a file size attractively. - * - * TODO: Match numeric formatting - * - * @param {number} s - * @return {string} - */ - function prettySize( s ) { - var sizeMsgs = ['size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes']; - while ( s >= 1024 && sizeMsgs.length > 1 ) { - s /= 1024; - sizeMsgs = sizeMsgs.slice( 1 ); - } - return mw.msg( sizeMsgs[0], Math.round( s ) ); - } - /** * Clear the file upload preview area. */ diff --git a/resources/src/mediawiki/mediawiki.Title.js b/resources/src/mediawiki/mediawiki.Title.js index 661ab7444b..e9d07eda43 100644 --- a/resources/src/mediawiki/mediawiki.Title.js +++ b/resources/src/mediawiki/mediawiki.Title.js @@ -4,6 +4,7 @@ * @since 1.18 */ ( function ( mw, $ ) { + /*jshint latedef:false */ /** * @class mw.Title diff --git a/resources/src/mediawiki/mediawiki.Uri.js b/resources/src/mediawiki/mediawiki.Uri.js index ac6c583dc6..7563ae4572 100644 --- a/resources/src/mediawiki/mediawiki.Uri.js +++ b/resources/src/mediawiki/mediawiki.Uri.js @@ -68,7 +68,9 @@ if ( val === undefined || val === null || val === '' ) { return ''; } + /* jshint latedef:false */ return pre + ( raw ? val : mw.Uri.encode( val ) ) + post; + /* jshint latedef:true */ } /** @@ -173,6 +175,7 @@ * @param {boolean} [options.overrideKeys=false] Whether to let duplicate query parameters * override each other (`true`) or automagically convert them to an array (`false`). */ + /* jshint latedef:false */ function Uri( uri, options ) { var prop, defaultUri = getDefaultUri(); diff --git a/resources/src/mediawiki/mediawiki.js b/resources/src/mediawiki/mediawiki.js index 2621a97dba..a73accf51b 100644 --- a/resources/src/mediawiki/mediawiki.js +++ b/resources/src/mediawiki/mediawiki.js @@ -7,6 +7,7 @@ * @alternateClassName mediaWiki * @singleton */ +/*jshint latedef:false */ /*global sha1 */ ( function ( $ ) { 'use strict'; @@ -97,11 +98,11 @@ function setGlobalMapValue( map, key, value ) { map.values[key] = value; mw.log.deprecate( - window, - key, - value, - // Deprecation notice for mw.config globals (T58550, T72470) - map === mw.config && 'Use mw.config instead.' + window, + key, + value, + // Deprecation notice for mw.config globals (T58550, T72470) + map === mw.config && 'Use mw.config instead.' ); } @@ -919,93 +920,6 @@ return sha1( hashes.join( '' ) ).slice( 0, 12 ); } - /** - * Resolve dependencies and detect circular references. - * - * @private - * @param {string} module Name of the top-level module whose dependencies shall be - * resolved and sorted. - * @param {Array} resolved Returns a topological sort of the given module and its - * dependencies, such that later modules depend on earlier modules. The array - * contains the module names. If the array contains already some module names, - * this function appends its result to the pre-existing array. - * @param {Object} [unresolved] Hash used to track the current dependency - * chain; used to report loops in the dependency graph. - * @throws {Error} If any unregistered module or a dependency loop is encountered - */ - function sortDependencies( module, resolved, unresolved ) { - var n, deps, len, skip; - - if ( !hasOwn.call( registry, module ) ) { - throw new Error( 'Unknown dependency: ' + module ); - } - - if ( registry[module].skip !== null ) { - /*jshint evil:true */ - skip = new Function( registry[module].skip ); - registry[module].skip = null; - if ( skip() ) { - registry[module].skipped = true; - registry[module].dependencies = []; - registry[module].state = 'ready'; - handlePending( module ); - return; - } - } - - // Resolves dynamic loader function and replaces it with its own results - if ( $.isFunction( registry[module].dependencies ) ) { - registry[module].dependencies = registry[module].dependencies(); - // Ensures the module's dependencies are always in an array - if ( typeof registry[module].dependencies !== 'object' ) { - registry[module].dependencies = [registry[module].dependencies]; - } - } - if ( $.inArray( module, resolved ) !== -1 ) { - // Module already resolved; nothing to do - return; - } - // Create unresolved if not passed in - if ( !unresolved ) { - unresolved = {}; - } - // Tracks down dependencies - deps = registry[module].dependencies; - len = deps.length; - for ( n = 0; n < len; n += 1 ) { - if ( $.inArray( deps[n], resolved ) === -1 ) { - if ( unresolved[deps[n]] ) { - throw new Error( - 'Circular reference detected: ' + module + - ' -> ' + deps[n] - ); - } - - // Add to unresolved - unresolved[module] = true; - sortDependencies( deps[n], resolved, unresolved ); - delete unresolved[module]; - } - } - resolved[resolved.length] = module; - } - - /** - * Get a list of module names that a module depends on in their proper dependency - * order. - * - * @private - * @param {string[]} module Array of string module names - * @return {Array} List of dependencies, including 'module'. - */ - function resolve( modules ) { - var resolved = []; - $.each( modules, function ( idx, module ) { - sortDependencies( module, resolved ); - } ); - return resolved; - } - /** * Determine whether all dependencies are in state 'ready', which means we may * execute the module or job now. @@ -1112,6 +1026,93 @@ } } + /** + * Resolve dependencies and detect circular references. + * + * @private + * @param {string} module Name of the top-level module whose dependencies shall be + * resolved and sorted. + * @param {Array} resolved Returns a topological sort of the given module and its + * dependencies, such that later modules depend on earlier modules. The array + * contains the module names. If the array contains already some module names, + * this function appends its result to the pre-existing array. + * @param {Object} [unresolved] Hash used to track the current dependency + * chain; used to report loops in the dependency graph. + * @throws {Error} If any unregistered module or a dependency loop is encountered + */ + function sortDependencies( module, resolved, unresolved ) { + var n, deps, len, skip; + + if ( !hasOwn.call( registry, module ) ) { + throw new Error( 'Unknown dependency: ' + module ); + } + + if ( registry[module].skip !== null ) { + /*jshint evil:true */ + skip = new Function( registry[module].skip ); + registry[module].skip = null; + if ( skip() ) { + registry[module].skipped = true; + registry[module].dependencies = []; + registry[module].state = 'ready'; + handlePending( module ); + return; + } + } + + // Resolves dynamic loader function and replaces it with its own results + if ( $.isFunction( registry[module].dependencies ) ) { + registry[module].dependencies = registry[module].dependencies(); + // Ensures the module's dependencies are always in an array + if ( typeof registry[module].dependencies !== 'object' ) { + registry[module].dependencies = [registry[module].dependencies]; + } + } + if ( $.inArray( module, resolved ) !== -1 ) { + // Module already resolved; nothing to do + return; + } + // Create unresolved if not passed in + if ( !unresolved ) { + unresolved = {}; + } + // Tracks down dependencies + deps = registry[module].dependencies; + len = deps.length; + for ( n = 0; n < len; n += 1 ) { + if ( $.inArray( deps[n], resolved ) === -1 ) { + if ( unresolved[deps[n]] ) { + throw new Error( + 'Circular reference detected: ' + module + + ' -> ' + deps[n] + ); + } + + // Add to unresolved + unresolved[module] = true; + sortDependencies( deps[n], resolved, unresolved ); + delete unresolved[module]; + } + } + resolved[resolved.length] = module; + } + + /** + * Get a list of module names that a module depends on in their proper dependency + * order. + * + * @private + * @param {string[]} module Array of string module names + * @return {Array} List of dependencies, including 'module'. + */ + function resolve( modules ) { + var resolved = []; + $.each( modules, function ( idx, module ) { + sortDependencies( module, resolved ); + } ); + return resolved; + } + /** * Load and execute a script with callback. * @@ -1140,7 +1141,7 @@ * @param {string} module Module name to execute */ function execute( module ) { - var key, value, media, i, urls, cssHandle, checkCssHandles, + var key, value, media, i, urls, cssHandle, checkCssHandles, runScript, cssHandlesRegistered = false; if ( !hasOwn.call( registry, module ) ) { @@ -1175,7 +1176,7 @@ el.href = url; } - function runScript() { + runScript = function () { var script, markModuleReady, nestedAddScript, legacyWait, // Expand to include dependencies since we have to exclude both legacy modules // and their dependencies from the legacyWait (to prevent a circular dependency). @@ -1236,7 +1237,7 @@ mw.track( 'resourceloader.exception', { exception: e, module: module, source: 'module-execute' } ); handlePending( module ); } - } + }; // This used to be inside runScript, but since that is now fired asychronously // (after CSS is loaded) we need to set it here right away. It is crucial that diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js index d9fd6a7d0a..778836068c 100644 --- a/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js +++ b/tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js @@ -98,6 +98,7 @@ * @return */ function process( tasks, complete ) { + /*jshint latedef:false */ function abort() { tasks.splice( 0, tasks.length ); next(); -- 2.20.1