Merge "Set up node-jscs via Grunt (and pass it)"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 25 Mar 2014 07:01:19 +0000 (07:01 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 25 Mar 2014 07:01:19 +0000 (07:01 +0000)
52 files changed:
.jscsrc [new file with mode: 0644]
.jshintignore
.jshintrc
resources/jquery/jquery.autoEllipsis.js
resources/jquery/jquery.byteLimit.js
resources/jquery/jquery.colorUtil.js
resources/jquery/jquery.highlightText.js
resources/jquery/jquery.localize.js
resources/jquery/jquery.placeholder.js
resources/jquery/jquery.qunit.completenessTest.js
resources/jquery/jquery.suggestions.js
resources/jquery/jquery.tablesorter.js
resources/jquery/jquery.textSelection.js
resources/mediawiki.action/mediawiki.action.edit.editWarning.js
resources/mediawiki.action/mediawiki.action.edit.js
resources/mediawiki.action/mediawiki.action.history.js
resources/mediawiki.action/mediawiki.action.view.metadata.js
resources/mediawiki.api/mediawiki.api.category.js
resources/mediawiki.language/languages/fi.js
resources/mediawiki.language/languages/hy.js
resources/mediawiki.language/languages/la.js
resources/mediawiki.language/languages/os.js
resources/mediawiki.language/languages/uk.js
resources/mediawiki.language/mediawiki.language.js
resources/mediawiki.page/mediawiki.page.gallery.js
resources/mediawiki.page/mediawiki.page.image.pagination.js
resources/mediawiki.special/mediawiki.special.block.js
resources/mediawiki.special/mediawiki.special.javaScriptTest.js
resources/mediawiki.special/mediawiki.special.preferences.js
resources/mediawiki.special/mediawiki.special.upload.js
resources/mediawiki/mediawiki.Title.js
resources/mediawiki/mediawiki.debug.js
resources/mediawiki/mediawiki.feedback.js
resources/mediawiki/mediawiki.jqueryMsg.js
skins/common/IEFixes.js
skins/common/ajax.js
skins/common/config.js
skins/common/upload.js
skins/common/wikibits.js
skins/vector/vector.js
tests/frontend/Gruntfile.js [new file with mode: 0644]
tests/frontend/package.json [new file with mode: 0644]
tests/qunit/data/generateJqueryMsgData.php
tests/qunit/data/mediawiki.jqueryMsg.data.js
tests/qunit/suites/resources/jquery/jquery.color.test.js
tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
tests/qunit/suites/resources/jquery/jquery.placeholder.test.js
tests/qunit/suites/resources/jquery/jquery.textSelection.test.js
tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.test.js

diff --git a/.jscsrc b/.jscsrc
new file mode 100644 (file)
index 0000000..b5481ea
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,30 @@
+{
+       "requireCurlyBraces": ["if", "else", "for", "while", "do"],
+       "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "function"],
+       "requireParenthesesAroundIIFE": true,
+       "requireSpacesInFunctionExpression": {
+               "beforeOpeningCurlyBrace": true
+       },
+       "requireMultipleVarDecl": true,
+       "disallowEmptyBlocks": true,
+       "requireSpacesInsideObjectBrackets": "all",
+       "disallowSpaceAfterObjectKeys": true,
+       "requireCommaBeforeLineBreak": true,
+       "disallowLeftStickedOperators": ["?", ">", ">=", "<", "<="],
+       "disallowRightStickedOperators": ["?", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
+       "requireRightStickedOperators": ["!"],
+       "requireLeftStickedOperators": [","],
+       "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~"],
+       "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
+       "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+       "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
+       "disallowKeywords": [ "with" ],
+       "disallowMultipleLineBreaks": true,
+       "validateLineBreaks": "LF",
+       "validateQuoteMarks": "'",
+       "disallowMixedSpacesAndTabs": "smart",
+       "disallowTrailingWhitespace": true,
+       "requireLineFeedAtFileEnd": true,
+       "requireCapitalizedConstructors": true,
+       "requireDotNotation": true
+}
index a44a4d4..627f7ab 100644 (file)
@@ -1,14 +1,14 @@
 # Generated documentation
-docs/html/
-docs/js/
-resources/mediawiki.ui/docs
+docs/html/**
+docs/js/**
+resources/mediawiki.ui/docs/**
 
 # kss template for mediawiki ui documentation
-resources/styleguide-template
+resources/styleguide-template/**
 
 # third-party libs
-extensions/
-node_modules/
+extensions/**
+node_modules/**
 resources/jquery/jquery.appear.js
 resources/jquery/jquery.async.js
 resources/jquery/jquery.ba-throttle-debounce.js
@@ -26,14 +26,15 @@ resources/jquery/jquery.qunit.js
 resources/jquery/jquery.validate.js
 resources/jquery/jquery.xmldom.js
 resources/jquery.chosen/chosen.jquery.js
-resources/jquery.effects/
-resources/jquery.tipsy/
-resources/jquery.ui/
-resources/mediawiki.libs/
-resources/oojs/
-resources/oojs-ui/
-resources/sinonjs/
-resources/moment/
+resources/jquery.effects/**
+resources/jquery.tipsy/**
+resources/jquery.ui/**
+resources/mediawiki.libs/**
+resources/moment/**
+resources/oojs-ui/**
+resources/oojs/**
+resources/sinonjs/**
+tests/frontend/node_modules/**
 
 # github.com/jshint/jshint/issues/729
 tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js
index c4e265a..9d6c4d2 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -2,24 +2,20 @@
        /* Common */
 
        // Enforcing
-       "camelcase": true,
-       "curly": true,
        "eqeqeq": true,
-       "immed": true,
        "latedef": true,
-       "newcap": true,
        "noarg": true,
-       "noempty": true,
        "nonew": true,
-       "quotmark": "single",
-       "trailing": true,
        "undef": true,
        "unused": true,
-       // Legacy
-       "onevar": true,
 
        /* Local */
 
+       // FIXME: Deprecated, handle these with node-jscs instead.
+       // Handled here because we still have inline overrides in some places.
+       "camelcase": true,
+       "nomen": true,
+
        // Enforcing
        "bitwise": true,
        "forin": false,
@@ -31,8 +27,6 @@
        "multistr": true,
        // Environment
        "browser": true,
-       // Legacy
-       "nomen": true,
 
        "predef": [
                "mediaWiki",
index 49a932a..a473f61 100644 (file)
@@ -146,4 +146,4 @@ $.fn.autoEllipsis = function ( options ) {
        } );
 };
 
-}( jQuery ) );
\ No newline at end of file
+}( jQuery ) );
index a8c0b06..b398fdd 100644 (file)
                                $el.attr( 'maxlength', elLimit );
                        }
 
-
                        // Safe base value, used to determine the path between the previous state
                        // and the state that triggered the event handler below - and enforce the
                        // limit approppiately (e.g. don't chop from the end if text was inserted
index 9c6f9ec..37bf176 100644 (file)
@@ -12,7 +12,7 @@
                // By Blair Mitchelmore
                // http://jquery.offput.ca/highlightFade/
                // Parse strings looking for color tuples [255,255,255]
-               getRGB : function ( color ) {
+               getRGB: function ( color ) {
                        /*jshint boss:true */
                        var result;
 
@@ -28,7 +28,7 @@
 
                        // Look for rgb(num%,num%,num%)
                        if (result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color)) {
-                               return [parseFloat(result[1],10)*2.55, parseFloat(result[2],10)*2.55, parseFloat(result[3])*2.55];
+                               return [parseFloat(result[1],10) * 2.55, parseFloat(result[2],10) * 2.55, parseFloat(result[3]) * 2.55];
                        }
 
                        // Look for #a0b1c2
@@ -38,7 +38,7 @@
 
                        // Look for #fff
                        if (result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color)) {
-                               return [parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16)];
+                               return [parseInt(result[1] + result[1],16), parseInt(result[2] + result[2],16), parseInt(result[3] + result[3],16)];
                        }
 
                        // Look for rgba(0, 0, 0, 0) == transparent in Safari 3
                                        if ( t > 1 ) {
                                                t -= 1;
                                        }
-                                       if ( t < 1/6 ) {
+                                       if ( t < 1 / 6 ) {
                                                return p + (q - p) * 6 * t;
                                        }
-                                       if ( t < 1/2 ) {
+                                       if ( t < 1 / 2 ) {
                                                return q;
                                        }
-                                       if ( t < 2/3 ) {
-                                               return p + (q - p) * (2/3 - t) * 6;
+                                       if ( t < 2 / 3 ) {
+                                               return p + (q - p) * (2 / 3 - t) * 6;
                                        }
                                        return p;
                                };
 
                                q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                                p = 2 * l - q;
-                               r = hue2rgb( p, q, h + 1/3 );
+                               r = hue2rgb( p, q, h + 1 / 3 );
                                g = hue2rgb( p, q, h );
-                               b = hue2rgb( p, q, h - 1/3 );
+                               b = hue2rgb( p, q, h - 1 / 3 );
                        }
 
                        return [r * 255, g * 255, b * 255];
                getColorBrightness: function ( currentColor, mod ) {
                        var rgbArr = $.colorUtil.getRGB( currentColor ),
                                hslArr = $.colorUtil.rgbToHsl(rgbArr[0], rgbArr[1], rgbArr[2] );
-                       rgbArr = $.colorUtil.hslToRgb(hslArr[0], hslArr[1], hslArr[2]+mod);
+                       rgbArr = $.colorUtil.hslToRgb(hslArr[0], hslArr[1], hslArr[2] + mod);
 
                        return 'rgb(' +
                                [parseInt( rgbArr[0], 10), parseInt( rgbArr[1], 10 ), parseInt( rgbArr[2], 10 )].join( ',' ) +
index e0d9de2..8c8fb56 100644 (file)
@@ -146,12 +146,12 @@ $.fn.localize = function ( options ) {
        } );
 
        // HTML, Text for elements which cannot have children e.g. OPTION
-       $target.find( '[data-msg-text]' ).each( function() {
+       $target.find( '[data-msg-text]' ).each( function () {
                var $el = $( this );
                $el.text( msg( options, $el.attr( 'data-msg-text' ) ) );
        } );
 
-       $target.find( '[data-msg-html]' ).each( function() {
+       $target.find( '[data-msg-html]' ).each( function () {
                var $el = $( this );
                $el.html( msg( options, $el.attr( 'data-msg-html' ) ) );
        } );
index 5020b37..6f7ada3 100644 (file)
@@ -13,7 +13,7 @@
  * @version 2.1.0
  * @license MIT
  */
-(function($) {
+(function ($) {
 
        var isInputSupported = 'placeholder' in document.createElement('input'),
                isTextareaSupported = 'placeholder' in document.createElement('textarea'),
 
        if (isInputSupported && isTextareaSupported) {
 
-               placeholder = prototype.placeholder = function(text) {
+               placeholder = prototype.placeholder = function (text) {
                        var hasArgs = arguments.length;
 
-                       if( hasArgs ) {
+                       if (hasArgs) {
                                changePlaceholder.call(this, text);
                        }
 
 
        } else {
 
-               placeholder = prototype.placeholder = function(text) {
+               placeholder = prototype.placeholder = function (text) {
                        var $this = this,
                                hasArgs = arguments.length;
 
-                       if(hasArgs) {
+                       if (hasArgs) {
                                changePlaceholder.call(this, text);
                        }
 
-
                        $this
                                .filter((isInputSupported ? 'textarea' : ':input') + '[placeholder]')
-                               .filter(function() {
+                               .filter(function () {
                                        return !$(this).data('placeholder-enabled');
                                })
                                .bind({
@@ -66,7 +65,7 @@
                placeholder.textarea = isTextareaSupported;
 
                hooks = {
-                       'get': function(element) {
+                       'get': function (element) {
                                var $element = $(element),
                                        $passwordInput = $element.data('placeholder-password');
                                if ($passwordInput) {
@@ -75,7 +74,7 @@
 
                                return $element.data('placeholder-enabled') && $element.hasClass('placeholder') ? '' : element.value;
                        },
-                       'set': function(element, value) {
+                       'set': function (element, value) {
                                var $element = $(element),
                                        $passwordInput = $element.data('placeholder-password');
                                if ($passwordInput) {
@@ -95,7 +94,7 @@
                                                setPlaceholder.call(element);
                                        }
                                } else if ($element.hasClass('placeholder')) {
-                                       if(!clearPlaceholder.call(element, true, value)) {
+                                       if (!clearPlaceholder.call(element, true, value)) {
                                                element.value = value;
                                        }
                                } else {
                        propHooks.value = hooks;
                }
 
-               $(function() {
+               $(function () {
                        // Look for forms
-                       $(document).delegate('form', 'submit.placeholder', function() {
+                       $(document).delegate('form', 'submit.placeholder', function () {
                                // Clear the placeholder values so they don't get submitted
                                var $inputs = $('.placeholder', this).each(clearPlaceholder);
-                               setTimeout(function() {
+                               setTimeout(function () {
                                        $inputs.each(setPlaceholder);
                                }, 10);
                        });
                });
 
                // Clear placeholder values upon page reload
-               $(window).bind('beforeunload.placeholder', function() {
-                       $('.placeholder').each(function() {
+               $(window).bind('beforeunload.placeholder', function () {
+                       $('.placeholder').each(function () {
                                this.value = '';
                        });
                });
                // Return an object of element attributes
                var newAttrs = {},
                        rinlinejQuery = /^jQuery\d+$/;
-               $.each(elem.attributes, function(i, attr) {
+               $.each(elem.attributes, function (i, attr) {
                        if (attr.specified && !rinlinejQuery.test(attr.name)) {
                                newAttrs[attr.name] = attr.value;
                        }
                        } else {
                                input.value = '';
                                $input.removeClass('placeholder');
-                               if(input === safeActiveElement()) {
+                               if (input === safeActiveElement()) {
                                        input.select();
                                }
                        }
        function changePlaceholder(text) {
                var hasArgs = arguments.length,
                        $input = this;
-               if(hasArgs) {
-                       if($input.attr('placeholder') !== text) {
+               if (hasArgs) {
+                       if ($input.attr('placeholder') !== text) {
                                $input.prop('placeholder', text);
-                               if($input.hasClass('placeholder')) {
+                               if ($input.hasClass('placeholder')) {
                                        $input[0].value = text;
                                }
                        }
index 20e6678..86fcaea 100644 (file)
@@ -79,7 +79,6 @@
                isEmptyObject: $.isEmptyObject
        };
 
-
        /**
         * CompletenessTest
         * @constructor
index f967a1d..f9a225f 100644 (file)
@@ -142,7 +142,7 @@ $.suggestions = {
                        i, expWidth, matchedText, maxWidth, text;
 
                // Validate creation using fallback values
-               switch( property ) {
+               switch ( property ) {
                        case 'fetch':
                        case 'cancel':
                        case 'special':
@@ -281,7 +281,7 @@ $.suggestions = {
                                                }
                                                // Apply new width for results box, if any
                                                if ( expWidth > context.data.$container.width() ) {
-                                                       maxWidth = context.config.maxExpandFactor*context.data.$textbox.width();
+                                                       maxWidth = context.config.maxExpandFactor * context.data.$textbox.width();
                                                        context.data.$container.width( Math.min( expWidth, maxWidth ) );
                                                }
                                                // autoEllipse the results. Has to be done after changing the width
index f9ee268..af0d897 100644 (file)
@@ -68,8 +68,6 @@
  */
 
 ( function ( $, mw ) {
-       /*jshint onevar:false */
-
        /* Local scope */
 
        var ts,
@@ -78,8 +76,9 @@
        /* Parser utility functions */
 
        function getParserById( name ) {
-               var len = parsers.length;
-               for ( var i = 0; i < len; i++ ) {
+               var i,
+                       len = parsers.length;
+               for ( i = 0; i < len; i++ ) {
                        if ( parsers[i].id.toLowerCase() === name.toLowerCase() ) {
                                return parsers[i];
                        }
        }
 
        function appendToTable( table, cache ) {
-               var row = cache.row,
+               var i, pos, l, j,
+                       row = cache.row,
                        normalized = cache.normalized,
                        totalRows = normalized.length,
                        checkCell = ( normalized[0].length - 1 ),
                        fragment = document.createDocumentFragment();
 
-               for ( var i = 0; i < totalRows; i++ ) {
-                       var pos = normalized[i][checkCell];
+               for ( i = 0; i < totalRows; i++ ) {
+                       pos = normalized[i][checkCell];
 
-                       var l = row[pos].length;
+                       l = row[pos].length;
 
-                       for ( var j = 0; j < l; j++ ) {
+                       for ( j = 0; j < l; j++ ) {
                                fragment.appendChild( row[pos][j] );
                        }
 
         * @param $table jQuery object for a <table>
         */
        function emulateTHeadAndFoot( $table ) {
-               var $rows = $table.find( '> tbody > tr' );
-               if( !$table.get(0).tHead ) {
-                       var $thead = $( '<thead>' );
+               var $thead, $tfoot, i, len,
+                       $rows = $table.find( '> tbody > tr' );
+               if ( !$table.get(0).tHead ) {
+                       $thead = $( '<thead>' );
                        $rows.each( function () {
-                               if ( $(this).children( 'td' ).length > 0 ) {
+                               if ( $(this).children( 'td' ).length ) {
                                        // This row contains a <td>, so it's not a header row
                                        // Stop here
                                        return false;
                        } );
                        $table.find(' > tbody:first').before( $thead );
                }
-               if( !$table.get(0).tFoot ) {
-                       var $tfoot = $( '<tfoot>' );
-                       var len = $rows.length;
-                       for ( var i = len-1; i >= 0; i-- ) {
-                               if( $( $rows[i] ).children( 'td' ).length > 0 ){
+               if ( !$table.get(0).tFoot ) {
+                       $tfoot = $( '<tfoot>' );
+                       len = $rows.length;
+                       for ( i = len - 1; i >= 0; i-- ) {
+                               if ( $( $rows[i] ).children( 'td' ).length ){
                                        break;
                                }
                                $tfoot.prepend( $( $rows[i] ));
                return false;
        }
 
-
        function uniqueElements( array ) {
                var uniques = [];
                $.each( array, function( index, elem ) {
        }
 
        function multisort( table, sortList, cache ) {
-               var sortFn = [];
-               var len = sortList.length;
-               for ( var i = 0; i < len; i++ ) {
+               var i,
+                       sortFn = [],
+                       len = sortList.length;
+               for ( i = 0; i < len; i++ ) {
                        sortFn[i] = ( sortList[i][1] ) ? sortTextDesc : sortText;
                }
                cache.normalized.sort( function ( array1, array2 ) {
-                       var col, ret;
-                       for ( var i = 0; i < len; i++ ) {
+                       var i, col, ret;
+                       for ( i = 0; i < len; i++ ) {
                                col = sortList[i][0];
                                ret = sortFn[i].call( this, array1[col], array2[col] );
                                if ( ret !== 0 ) {
        }
 
        function buildTransformTable() {
-               var digits = '0123456789,.'.split( '' );
-               var separatorTransformTable = mw.config.get( 'wgSeparatorTransformTable' );
-               var digitTransformTable = mw.config.get( 'wgDigitTransformTable' );
+               var ascii, localised, i, digitClass,
+                       digits = '0123456789,.'.split( '' ),
+                       separatorTransformTable = mw.config.get( 'wgSeparatorTransformTable' ),
+                       digitTransformTable = mw.config.get( 'wgDigitTransformTable' );
+
                if ( separatorTransformTable === null || ( separatorTransformTable[0] === '' && digitTransformTable[2] === '' ) ) {
                        ts.transformTable = false;
                } else {
                        ts.transformTable = {};
 
                        // Unpack the transform table
-                       var ascii = separatorTransformTable[0].split( '\t' ).concat( digitTransformTable[0].split( '\t' ) );
-                       var localised = separatorTransformTable[1].split( '\t' ).concat( digitTransformTable[1].split( '\t' ) );
+                       ascii = separatorTransformTable[0].split( '\t' ).concat( digitTransformTable[0].split( '\t' ) );
+                       localised = separatorTransformTable[1].split( '\t' ).concat( digitTransformTable[1].split( '\t' ) );
 
                        // Construct regex for number identification
-                       for ( var i = 0; i < ascii.length; i++ ) {
+                       for ( i = 0; i < ascii.length; i++ ) {
                                ts.transformTable[localised[i]] = ascii[i];
                                digits.push( $.escapeRE( localised[i] ) );
                        }
                }
-               var digitClass = '[' + digits.join( '', digits ) + ']';
+               digitClass = '[' + digits.join( '', digits ) + ']';
 
                // We allow a trailing percent sign, which we just strip. This works fine
                // if percents and regular numbers aren't being mixed.
        }
 
        function buildDateTable() {
-               var regex = [];
+               var i, name,
+                       regex = [];
+
                ts.monthNames = {};
 
-               for ( var i = 0; i < 12; i++ ) {
-                       var name = mw.language.months.names[i].toLowerCase();
+               for ( i = 0; i < 12; i++ ) {
+                       name = mw.language.months.names[i].toLowerCase();
                        ts.monthNames[name] = i + 1;
                        regex.push( $.escapeRE( name ) );
                        name = mw.language.months.genitive[i].toLowerCase();
         * @param $table jQuery object for a <table>
         */
        function explodeRowspans( $table ) {
-               var rowspanCells = $table.find( '> tbody > tr > [rowspan]' ).get();
+               var spanningRealCellIndex, rowSpan, colSpan,
+                       cell, i, $tds, $clone, $nextRows,
+                       rowspanCells = $table.find( '> tbody > tr > [rowspan]' ).get();
 
                // Short circuit
                if ( !rowspanCells.length ) {
                // account colspans. We also cache the rowIndex to avoid having to take
                // cell.parentNode.rowIndex in the sorting function below.
                $table.find( '> tbody > tr' ).each( function () {
-                       var col = 0;
-                       var l = this.cells.length;
-                       for ( var i = 0; i < l; i++ ) {
+                       var i,
+                               col = 0,
+                               l = this.cells.length;
+                       for ( i = 0; i < l; i++ ) {
                                this.cells[i].realCellIndex = col;
                                this.cells[i].realRowIndex = this.rowIndex;
                                col += this.cells[i].colSpan;
                }
                resortCells();
 
-               var spanningRealCellIndex, rowSpan, colSpan;
                function filterfunc() {
                        return this.realCellIndex >= spanningRealCellIndex;
                }
                                resortCells();
                        }
 
-                       var cell = rowspanCells.shift();
+                       cell = rowspanCells.shift();
                        rowSpan = cell.rowSpan;
                        colSpan = cell.colSpan;
                        spanningRealCellIndex = cell.realCellIndex;
                        cell.rowSpan = 1;
-                       var $nextRows = $( cell ).parent().nextAll();
-                       for ( var i = 0; i < rowSpan - 1; i++ ) {
-                               var $tds = $( $nextRows[i].cells ).filter( filterfunc );
-                               var $clone = $( cell ).clone();
+                       $nextRows = $( cell ).parent().nextAll();
+                       for ( i = 0; i < rowSpan - 1; i++ ) {
+                               $tds = $( $nextRows[i].cells ).filter( filterfunc );
+                               $clone = $( cell ).clone();
                                $clone[0].realCellIndex = spanningRealCellIndex;
                                if ( $tds.length ) {
                                        $tds.each( fixTdCellIndex );
                ts.collationTable = mw.config.get( 'tableSorterCollation' );
                ts.collationRegex = null;
                if ( ts.collationTable ) {
-                       var keys = [];
+                       var key,
+                               keys = [];
 
                        // Build array of key names
-                       for ( var key in ts.collationTable ) {
-                               if ( ts.collationTable.hasOwnProperty(key) ) { //to be safe
+                       for ( key in ts.collationTable ) {
+                               // Check hasOwn to be safe
+                               if ( ts.collationTable.hasOwnProperty(key) ) {
                                        keys.push(key);
                                }
                        }
-                       if (keys.length) {
+                       if ( keys.length ) {
                                ts.collationRegex = new RegExp( '[' + keys.join( '' ) + ']', 'ig' );
                        }
                }
                        construct: function ( $tables, settings ) {
                                return $tables.each( function ( i, table ) {
                                        // Declare and cache.
-                                       var $headers, cache, config,
+                                       var $headers, cache, config, sortCSS, sortMsg,
                                                $table = $( table ),
                                                firstTime = true;
 
                                        $.data( table, 'tablesorter', { config: config } );
 
                                        // Get the CSS class names, could be done else where.
-                                       var sortCSS = [ config.cssDesc, config.cssAsc ];
-                                       var sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ];
+                                       sortCSS = [ config.cssDesc, config.cssAsc ];
+                                       sortMsg = [ mw.msg( 'sort-descending' ), mw.msg( 'sort-ascending' ) ];
 
                                        // Build headers
                                        $headers = buildHeaders( table, sortMsg );
                                                        this.order = this.count % 2;
                                                        this.count++;
 
-                                                       var cell = this;
+                                                       var cell, columns, newSortList, i;
+
+                                                       cell = this;
                                                        // Get current column index
-                                                       var columns = table.headerToColumns[ this.headerIndex ];
-                                                       var newSortList = $.map( columns, function (c) {
+                                                       columns = table.headerToColumns[ this.headerIndex ];
+                                                       newSortList = $.map( columns, function ( c ) {
                                                                // jQuery "helpfully" flattens the arrays...
                                                                return [[c, cell.order]];
                                                        });
                                                        // Index of first column belonging to this header
-                                                       var i = columns[0];
+                                                       i = columns[0];
 
                                                        if ( !e[config.sortMultiSortKey] ) {
                                                                // User only wants to sort on one column set
                                         *
                                         * @param sortList {Array} (optional) List of sort objects.
                                         */
-                                       $table.data( 'tablesorter' ).sort = function( sortList ) {
+                                       $table.data( 'tablesorter' ).sort = function ( sortList ) {
 
                                                if ( firstTime ) {
                                                        setupForFirstSort();
index 2b5a440..7262fe6 100644 (file)
@@ -2,8 +2,6 @@
  * These plugins provide extra functionality for interaction with textareas.
  */
 ( function ( $ ) {
-       /*jshint noempty:false */
-
        if ( document.selection && document.selection.createRange ) {
                // On IE, patch the focus() method to restore the windows' scroll position
                // (bug 32241)
@@ -13,7 +11,7 @@
                                        var $w, state, result;
                                        if ( arguments.length === 0 ) {
                                                $w = $( window );
-                                               state = {top: $w.scrollTop(), left: $w.scrollLeft()};
+                                               state = { top: $w.scrollTop(), left: $w.scrollLeft() };
                                                result = jqFocus.apply( this, arguments );
                                                window.scrollTo( state.top, state.left );
                                                return result;
@@ -73,7 +71,6 @@
                                        el = this.get( 0 );
 
                                if ( $(el).is( ':hidden' ) ) {
-                                       // Do nothing
                                        retval = '';
                                } else if ( document.selection && document.selection.createRange ) {
                                        activateElementOnIE( el );
                                        }
 
                                        isSample = false;
-                                       if ( this.style.display === 'none' ) {
-                                               // Do nothing
-                                       } else if ( document.selection && document.selection.createRange ) {
-                                               // IE
+                                       // Do nothing if display none
+                                       if ( this.style.display !== 'none' ) {
+                                               if ( document.selection && document.selection.createRange ) {
+                                                       // IE
 
-                                               // Note that IE9 will trigger the next section unless we check this first.
-                                               // See bug 35201.
+                                                       // Note that IE9 will trigger the next section unless we check this first.
+                                                       // See bug 35201.
 
-                                               activateElementOnIE( this );
-                                               if ( context ) {
-                                                       context.fn.restoreCursorAndScrollTop();
-                                               }
-                                               if ( options.selectionStart !== undefined ) {
-                                                       $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } );
-                                               }
+                                                       activateElementOnIE( this );
+                                                       if ( context ) {
+                                                               context.fn.restoreCursorAndScrollTop();
+                                                       }
+                                                       if ( options.selectionStart !== undefined ) {
+                                                               $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } );
+                                                       }
 
-                                               selText = $(this).textSelection( 'getSelection' );
-                                               scrollTop = this.scrollTop;
-                                               range = document.selection.createRange();
+                                                       selText = $(this).textSelection( 'getSelection' );
+                                                       scrollTop = this.scrollTop;
+                                                       range = document.selection.createRange();
 
-                                               checkSelectedText();
-                                               insertText = pre + selText + post;
-                                               if ( options.splitlines ) {
-                                                       insertText = doSplitLines( selText, pre, post );
-                                               }
-                                               if ( options.ownline && range.moveStart ) {
-                                                       range2 = document.selection.createRange();
-                                                       range2.collapse();
-                                                       range2.moveStart( 'character', -1 );
-                                                       // FIXME: Which check is correct?
-                                                       if ( range2.text !== '\r' && range2.text !== '\n' && range2.text !== '' ) {
-                                                               insertText = '\n' + insertText;
-                                                               pre += '\n';
+                                                       checkSelectedText();
+                                                       insertText = pre + selText + post;
+                                                       if ( options.splitlines ) {
+                                                               insertText = doSplitLines( selText, pre, post );
                                                        }
-                                                       range3 = document.selection.createRange();
-                                                       range3.collapse( false );
-                                                       range3.moveEnd( 'character', 1 );
-                                                       if ( range3.text !== '\r' && range3.text !== '\n' && range3.text !== '' ) {
-                                                               insertText += '\n';
-                                                               post += '\n';
+                                                       if ( options.ownline && range.moveStart ) {
+                                                               range2 = document.selection.createRange();
+                                                               range2.collapse();
+                                                               range2.moveStart( 'character', -1 );
+                                                               // FIXME: Which check is correct?
+                                                               if ( range2.text !== '\r' && range2.text !== '\n' && range2.text !== '' ) {
+                                                                       insertText = '\n' + insertText;
+                                                                       pre += '\n';
+                                                               }
+                                                               range3 = document.selection.createRange();
+                                                               range3.collapse( false );
+                                                               range3.moveEnd( 'character', 1 );
+                                                               if ( range3.text !== '\r' && range3.text !== '\n' && range3.text !== '' ) {
+                                                                       insertText += '\n';
+                                                                       post += '\n';
+                                                               }
                                                        }
-                                               }
 
-                                               range.text = insertText;
-                                               if ( isSample && options.selectPeri && range.moveStart ) {
-                                                       range.moveStart( 'character', - post.length - selText.length );
-                                                       range.moveEnd( 'character', - post.length );
-                                               }
-                                               range.select();
-                                               // Restore the scroll position
-                                               this.scrollTop = scrollTop;
-                                       } else if ( this.selectionStart || this.selectionStart === 0 ) {
-                                               // Mozilla/Opera
-
-                                               $(this).focus();
-                                               if ( options.selectionStart !== undefined ) {
-                                                       $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } );
-                                               }
+                                                       range.text = insertText;
+                                                       if ( isSample && options.selectPeri && range.moveStart ) {
+                                                               range.moveStart( 'character', -post.length - selText.length );
+                                                               range.moveEnd( 'character', -post.length );
+                                                       }
+                                                       range.select();
+                                                       // Restore the scroll position
+                                                       this.scrollTop = scrollTop;
+                                               } else if ( this.selectionStart || this.selectionStart === 0 ) {
+                                                       // Mozilla/Opera
+
+                                                       $(this).focus();
+                                                       if ( options.selectionStart !== undefined ) {
+                                                               $(this).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } );
+                                                       }
 
-                                               selText = $(this).textSelection( 'getSelection' );
-                                               startPos = this.selectionStart;
-                                               endPos = this.selectionEnd;
-                                               scrollTop = this.scrollTop;
-                                               checkSelectedText();
-                                               if ( options.selectionStart !== undefined
-                                                               && endPos - startPos !== options.selectionEnd - options.selectionStart )
-                                               {
-                                                       // This means there is a difference in the selection range returned by browser and what we passed.
-                                                       // This happens for Chrome in the case of composite characters. Ref bug #30130
-                                                       // Set the startPos to the correct position.
-                                                       startPos = options.selectionStart;
-                                               }
+                                                       selText = $(this).textSelection( 'getSelection' );
+                                                       startPos = this.selectionStart;
+                                                       endPos = this.selectionEnd;
+                                                       scrollTop = this.scrollTop;
+                                                       checkSelectedText();
+                                                       if ( options.selectionStart !== undefined
+                                                                       && endPos - startPos !== options.selectionEnd - options.selectionStart )
+                                                       {
+                                                               // This means there is a difference in the selection range returned by browser and what we passed.
+                                                               // This happens for Chrome in the case of composite characters. Ref bug #30130
+                                                               // Set the startPos to the correct position.
+                                                               startPos = options.selectionStart;
+                                                       }
 
-                                               insertText = pre + selText + post;
-                                               if ( options.splitlines ) {
-                                                       insertText = doSplitLines( selText, pre, post );
-                                               }
-                                               if ( options.ownline ) {
-                                                       if ( startPos !== 0 && this.value.charAt( startPos - 1 ) !== '\n' && this.value.charAt( startPos - 1 ) !== '\r' ) {
-                                                               insertText = '\n' + insertText;
-                                                               pre += '\n';
+                                                       insertText = pre + selText + post;
+                                                       if ( options.splitlines ) {
+                                                               insertText = doSplitLines( selText, pre, post );
                                                        }
-                                                       if ( this.value.charAt( endPos ) !== '\n' && this.value.charAt( endPos ) !== '\r' ) {
-                                                               insertText += '\n';
-                                                               post += '\n';
+                                                       if ( options.ownline ) {
+                                                               if ( startPos !== 0 && this.value.charAt( startPos - 1 ) !== '\n' && this.value.charAt( startPos - 1 ) !== '\r' ) {
+                                                                       insertText = '\n' + insertText;
+                                                                       pre += '\n';
+                                                               }
+                                                               if ( this.value.charAt( endPos ) !== '\n' && this.value.charAt( endPos ) !== '\r' ) {
+                                                                       insertText += '\n';
+                                                                       post += '\n';
+                                                               }
+                                                       }
+                                                       this.value = this.value.substring( 0, startPos ) + insertText +
+                                                               this.value.substring( endPos, this.value.length );
+                                                       // Setting this.value scrolls the textarea to the top, restore the scroll position
+                                                       this.scrollTop = scrollTop;
+                                                       if ( window.opera ) {
+                                                               pre = pre.replace( /\r?\n/g, '\r\n' );
+                                                               selText = selText.replace( /\r?\n/g, '\r\n' );
+                                                               post = post.replace( /\r?\n/g, '\r\n' );
+                                                       }
+                                                       if ( isSample && options.selectPeri && !options.splitlines ) {
+                                                               this.selectionStart = startPos + pre.length;
+                                                               this.selectionEnd = startPos + pre.length + selText.length;
+                                                       } else {
+                                                               this.selectionStart = startPos + insertText.length;
+                                                               this.selectionEnd = this.selectionStart;
                                                        }
-                                               }
-                                               this.value = this.value.substring( 0, startPos ) + insertText +
-                                                       this.value.substring( endPos, this.value.length );
-                                               // Setting this.value scrolls the textarea to the top, restore the scroll position
-                                               this.scrollTop = scrollTop;
-                                               if ( window.opera ) {
-                                                       pre = pre.replace( /\r?\n/g, '\r\n' );
-                                                       selText = selText.replace( /\r?\n/g, '\r\n' );
-                                                       post = post.replace( /\r?\n/g, '\r\n' );
-                                               }
-                                               if ( isSample && options.selectPeri && !options.splitlines ) {
-                                                       this.selectionStart = startPos + pre.length;
-                                                       this.selectionEnd = startPos + pre.length + selText.length;
-                                               } else {
-                                                       this.selectionStart = startPos + insertText.length;
-                                                       this.selectionEnd = this.selectionStart;
                                                }
                                        }
                                        $(this).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline,
                        setSelection: function ( options ) {
                                return this.each( function () {
                                        var selection, length, newLines;
-                                       if ( $(this).is( ':hidden' ) ) {
-                                               // Do nothing
-                                       } else if ( this.selectionStart || this.selectionStart === 0 ) {
-                                               // Opera 9.0 doesn't allow setting selectionStart past
-                                               // selectionEnd; any attempts to do that will be ignored
-                                               // Make sure to set them in the right order
-                                               if ( options.start > this.selectionEnd ) {
-                                                       this.selectionEnd = options.end;
-                                                       this.selectionStart = options.start;
-                                               } else {
-                                                       this.selectionStart = options.start;
-                                                       this.selectionEnd = options.end;
-                                               }
-                                       } else if ( document.body.createTextRange ) {
-                                               selection = rangeForElementIE( this );
-                                               length = this.value.length;
-                                               // IE doesn't count \n when computing the offset, so we won't either
-                                               newLines = this.value.match( /\n/g );
-                                               if ( newLines ) {
-                                                       length = length - newLines.length;
+                                       // Do nothing if hidden
+                                       if ( !$(this).is( ':hidden' ) ) {
+                                               if ( this.selectionStart || this.selectionStart === 0 ) {
+                                                       // Opera 9.0 doesn't allow setting selectionStart past
+                                                       // selectionEnd; any attempts to do that will be ignored
+                                                       // Make sure to set them in the right order
+                                                       if ( options.start > this.selectionEnd ) {
+                                                               this.selectionEnd = options.end;
+                                                               this.selectionStart = options.start;
+                                                       } else {
+                                                               this.selectionStart = options.start;
+                                                               this.selectionEnd = options.end;
+                                                       }
+                                               } else if ( document.body.createTextRange ) {
+                                                       selection = rangeForElementIE( this );
+                                                       length = this.value.length;
+                                                       // IE doesn't count \n when computing the offset, so we won't either
+                                                       newLines = this.value.match( /\n/g );
+                                                       if ( newLines ) {
+                                                               length = length - newLines.length;
+                                                       }
+                                                       selection.moveStart( 'character', options.start );
+                                                       selection.moveEnd( 'character', -length + options.end );
+
+                                                       // This line can cause an error under certain circumstances (textarea empty, no selection)
+                                                       // Silence that error
+                                                       try {
+                                                               selection.select();
+                                                       } catch ( e ) { }
                                                }
-                                               selection.moveStart( 'character', options.start );
-                                               selection.moveEnd( 'character', -length + options.end );
-
-                                               // This line can cause an error under certain circumstances (textarea empty, no selection)
-                                               // Silence that error
-                                               try {
-                                                       selection.select();
-                                               } catch ( e ) { }
                                        }
                                });
                        },
                                }
                                return this.each(function () {
                                        var scroll, range, savedRange, pos, oldScrollTop;
-                                       if ( $(this).is( ':hidden' ) ) {
-                                               // Do nothing
-                                       } else if ( this.selectionStart || this.selectionStart === 0 ) {
-                                               // Mozilla
-                                               scroll = getCaretScrollPosition( this );
-                                               if ( options.force || scroll < $(this).scrollTop() ||
-                                                               scroll > $(this).scrollTop() + $(this).height() ) {
-                                                       $(this).scrollTop( scroll );
-                                               }
-                                       } else if ( document.selection && document.selection.createRange ) {
-                                               // IE / Opera
-                                               /*
-                                                * IE automatically scrolls the selected text to the
-                                                * bottom of the textarea at range.select() time, except
-                                                * if it was already in view and the cursor position
-                                                * wasn't changed, in which case it does nothing. To
-                                                * cover that case, we'll force it to act by moving one
-                                                * character back and forth.
-                                                */
-                                               range = document.body.createTextRange();
-                                               savedRange = document.selection.createRange();
-                                               pos = $(this).textSelection( 'getCaretPosition' );
-                                               oldScrollTop = this.scrollTop;
-                                               range.moveToElementText( this );
-                                               range.collapse();
-                                               range.move( 'character', pos + 1);
-                                               range.select();
-                                               if ( this.scrollTop !== oldScrollTop ) {
-                                                       this.scrollTop += range.offsetTop;
-                                               } else if ( options.force ) {
-                                                       range.move( 'character', -1 );
+                                       // Do nothing if hidden
+                                       if ( !$(this).is( ':hidden' ) ) {
+                                               if ( this.selectionStart || this.selectionStart === 0 ) {
+                                                       // Mozilla
+                                                       scroll = getCaretScrollPosition( this );
+                                                       if ( options.force || scroll < $(this).scrollTop() ||
+                                                                       scroll > $(this).scrollTop() + $(this).height() ) {
+                                                               $(this).scrollTop( scroll );
+                                                       }
+                                               } else if ( document.selection && document.selection.createRange ) {
+                                                       // IE / Opera
+                                                       /*
+                                                        * IE automatically scrolls the selected text to the
+                                                        * bottom of the textarea at range.select() time, except
+                                                        * if it was already in view and the cursor position
+                                                        * wasn't changed, in which case it does nothing. To
+                                                        * cover that case, we'll force it to act by moving one
+                                                        * character back and forth.
+                                                        */
+                                                       range = document.body.createTextRange();
+                                                       savedRange = document.selection.createRange();
+                                                       pos = $(this).textSelection( 'getCaretPosition' );
+                                                       oldScrollTop = this.scrollTop;
+                                                       range.moveToElementText( this );
+                                                       range.collapse();
+                                                       range.move( 'character', pos + 1);
                                                        range.select();
+                                                       if ( this.scrollTop !== oldScrollTop ) {
+                                                               this.scrollTop += range.offsetTop;
+                                                       } else if ( options.force ) {
+                                                               range.move( 'character', -1 );
+                                                               range.select();
+                                                       }
+                                                       savedRange.select();
                                                }
-                                               savedRange.select();
                                        }
                                        $(this).trigger( 'scrollToPosition' );
                                } );
index 299fabd..bbffe79 100644 (file)
                        };
                }
                var $image = $( '<img>' ).attr( {
-                       width : 23,
+                       width: 23,
                        height: 22,
-                       src   : b.imageFile,
-                       alt   : b.speedTip,
-                       title : b.speedTip,
-                       id    : b.imageId || undefined,
+                       src: b.imageFile,
+                       alt: b.speedTip,
+                       title: b.speedTip,
+                       id: b.imageId || undefined,
                        'class': 'mw-toolbar-editbutton'
                } ).click( function () {
                        toolbar.insertTags( b.tagOpen, b.tagClose, b.sampleText );
index 500a47a..2a02d87 100644 (file)
@@ -70,7 +70,6 @@ jQuery( function ( $ ) {
        // Set initial state
        updateDiffRadios();
 
-
        // Prettify url output for HistoryAction submissions,
        // to cover up action=historysubmit construction.
 
index d23a937..21f40c5 100644 (file)
@@ -44,4 +44,4 @@
                $table.addClass( 'collapsed' );
        } );
 
-}( mediaWiki, jQuery ) );
\ No newline at end of file
+}( mediaWiki, jQuery ) );
index 026052c..4321b1b 100644 (file)
@@ -84,7 +84,6 @@
                                .promise( { abort: apiPromise.abort } );
                },
 
-
                /**
                 * Get the categories that a particular page on the wiki belongs to
                 * @param {mw.Title} title
index 2410091..2382aae 100644 (file)
@@ -34,7 +34,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
                        break;
                case 'illative':
                        // Double the last letter and add 'n'
-                       word += word.substr(  word.length-1 ) + 'n';
+                       word += word.substr(  word.length - 1 ) + 'n';
                        break;
                case 'inessive':
                        word += ( aou ? 'ssa' : 'ssä' );
index d1ed041..ae16f24 100644 (file)
@@ -15,7 +15,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
        switch ( form ) {
                case 'genitive': // սեռական հոլով
                        if ( word.substr( -1 ) === 'ա' ) {
-                               word = word.substr( 0, word.length -1 )  + 'այի';
+                               word = word.substr( 0, word.length - 1 ) + 'այի';
                        } else if ( word.substr( -1 ) === 'ո' ) {
                                word = word.substr( 0, word.length - 1 ) + 'ոյի';
                        } else if ( word.substr( -4 ) === 'գիրք' ) {
index 7fc171d..04b7d0a 100644 (file)
@@ -19,7 +19,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
                        word = word.replace( /tio$/i,'tionis' ); // 3rd declension singular (partly)
                        word = word.replace( /ns$/i, 'ntis' );
                        word = word.replace( /as$/i, 'atis' );
-                       word = word.replace( /es$/i ,'ei' ); // 5th declension singular
+                       word = word.replace( /es$/i'ei' ); // 5th declension singular
                        break;
                case 'accusative':
                        // only a few declensions, and even for those mostly the singular only
@@ -31,7 +31,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
                        word = word.replace( /tio$/i,'tionem' ); // 3rd declension singular (partly)
                        word = word.replace( /ns$/i, 'ntem' );
                        word = word.replace( /as$/i, 'atem');
-                       word = word.replace( /es$/i ,'em' ); // 5th declension singular
+                       word = word.replace( /es$/i'em' ); // 5th declension singular
                        break;
                case 'ablative':
                        // only a few declensions, and even for those mostly the singular only
@@ -43,7 +43,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
                        word = word.replace( /tio$/i,'tione' ); // 3rd declension singular (partly)
                        word = word.replace( /ns$/i, 'nte' );
                        word = word.replace( /as$/i, 'ate');
-                       word = word.replace( /es$/i ,'e' ); // 5th declension singular
+                       word = word.replace( /es$/i'e' ); // 5th declension singular
                        break;
        }
        return word;
index 4b50834..bdf59be 100644 (file)
@@ -30,7 +30,7 @@ mediaWiki.language.convertGrammar = function ( word, form ) {
        // Checking if word ends on 'у'. 'У' can be either consonant 'W' or vowel 'U' in cyrillic Ossetic.
        // Examples: {{grammar:genitive|аунеу}} = аунеуы, {{grammar:genitive|лæппу}} = лæппуйы.
        else if ( word.match( /у$/i ) ) {
-               if ( ! word.substring( word.length-2, word.length-1 ).match( /[аæеёиоыэюя]$/i ) ) {
+               if ( !word.substring( word.length - 2, word.length - 1 ).match( /[аæеёиоыэюя]$/i ) ) {
                        jot = 'й';
                }
        } else if ( !word.match( /[бвгджзйклмнопрстфхцчшщьъ]$/i ) ) {
index 9b63382..69f7ec5 100644 (file)
@@ -3,33 +3,33 @@
  */
 
 mediaWiki.language.convertGrammar = function ( word, form ) {
-       /*jshint noempty:false */
        var grammarForms = mediaWiki.language.getData( 'uk', 'grammarForms' );
        if ( grammarForms && grammarForms[form] ) {
                return grammarForms[form][word];
        }
        switch ( form ) {
                case 'genitive': // родовий відмінок
-                       if ( ( word.substr( word.length - 4 ) === 'вікі' ) || ( word.substr( word.length - 4 ) === 'Вікі' ) ) {
-                       } else if ( word.substr( word.length - 1 ) === 'ь' ) {
-                               word = word.substr(0, word.length - 1 ) + 'я';
-                       } else if ( word.substr( word.length - 2 ) === 'ія' ) {
-                               word = word.substr(0, word.length - 2 ) + 'ії';
-                       } else if ( word.substr( word.length - 2 ) === 'ка' ) {
-                               word = word.substr(0, word.length - 2 ) + 'ки';
-                       } else if ( word.substr( word.length - 2 ) === 'ти' ) {
-                               word = word.substr(0, word.length - 2 ) + 'тей';
-                       } else if ( word.substr( word.length - 2 ) === 'ды' ) {
-                               word = word.substr(0, word.length - 2 ) + 'дов';
-                       } else if ( word.substr( word.length - 3 ) === 'ник' ) {
-                               word = word.substr(0, word.length - 3 ) + 'ника';
+                       if ( word.substr( word.length - 4 ) !== 'вікі' && word.substr( word.length - 4 ) !== 'Вікі' ) {
+                               if ( word.substr( word.length - 1 ) === 'ь' ) {
+                                       word = word.substr(0, word.length - 1 ) + 'я';
+                               } else if ( word.substr( word.length - 2 ) === 'ія' ) {
+                                       word = word.substr(0, word.length - 2 ) + 'ії';
+                               } else if ( word.substr( word.length - 2 ) === 'ка' ) {
+                                       word = word.substr(0, word.length - 2 ) + 'ки';
+                               } else if ( word.substr( word.length - 2 ) === 'ти' ) {
+                                       word = word.substr(0, word.length - 2 ) + 'тей';
+                               } else if ( word.substr( word.length - 2 ) === 'ды' ) {
+                                       word = word.substr(0, word.length - 2 ) + 'дов';
+                               } else if ( word.substr( word.length - 3 ) === 'ник' ) {
+                                       word = word.substr(0, word.length - 3 ) + 'ника';
+                               }
                        }
                        break;
                case 'accusative': // знахідний відмінок
-                       if ( ( word.substr( word.length - 4 ) === 'вікі' ) || ( word.substr( word.length - 4 ) === 'Вікі' ) ) {
-                       }
-                       else if ( word.substr( word.length - 2 ) === 'ія' ) {
-                               word = word.substr(0, word.length - 2 ) + 'ію';
+                       if ( word.substr( word.length - 4 ) !== 'вікі' && word.substr( word.length - 4 ) !== 'Вікі' ) {
+                               if ( word.substr( word.length - 2 ) === 'ія' ) {
+                                       word = word.substr(0, word.length - 2 ) + 'ію';
+                               }
                        }
                        break;
        }
index 87018f6..a0b5569 100644 (file)
@@ -96,7 +96,7 @@ $.extend( mw.language, {
         */
        preConvertPlural: function ( forms, count ) {
                while ( forms.length < count ) {
-                       forms.push( forms[ forms.length-1 ] );
+                       forms.push( forms[ forms.length - 1 ] );
                }
                return forms;
        },
index 64efbb9..f92d372 100644 (file)
                                        imgHeight = 0;
                                }
 
-                               rows[rows.length-1][rows[rows.length-1].length] = {
+                               rows[rows.length - 1][rows[rows.length - 1].length] = {
                                        $elm: $this,
                                        width: $this.outerWidth(),
                                        imgWidth: imgWidth,
-                                       aspect: imgWidth / imgHeight, // XXX: can divide by 0 ever happen?
+                                       // XXX: can divide by 0 ever happen?
+                                       aspect: imgWidth / imgHeight,
                                        captionWidth: $this.children().children( 'div.gallerytextwrapper' ).width(),
                                        height: imgHeight
                                };
                                                // Also on the off chance there is a bug in this
                                                // code, would prevent accidentally expanding to
                                                // be 10 billion pixels wide.
-                                               mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight/curRowHeight );
+                                               mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight / curRowHeight );
                                                if ( i === rows.length - 1 ) {
                                                        // If its the last row, and we can't fit it,
                                                        // don't make the entire row huge.
                                        }
                                        if ( preferredHeight < 5 ) {
                                                // Well something clearly went wrong...
-                                               mw.log( {maxWidth: maxWidth, combinedPadding: combinedPadding, combinedAspect: combinedAspect, wantedWidth: wantedWidth } );
+                                               mw.log( {
+                                                       maxWidth: maxWidth,
+                                                       combinedPadding: combinedPadding,
+                                                       combinedAspect: combinedAspect,
+                                                       wantedWidth: wantedWidth
+                                               } );
                                                mw.log( 'mw.page.gallery: [BUG!] Fitting row ' + i + ' to too small a size: ' + preferredHeight );
                                                // Skip this row.
                                                continue;
                                                imageElm = $imageElm.length ? $imageElm[0] : null;
                                                $caption = $outerDiv.find( 'div.gallerytextwrapper' );
 
-
                                                // Since we are going to re-adjust the height, the vertical
                                                // centering margins need to be reset.
                                                $imageDiv.children( 'div' ).css( 'margin', '0px auto' );
index 8a4d526..50301bd 100644 (file)
@@ -90,4 +90,4 @@
                        }
                }
        } );
-}( mediaWiki, jQuery ) );
\ No newline at end of file
+}( mediaWiki, jQuery ) );
index 23d7837..2cd27af 100644 (file)
@@ -128,7 +128,7 @@ jQuery( function ( $ ) {
        // make the selected tab visible.
        hash = window.location.hash;
        if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) {
-               switchPrefTab( hash.replace( '#mw-prefsection-' , '' ) );
+               switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
        }
 
        // In browsers that support the onhashchange event we will not bind click
@@ -140,7 +140,7 @@ jQuery( function ( $ ) {
        if ( 'onhashchange' in window &&
                ( document.documentMode === undefined || document.documentMode >= 8 )
        ) {
-               $( window ).on( 'hashchange' , function () {
+               $( window ).on( 'hashchange', function () {
                        var hash = window.location.hash;
                        if ( hash.match( /^#mw-prefsection-[\w\-]+/ ) ) {
                                switchPrefTab( hash.replace( '#mw-prefsection-', '' ) );
index 4ee3de3..8a5ff59 100644 (file)
                                };
                                img.src = dataURL;
                        }, mw.config.get( 'wgFileCanRotate' ) ? function ( data ) {
-                               /*jshint camelcase: false, nomen: false */
+                               /*jshint camelcase:false, nomen:false */
                                try {
                                        meta = mw.libs.jpegmeta( data, file.fileName );
                                        meta._binary_data = null;
                        return true;
                }
 
-
                /**
                 * Initialization
                 */
index 32c3f1e..8a7aec2 100644 (file)
                                .replace( rUnderscoreTrim, '' );
                }
 
-
                // Reject illegal characters
                if ( title.match( rInvalid ) ) {
                        return false;
                };
        }() );
 
-
        /* Static members */
 
        /**
         */
        Title.prototype.toString = Title.prototype.getPrefixedDb;
 
-
        /**
         * @alias #getPrefixedText
         * @method
index b6cc2b7..1b183d9 100644 (file)
                        $( '<colgroup>' ).appendTo( $table );
                        $( '<colgroup>' ).css( 'width', 350 ).appendTo( $table );
 
-
                        entryTypeText = function ( entryType ) {
                                switch ( entryType ) {
                                        case 'log':
                                .appendTo( $table );
                        }
 
-
                        return $table;
                },
 
index 6dd4f88..90a6425 100644 (file)
                 * The section of the dialog to show.
                 */
                display: function ( s ) {
-                       this.$dialog.dialog( { buttons:{} } ); // hide the buttons
-                       this.$dialog.find( '.feedback-mode' ).hide(); // hide everything
-                       this.$dialog.find( '.feedback-' + s ).show(); // show the desired div
+                       // Hide the buttons
+                       this.$dialog.dialog( { buttons: {} } );
+                       // Hide everything
+                       this.$dialog.find( '.feedback-mode' ).hide();
+                       // Show the desired div
+                       this.$dialog.find( '.feedback-' + s ).show();
                },
 
                /**
index 85669fb..3a6869e 100644 (file)
        var oldParser,
                slice = Array.prototype.slice,
                parserDefaults = {
-                       magic : {
-                               'SITENAME' : mw.config.get( 'wgSiteName' )
+                       magic: {
+                               'SITENAME': mw.config.get( 'wgSiteName' )
                        },
                        // This is a whitelist based on, but simpler than, Sanitizer.php.
                        // Self-closing tags are not currently supported.
-                       allowedHtmlElements : [
+                       allowedHtmlElements: [
                                'b',
                                'i'
                        ],
                        // Key tag name, value allowed attributes for that tag.
                        // See Sanitizer::setupAttributeWhitelist
-                       allowedHtmlCommonAttributes : [
+                       allowedHtmlCommonAttributes: [
                                // HTML
                                'id',
                                'class',
@@ -41,9 +41,9 @@
                        // Attributes allowed for specific elements.
                        // Key is element name in lower case
                        // Value is array of allowed attributes for that element
-                       allowedHtmlAttributesByElement : {},
-                       messages : mw.messages,
-                       language : mw.language,
+                       allowedHtmlAttributesByElement: {},
+                       messages: mw.messages,
+                       language: mw.language,
 
                        // Same meaning as in mediawiki.js.
                        //
index e35fcd1..03f133e 100644 (file)
@@ -91,7 +91,7 @@ relativeforfloats = window.relativeforfloats = function () {
 setrelative = window.setrelative = function ( nodes ) {
        var i = 0;
        while ( i < nodes.length ) {
-               if( ( ( nodes[i].style.float && nodes[i].style.float !== ( 'none' ) ||
+               if ( ( ( nodes[i].style.float && nodes[i].style.float !== ( 'none' ) ||
                        ( nodes[i].align && nodes[i].align !== ( 'none' ) ) ) &&
                        ( !nodes[i].style.position || nodes[i].style.position !== 'relative' ) ) )
                {
@@ -135,7 +135,7 @@ window.onbeforeprint = function () {
        }
 };
 
-window.onafterprint = function() {
+window.onafterprint = function () {
        for ( var i = 0; i < expandedURLs.length; i++ ) {
                if ( expandedURLs[i] ) {
                        expandedURLs[i].removeNode( true );
index ca74b38..c017e3c 100644 (file)
@@ -5,7 +5,7 @@
  * http://www.modernmethod.com/sajax/
  */
 
-/*jshint camelcase:false, onevar:false */
+/*jshint camelcase:false */
 /*global alert */
 ( function ( mw ) {
 
@@ -89,9 +89,7 @@ function createXhr() {
  * with id = showFoo
  */
 function doAjaxRequest( func_name, args, target ) {
-       var i, x;
-       var uri;
-       var post_data;
+       var i, x, uri, post_data;
        uri = mw.util.wikiScript() + '?action=ajax';
        if ( window.sajax_request_type === 'GET' ) {
                if ( uri.indexOf( '?' ) === -1 ) {
@@ -146,7 +144,7 @@ function doAjaxRequest( func_name, args, target ) {
                } else if ( typeof target === 'object' ) {
                        if ( target.tagName === 'INPUT' ) {
                                if ( x.status === 200 ) {
-                                       target.value= x.responseText;
+                                       target.value = x.responseText;
                                }
                                //else alert( 'Error: ' + x.status + ' ' + x.statusText + ' (' + x.responseText + ')' );
                        } else {
@@ -173,8 +171,9 @@ function doAjaxRequest( func_name, args, target ) {
  * @return {boolean} Whether the browser supports AJAX
  */
 function wfSupportsAjax() {
-       var request = createXhr();
-       var supportsAjax = request ? true : false;
+       var request = createXhr(),
+               supportsAjax = request ? true : false;
+
        request = undefined;
        return supportsAjax;
 }
index fb8edc1..2886e08 100644 (file)
@@ -98,7 +98,7 @@
                // Show/Hide memcached servers when needed
                $( 'input[name$="config_wgMainCacheType"]' ).change( function () {
                        var $memc = $( '#config-memcachewrapper' );
-                       if( $( 'input[name$="config_wgMainCacheType"]:checked' ).val() === 'memcached' ) {
+                       if ( $( 'input[name$="config_wgMainCacheType"]:checked' ).val() === 'memcached' ) {
                                $memc.show( 'slow' );
                        } else {
                                $memc.hide( 'slow' );
index d639f63..7933caf 100644 (file)
@@ -85,7 +85,6 @@ function uploadSetup() {
                wpLicenseTbody.insertBefore( row, wpLicenseRow.nextSibling );
        }
 
-
        // fillDestFile setup
        uploadSourceIds = mw.config.get( 'wgUploadSourceIds' );
        len = uploadSourceIds.length;
@@ -98,7 +97,7 @@ function uploadSetup() {
 }
 
 wgUploadWarningObj = window.wgUploadWarningObj = {
-       responseCache: { '' : '&nbsp;' },
+       responseCache: { '': '&nbsp;' },
        nameToCheck: '',
        typing: false,
        delay: 500, // ms
@@ -303,7 +302,7 @@ window.toggleFilenameFiller = function () {
 
 wgUploadLicenseObj = window.wgUploadLicenseObj = {
 
-       responseCache: { '' : '' },
+       responseCache: { '': '' },
 
        fetchPreview: function ( license ) {
                var cached, title;
index f0aa943..df5682e 100644 (file)
@@ -240,7 +240,7 @@ win.importStylesheet = function ( page ) {
        return win.importStylesheetURI( uri );
 };
 
-win.importStylesheetURI = function( url, media ) {
+win.importStylesheetURI = function ( url, media ) {
        var l = document.createElement( 'link' );
        l.rel = 'stylesheet';
        l.href = url;
index 8420431..0bc114a 100644 (file)
@@ -8,7 +8,7 @@ jQuery( function ( $ ) {
                        .attr( 'tabindex', '0' )
                        // For accessibility, show the menu when the h3 is clicked (bug 24298/46486)
                        .on( 'click keypress', function ( e ) {
-                               if( e.type === 'click' || e.which === 13 ) {
+                               if ( e.type === 'click' || e.which === 13 ) {
                                        $el.toggleClass( 'menuForceShow' );
                                        e.preventDefault();
                                }
diff --git a/tests/frontend/Gruntfile.js b/tests/frontend/Gruntfile.js
new file mode 100644 (file)
index 0000000..a8aad41
--- /dev/null
@@ -0,0 +1,51 @@
+/*!
+ * Grunt file
+ */
+
+/*jshint node:true */
+module.exports = function ( grunt ) {
+       grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+       grunt.loadNpmTasks( 'grunt-contrib-watch' );
+       grunt.loadNpmTasks( 'grunt-jscs-checker' );
+
+       grunt.file.setBase(  __dirname + '/../..' );
+
+       grunt.initConfig( {
+               pkg: grunt.file.readJSON( __dirname + '/package.json' ),
+               jshint: {
+                       options: {
+                               jshintrc: '.jshintrc'
+                       },
+                       all: [ '*.js', '{includes,languages,resources,skins,tests}/**/*.js' ]
+               },
+               jscs: {
+                       // Known issues:
+                       // - https://github.com/mdevils/node-jscs/issues/277
+                       // - https://github.com/mdevils/node-jscs/issues/278
+                       all: [
+                               '<%= jshint.all %>',
+                               // Auto-generated file with JSON (double quotes)
+                               '!tests/qunit/data/mediawiki.jqueryMsg.data.js'
+
+                       // Exclude all files ignored by jshint
+                       ].concat( grunt.file.read( '.jshintignore' ).split( '\n' ).reduce( function ( patterns, pattern ) {
+                               // Filter out empty lines
+                               if ( pattern.length && pattern[0] !== '#' ) {
+                                       patterns.push( '!' + pattern );
+                               }
+                               return patterns;
+                       }, [] ) )
+               },
+               watch: {
+                       files: [
+                               '.{jshintrc,jscs.json,jshintignore,csslintrc}',
+                               '<%= jshint.all %>'
+                       ],
+                       tasks: ['test']
+               }
+       } );
+
+       grunt.registerTask( 'lint', ['jshint', 'jscs'] );
+       grunt.registerTask( 'test', ['lint'] );
+       grunt.registerTask( 'default', ['test'] );
+};
diff --git a/tests/frontend/package.json b/tests/frontend/package.json
new file mode 100644 (file)
index 0000000..5eb5c0a
--- /dev/null
@@ -0,0 +1,13 @@
+{
+  "name": "mediawiki",
+  "version": "0.0.0",
+  "scripts": {
+    "test": "grunt test"
+  },
+  "devDependencies": {
+    "grunt": "0.4.2",
+    "grunt-contrib-jshint": "0.8.0",
+    "grunt-contrib-watch": "0.5.3",
+    "grunt-jscs-checker": "0.4.0"
+  }
+}
index 12e5a2d..461ab87 100644 (file)
@@ -135,7 +135,6 @@ class GenerateJqueryMsgData extends Maintenance {
                                . '// Last generated with ' . basename( __FILE__ ) . ' at ' . gmdate( 'r' ) . "\n"
                                // This file will contain unquoted JSON strings as javascript native object literals,
                                // flip the quotemark convention for this file.
-                               . "/*jshint quotmark: double */\n"
                                . "\n"
                                . 'mediaWiki.libs.phpParserData = ' . FormatJson::encode( $phpParserData, true ) . ";\n";
 
index 776ee24..4ab5f14 100644 (file)
 // This file stores the output from the PHP parser for various messages, arguments,
 // languages, and parser modes. Intended for use by a unit test framework by looping
 // through the object and comparing its parser return value with the 'result' property.
-// Last generated with generateJqueryMsgData.php at Sat, 03 Nov 2012 21:32:01 +0000
-/*jshint quotmark: double */
+// Last generated with generateJqueryMsgData.php at Thu, 30 Jan 2014 04:04:41 +0000
 
 mediaWiki.libs.phpParserData = {
-       "messages": {
-               "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
-               "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
-               "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
-               "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
-               "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1|\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644|$1 \u062a\u0639\u062f\u064a\u0644\u0627}}",
-               "ar_category-subcat-count": "{{PLURAL:$2|\u0644\u0627 \u062a\u0635\u0627\u0646\u064a\u0641 \u0641\u0631\u0639\u064a\u0629 \u0641\u064a \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 {{PLURAL:$1||\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u0647\u0630\u064a\u0646 \u0627\u0644\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0627\u0644\u0641\u0631\u0639\u064a\u064a\u0646|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641\u0627 \u0641\u0631\u0639\u064a\u0627|\u0647\u0630\u0647 \u0627\u0644$1 \u062a\u0635\u0646\u064a\u0641 \u0641\u0631\u0639\u064a}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
-               "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
-               "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
-               "zh_undelete_short": "\u6062\u590d$1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-               "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u5217$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
-       },
-       "tests": [
-               {
-                       "name": "en undelete_short 0",
-                       "key": "en_undelete_short",
-                       "args": [
-                               0
-                       ],
-                       "result": "Undelete 0 edits",
-                       "lang": "en"
-               },
-               {
-                       "name": "en undelete_short 1",
-                       "key": "en_undelete_short",
-                       "args": [
-                               1
-                       ],
-                       "result": "Undelete one edit",
-                       "lang": "en"
-               },
-               {
-                       "name": "en undelete_short 2",
-                       "key": "en_undelete_short",
-                       "args": [
-                               2
-                       ],
-                       "result": "Undelete 2 edits",
-                       "lang": "en"
-               },
-               {
-                       "name": "en undelete_short 5",
-                       "key": "en_undelete_short",
-                       "args": [
-                               5
-                       ],
-                       "result": "Undelete 5 edits",
-                       "lang": "en"
-               },
-               {
-                       "name": "en undelete_short 21",
-                       "key": "en_undelete_short",
-                       "args": [
-                               21
-                       ],
-                       "result": "Undelete 21 edits",
-                       "lang": "en"
-               },
-               {
-                       "name": "en undelete_short 101",
-                       "key": "en_undelete_short",
-                       "args": [
-                               101
-                       ],
-                       "result": "Undelete 101 edits",
-                       "lang": "en"
-               },
-               {
-                       "name": "en category-subcat-count 0,10",
-                       "key": "en_category-subcat-count",
-                       "args": [
-                               0,
-                               10
-                       ],
-                       "result": "This category has the following 0 subcategories, out of 10 total.",
-                       "lang": "en"
-               },
-               {
-                       "name": "en category-subcat-count 1,1",
-                       "key": "en_category-subcat-count",
-                       "args": [
-                               1,
-                               1
-                       ],
-                       "result": "This category has only the following subcategory.",
-                       "lang": "en"
-               },
-               {
-                       "name": "en category-subcat-count 1,2",
-                       "key": "en_category-subcat-count",
-                       "args": [
-                               1,
-                               2
-                       ],
-                       "result": "This category has the following subcategory, out of 2 total.",
-                       "lang": "en"
-               },
-               {
-                       "name": "en category-subcat-count 3,30",
-                       "key": "en_category-subcat-count",
-                       "args": [
-                               3,
-                               30
-                       ],
-                       "result": "This category has the following 3 subcategories, out of 30 total.",
-                       "lang": "en"
-               },
-               {
-                       "name": "fr undelete_short 0",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               0
-                       ],
-                       "result": "Restaurer 0 modification",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr undelete_short 1",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               1
-                       ],
-                       "result": "Restaurer 1 modification",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr undelete_short 2",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               2
-                       ],
-                       "result": "Restaurer 2 modifications",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr undelete_short 5",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               5
-                       ],
-                       "result": "Restaurer 5 modifications",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr undelete_short 21",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               21
-                       ],
-                       "result": "Restaurer 21 modifications",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr undelete_short 101",
-                       "key": "fr_undelete_short",
-                       "args": [
-                               101
-                       ],
-                       "result": "Restaurer 101 modifications",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr category-subcat-count 0,10",
-                       "key": "fr_category-subcat-count",
-                       "args": [
-                               0,
-                               10
-                       ],
-                       "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr category-subcat-count 1,1",
-                       "key": "fr_category-subcat-count",
-                       "args": [
-                               1,
-                               1
-                       ],
-                       "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr category-subcat-count 1,2",
-                       "key": "fr_category-subcat-count",
-                       "args": [
-                               1,
-                               2
-                       ],
-                       "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
-                       "lang": "fr"
-               },
-               {
-                       "name": "fr category-subcat-count 3,30",
-                       "key": "fr_category-subcat-count",
-                       "args": [
-                               3,
-                               30
-                       ],
-                       "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
-                       "lang": "fr"
-               },
-               {
-                       "name": "ar undelete_short 0",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               0
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar undelete_short 1",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               1
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar undelete_short 2",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               2
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 2 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar undelete_short 5",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               5
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar undelete_short 21",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               21
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar undelete_short 101",
-                       "key": "ar_undelete_short",
-                       "args": [
-                               101
-                       ],
-                       "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644\u0627",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar category-subcat-count 0,10",
-                       "key": "ar_category-subcat-count",
-                       "args": [
-                               0,
-                               10
-                       ],
-                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar category-subcat-count 1,1",
-                       "key": "ar_category-subcat-count",
-                       "args": [
-                               1,
-                               1
-                       ],
-                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a \u0641\u0642\u0637.",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar category-subcat-count 1,2",
-                       "key": "ar_category-subcat-count",
-                       "args": [
-                               1,
-                               2
-                       ],
-                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
-                       "lang": "ar"
-               },
-               {
-                       "name": "ar category-subcat-count 3,30",
-                       "key": "ar_category-subcat-count",
-                       "args": [
-                               3,
-                               30
-                       ],
-                       "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0641\u064a\u0647 \u0647\u0630\u0647 \u0627\u06443 \u062a\u0635\u0627\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
-                       "lang": "ar"
-               },
-               {
-                       "name": "jp undelete_short 0",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               0
-                       ],
-                       "result": "Undelete 0 edits",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp undelete_short 1",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               1
-                       ],
-                       "result": "Undelete one edit",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp undelete_short 2",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               2
-                       ],
-                       "result": "Undelete 2 edits",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp undelete_short 5",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               5
-                       ],
-                       "result": "Undelete 5 edits",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp undelete_short 21",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               21
-                       ],
-                       "result": "Undelete 21 edits",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp undelete_short 101",
-                       "key": "jp_undelete_short",
-                       "args": [
-                               101
-                       ],
-                       "result": "Undelete 101 edits",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp category-subcat-count 0,10",
-                       "key": "jp_category-subcat-count",
-                       "args": [
-                               0,
-                               10
-                       ],
-                       "result": "This category has the following 0 subcategories, out of 10 total.",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp category-subcat-count 1,1",
-                       "key": "jp_category-subcat-count",
-                       "args": [
-                               1,
-                               1
-                       ],
-                       "result": "This category has only the following subcategory.",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp category-subcat-count 1,2",
-                       "key": "jp_category-subcat-count",
-                       "args": [
-                               1,
-                               2
-                       ],
-                       "result": "This category has the following subcategory, out of 2 total.",
-                       "lang": "jp"
-               },
-               {
-                       "name": "jp category-subcat-count 3,30",
-                       "key": "jp_category-subcat-count",
-                       "args": [
-                               3,
-                               30
-                       ],
-                       "result": "This category has the following 3 subcategories, out of 30 total.",
-                       "lang": "jp"
-               },
-               {
-                       "name": "zh undelete_short 0",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               0
-                       ],
-                       "result": "\u6062\u590d0\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh undelete_short 1",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               1
-                       ],
-                       "result": "\u6062\u590d1\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh undelete_short 2",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               2
-                       ],
-                       "result": "\u6062\u590d2\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh undelete_short 5",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               5
-                       ],
-                       "result": "\u6062\u590d5\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh undelete_short 21",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               21
-                       ],
-                       "result": "\u6062\u590d21\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh undelete_short 101",
-                       "key": "zh_undelete_short",
-                       "args": [
-                               101
-                       ],
-                       "result": "\u6062\u590d101\u4e2a\u88ab\u5220\u9664\u7684\u7f16\u8f91",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh category-subcat-count 0,10",
-                       "key": "zh_category-subcat-count",
-                       "args": [
-                               0,
-                               10
-                       ],
-                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52170\u4e2a\u5b50\u5206\u7c7b\uff0c\u517110\u4e2a\u5b50\u5206\u7c7b\u3002",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh category-subcat-count 1,1",
-                       "key": "zh_category-subcat-count",
-                       "args": [
-                               1,
-                               1
-                       ],
-                       "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4e0b\u5217\u4e00\u4e2a\u5b50\u5206\u7c7b\u3002",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh category-subcat-count 1,2",
-                       "key": "zh_category-subcat-count",
-                       "args": [
-                               1,
-                               2
-                       ],
-                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52171\u4e2a\u5b50\u5206\u7c7b\uff0c\u51712\u4e2a\u5b50\u5206\u7c7b\u3002",
-                       "lang": "zh"
-               },
-               {
-                       "name": "zh category-subcat-count 3,30",
-                       "key": "zh_category-subcat-count",
-                       "args": [
-                               3,
-                               30
-                       ],
-                       "result": "\u672c\u5206\u7c7b\u5305\u542b\u4e0b\u52173\u4e2a\u5b50\u5206\u7c7b\uff0c\u517130\u4e2a\u5b50\u5206\u7c7b\u3002",
-                       "lang": "zh"
-               }
-       ]
+    "messages": {
+        "en_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+        "en_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+        "fr_undelete_short": "Restaurer $1 modification{{PLURAL:$1||s}}",
+        "fr_category-subcat-count": "Cette cat\u00e9gorie comprend {{PLURAL:$2|la sous-cat\u00e9gorie|$2 sous-cat\u00e9gories, dont {{PLURAL:$1|celle|les $1}}}} ci-dessous.",
+        "ar_undelete_short": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 {{PLURAL:$1||\u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f|\u062a\u0639\u062f\u064a\u0644\u064a\u0646|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u062a|$1 \u062a\u0639\u062f\u064a\u0644\u0627\u064b|$1 \u062a\u0639\u062f\u064a\u0644}}",
+        "ar_category-subcat-count": "{{PLURAL:$2|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a \u0627\u0644\u062a\u0627\u0644\u064a|\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a {{PLURAL:$1||\u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a|\u062a\u0635\u0646\u064a\u0641\u064a\u0646 \u0641\u0631\u0639\u064a\u064a\u0646|$1 \u062a\u0635\u0646\u064a\u0641\u0627\u062a \u0641\u0631\u0639\u064a\u0629}}\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a $2.}}",
+        "jp_undelete_short": "Undelete {{PLURAL:$1|one edit|$1 edits}}",
+        "jp_category-subcat-count": "{{PLURAL:$2|This category has only the following subcategory.|This category has the following {{PLURAL:$1|subcategory|$1 subcategories}}, out of $2 total.}}",
+        "zh_undelete_short": "\u8fd8\u539f{{PLURAL:$1|$1\u4e2a\u7f16\u8f91}}",
+        "zh_category-subcat-count": "{{PLURAL:$2|\u672c\u5206\u7c7b\u53ea\u6709\u4ee5\u4e0b\u5b50\u5206\u7c7b\u3002|\u672c\u5206\u7c7b\u6709\u4ee5\u4e0b$1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u6709$2\u4e2a\u5b50\u5206\u7c7b\u3002}}"
+    },
+    "tests": [
+        {
+            "name": "en undelete_short 0",
+            "key": "en_undelete_short",
+            "args": [
+                0
+            ],
+            "result": "Undelete 0 edits",
+            "lang": "en"
+        },
+        {
+            "name": "en undelete_short 1",
+            "key": "en_undelete_short",
+            "args": [
+                1
+            ],
+            "result": "Undelete one edit",
+            "lang": "en"
+        },
+        {
+            "name": "en undelete_short 2",
+            "key": "en_undelete_short",
+            "args": [
+                2
+            ],
+            "result": "Undelete 2 edits",
+            "lang": "en"
+        },
+        {
+            "name": "en undelete_short 5",
+            "key": "en_undelete_short",
+            "args": [
+                5
+            ],
+            "result": "Undelete 5 edits",
+            "lang": "en"
+        },
+        {
+            "name": "en undelete_short 21",
+            "key": "en_undelete_short",
+            "args": [
+                21
+            ],
+            "result": "Undelete 21 edits",
+            "lang": "en"
+        },
+        {
+            "name": "en undelete_short 101",
+            "key": "en_undelete_short",
+            "args": [
+                101
+            ],
+            "result": "Undelete 101 edits",
+            "lang": "en"
+        },
+        {
+            "name": "en category-subcat-count 0,10",
+            "key": "en_category-subcat-count",
+            "args": [
+                0,
+                10
+            ],
+            "result": "This category has the following 0 subcategories, out of 10 total.",
+            "lang": "en"
+        },
+        {
+            "name": "en category-subcat-count 1,1",
+            "key": "en_category-subcat-count",
+            "args": [
+                1,
+                1
+            ],
+            "result": "This category has only the following subcategory.",
+            "lang": "en"
+        },
+        {
+            "name": "en category-subcat-count 1,2",
+            "key": "en_category-subcat-count",
+            "args": [
+                1,
+                2
+            ],
+            "result": "This category has the following subcategory, out of 2 total.",
+            "lang": "en"
+        },
+        {
+            "name": "en category-subcat-count 3,30",
+            "key": "en_category-subcat-count",
+            "args": [
+                3,
+                30
+            ],
+            "result": "This category has the following 3 subcategories, out of 30 total.",
+            "lang": "en"
+        },
+        {
+            "name": "fr undelete_short 0",
+            "key": "fr_undelete_short",
+            "args": [
+                0
+            ],
+            "result": "Restaurer 0 modification",
+            "lang": "fr"
+        },
+        {
+            "name": "fr undelete_short 1",
+            "key": "fr_undelete_short",
+            "args": [
+                1
+            ],
+            "result": "Restaurer 1 modification",
+            "lang": "fr"
+        },
+        {
+            "name": "fr undelete_short 2",
+            "key": "fr_undelete_short",
+            "args": [
+                2
+            ],
+            "result": "Restaurer 2 modifications",
+            "lang": "fr"
+        },
+        {
+            "name": "fr undelete_short 5",
+            "key": "fr_undelete_short",
+            "args": [
+                5
+            ],
+            "result": "Restaurer 5 modifications",
+            "lang": "fr"
+        },
+        {
+            "name": "fr undelete_short 21",
+            "key": "fr_undelete_short",
+            "args": [
+                21
+            ],
+            "result": "Restaurer 21 modifications",
+            "lang": "fr"
+        },
+        {
+            "name": "fr undelete_short 101",
+            "key": "fr_undelete_short",
+            "args": [
+                101
+            ],
+            "result": "Restaurer 101 modifications",
+            "lang": "fr"
+        },
+        {
+            "name": "fr category-subcat-count 0,10",
+            "key": "fr_category-subcat-count",
+            "args": [
+                0,
+                10
+            ],
+            "result": "Cette cat\u00e9gorie comprend 10 sous-cat\u00e9gories, dont celle ci-dessous.",
+            "lang": "fr"
+        },
+        {
+            "name": "fr category-subcat-count 1,1",
+            "key": "fr_category-subcat-count",
+            "args": [
+                1,
+                1
+            ],
+            "result": "Cette cat\u00e9gorie comprend la sous-cat\u00e9gorie ci-dessous.",
+            "lang": "fr"
+        },
+        {
+            "name": "fr category-subcat-count 1,2",
+            "key": "fr_category-subcat-count",
+            "args": [
+                1,
+                2
+            ],
+            "result": "Cette cat\u00e9gorie comprend 2 sous-cat\u00e9gories, dont celle ci-dessous.",
+            "lang": "fr"
+        },
+        {
+            "name": "fr category-subcat-count 3,30",
+            "key": "fr_category-subcat-count",
+            "args": [
+                3,
+                30
+            ],
+            "result": "Cette cat\u00e9gorie comprend 30 sous-cat\u00e9gories, dont les 3 ci-dessous.",
+            "lang": "fr"
+        },
+        {
+            "name": "ar undelete_short 0",
+            "key": "ar_undelete_short",
+            "args": [
+                0
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 ",
+            "lang": "ar"
+        },
+        {
+            "name": "ar undelete_short 1",
+            "key": "ar_undelete_short",
+            "args": [
+                1
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644 \u0648\u0627\u062d\u062f",
+            "lang": "ar"
+        },
+        {
+            "name": "ar undelete_short 2",
+            "key": "ar_undelete_short",
+            "args": [
+                2
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 \u062a\u0639\u062f\u064a\u0644\u064a\u0646",
+            "lang": "ar"
+        },
+        {
+            "name": "ar undelete_short 5",
+            "key": "ar_undelete_short",
+            "args": [
+                5
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 5 \u062a\u0639\u062f\u064a\u0644\u0627\u062a",
+            "lang": "ar"
+        },
+        {
+            "name": "ar undelete_short 21",
+            "key": "ar_undelete_short",
+            "args": [
+                21
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 21 \u062a\u0639\u062f\u064a\u0644\u0627\u064b",
+            "lang": "ar"
+        },
+        {
+            "name": "ar undelete_short 101",
+            "key": "ar_undelete_short",
+            "args": [
+                101
+            ],
+            "result": "\u0627\u0633\u062a\u0631\u062c\u0627\u0639 101 \u062a\u0639\u062f\u064a\u0644",
+            "lang": "ar"
+        },
+        {
+            "name": "ar category-subcat-count 0,10",
+            "key": "ar_category-subcat-count",
+            "args": [
+                0,
+                10
+            ],
+            "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a \u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 10.",
+            "lang": "ar"
+        },
+        {
+            "name": "ar category-subcat-count 1,1",
+            "key": "ar_category-subcat-count",
+            "args": [
+                1,
+                1
+            ],
+            "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 1.",
+            "lang": "ar"
+        },
+        {
+            "name": "ar category-subcat-count 1,2",
+            "key": "ar_category-subcat-count",
+            "args": [
+                1,
+                2
+            ],
+            "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u0627\u0644\u0641\u0631\u0639\u064a\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 2.",
+            "lang": "ar"
+        },
+        {
+            "name": "ar category-subcat-count 3,30",
+            "key": "ar_category-subcat-count",
+            "args": [
+                3,
+                30
+            ],
+            "result": "\u0647\u0630\u0627 \u0627\u0644\u062a\u0635\u0646\u064a\u0641 \u064a\u062d\u0648\u064a 3 \u062a\u0635\u0646\u064a\u0641\u0627\u062a \u0641\u0631\u0639\u064a\u0629\u060c \u0645\u0646 \u0625\u062c\u0645\u0627\u0644\u064a 30.",
+            "lang": "ar"
+        },
+        {
+            "name": "jp undelete_short 0",
+            "key": "jp_undelete_short",
+            "args": [
+                0
+            ],
+            "result": "Undelete 0 edits",
+            "lang": "jp"
+        },
+        {
+            "name": "jp undelete_short 1",
+            "key": "jp_undelete_short",
+            "args": [
+                1
+            ],
+            "result": "Undelete one edit",
+            "lang": "jp"
+        },
+        {
+            "name": "jp undelete_short 2",
+            "key": "jp_undelete_short",
+            "args": [
+                2
+            ],
+            "result": "Undelete 2 edits",
+            "lang": "jp"
+        },
+        {
+            "name": "jp undelete_short 5",
+            "key": "jp_undelete_short",
+            "args": [
+                5
+            ],
+            "result": "Undelete 5 edits",
+            "lang": "jp"
+        },
+        {
+            "name": "jp undelete_short 21",
+            "key": "jp_undelete_short",
+            "args": [
+                21
+            ],
+            "result": "Undelete 21 edits",
+            "lang": "jp"
+        },
+        {
+            "name": "jp undelete_short 101",
+            "key": "jp_undelete_short",
+            "args": [
+                101
+            ],
+            "result": "Undelete 101 edits",
+            "lang": "jp"
+        },
+        {
+            "name": "jp category-subcat-count 0,10",
+            "key": "jp_category-subcat-count",
+            "args": [
+                0,
+                10
+            ],
+            "result": "This category has the following 0 subcategories, out of 10 total.",
+            "lang": "jp"
+        },
+        {
+            "name": "jp category-subcat-count 1,1",
+            "key": "jp_category-subcat-count",
+            "args": [
+                1,
+                1
+            ],
+            "result": "This category has only the following subcategory.",
+            "lang": "jp"
+        },
+        {
+            "name": "jp category-subcat-count 1,2",
+            "key": "jp_category-subcat-count",
+            "args": [
+                1,
+                2
+            ],
+            "result": "This category has the following subcategory, out of 2 total.",
+            "lang": "jp"
+        },
+        {
+            "name": "jp category-subcat-count 3,30",
+            "key": "jp_category-subcat-count",
+            "args": [
+                3,
+                30
+            ],
+            "result": "This category has the following 3 subcategories, out of 30 total.",
+            "lang": "jp"
+        },
+        {
+            "name": "zh undelete_short 0",
+            "key": "zh_undelete_short",
+            "args": [
+                0
+            ],
+            "result": "\u8fd8\u539f0\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh undelete_short 1",
+            "key": "zh_undelete_short",
+            "args": [
+                1
+            ],
+            "result": "\u8fd8\u539f1\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh undelete_short 2",
+            "key": "zh_undelete_short",
+            "args": [
+                2
+            ],
+            "result": "\u8fd8\u539f2\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh undelete_short 5",
+            "key": "zh_undelete_short",
+            "args": [
+                5
+            ],
+            "result": "\u8fd8\u539f5\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh undelete_short 21",
+            "key": "zh_undelete_short",
+            "args": [
+                21
+            ],
+            "result": "\u8fd8\u539f21\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh undelete_short 101",
+            "key": "zh_undelete_short",
+            "args": [
+                101
+            ],
+            "result": "\u8fd8\u539f101\u4e2a\u7f16\u8f91",
+            "lang": "zh"
+        },
+        {
+            "name": "zh category-subcat-count 0,10",
+            "key": "zh_category-subcat-count",
+            "args": [
+                0,
+                10
+            ],
+            "result": "\u672c\u5206\u7c7b\u6709\u4ee5\u4e0b0\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670910\u4e2a\u5b50\u5206\u7c7b\u3002",
+            "lang": "zh"
+        },
+        {
+            "name": "zh category-subcat-count 1,1",
+            "key": "zh_category-subcat-count",
+            "args": [
+                1,
+                1
+            ],
+            "result": "\u672c\u5206\u7c7b\u53ea\u6709\u4ee5\u4e0b\u5b50\u5206\u7c7b\u3002",
+            "lang": "zh"
+        },
+        {
+            "name": "zh category-subcat-count 1,2",
+            "key": "zh_category-subcat-count",
+            "args": [
+                1,
+                2
+            ],
+            "result": "\u672c\u5206\u7c7b\u6709\u4ee5\u4e0b1\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u67092\u4e2a\u5b50\u5206\u7c7b\u3002",
+            "lang": "zh"
+        },
+        {
+            "name": "zh category-subcat-count 3,30",
+            "key": "zh_category-subcat-count",
+            "args": [
+                3,
+                30
+            ],
+            "result": "\u672c\u5206\u7c7b\u6709\u4ee5\u4e0b3\u4e2a\u5b50\u5206\u7c7b\uff0c\u5171\u670930\u4e2a\u5b50\u5206\u7c7b\u3002",
+            "lang": "zh"
+        }
+    ]
 };
index b644a3c..839c5d5 100644 (file)
@@ -4,7 +4,7 @@
        QUnit.asyncTest( 'animate', 3, function ( assert ) {
                var $canvas = $( '<div>' ).css( 'background-color', '#fff' );
 
-               $canvas.animate( { backgroundColor: '#000' }, 4 ).promise().then( function() {
+               $canvas.animate( { backgroundColor: '#000' }, 4 ).promise().then( function () {
                        var endColors = $.colorUtil.getRGB( $canvas.css( 'background-color' ) );
                        assert.strictEqual( endColors[0], 0 );
                        assert.strictEqual( endColors[1], 0 );
index bfb857d..e6a6124 100644 (file)
                $collapsible.find( '.mw-collapsible-toggle' ).trigger( 'click' );
        } );
 
-       QUnit.test( 'clicks on links inside toggler pass through (options.linksPassthru)' , 2, function ( assert ) {
+       QUnit.test( 'clicks on links inside toggler pass through (options.linksPassthru)', 2, function ( assert ) {
                var $collapsible = prepareCollapsible(
                                '<div class="mw-collapsible">' +
                                        '<div class="mw-collapsible-toggle">' +
index ca0ea67..bbea829 100644 (file)
@@ -1,8 +1,8 @@
-(function($) {
+(function ($) {
 
        QUnit.module('jquery.placeholder', QUnit.newMwEnvironment());
 
-       QUnit.test('caches results of feature tests', 2, function(assert) {
+       QUnit.test('caches results of feature tests', 2, function (assert) {
                assert.strictEqual(typeof $.fn.placeholder.input, 'boolean', '$.fn.placeholder.input');
                assert.strictEqual(typeof $.fn.placeholder.textarea, 'boolean', '$.fn.placeholder.textarea');
        });
@@ -20,7 +20,7 @@
                        '<input id="input-type-password" type="password" placeholder="e.g. hunter2">' +
                        '<textarea id="textarea" name="message" placeholder="Your message goes here"></textarea>' +
                '</form>',
-       testElement = function($el, assert) {
+       testElement = function ($el, assert) {
 
                var el = $el[0],
                        placeholder = el.getAttribute('placeholder');
                $el.placeholder(placeholder);
        };
 
-       QUnit.test('emulates placeholder for <input type=text>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <input type=text>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#input-type-text'), assert);
        });
 
-       QUnit.test('emulates placeholder for <input type=search>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <input type=search>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#input-type-search'), assert);
        });
 
-       QUnit.test('emulates placeholder for <input type=email>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <input type=email>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#input-type-email'), assert);
        });
 
-       QUnit.test('emulates placeholder for <input type=url>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <input type=url>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#input-type-url'), assert);
        });
 
-       QUnit.test('emulates placeholder for <input type=tel>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <input type=tel>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#input-type-tel'), assert);
        });
 
-       QUnit.test('emulates placeholder for <input type=password>', 13, function(assert) {
+       QUnit.test('emulates placeholder for <input type=password>', 13, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
 
                var selector = '#input-type-password',
 
        });
 
-       QUnit.test('emulates placeholder for <textarea></textarea>', 22, function(assert) {
+       QUnit.test('emulates placeholder for <textarea></textarea>', 22, function (assert) {
                $('<div>').html(html).appendTo($('#qunit-fixture'));
                testElement($('#textarea'), assert);
        });
index 5fe2394..25d9f73 100644 (file)
                }, opt.after );
 
                QUnit.test( opt.description, function ( assert ) {
-                       /*jshint onevar: false */
-                       var tests = 1;
+                       var $textarea, start, end, options, text,
+                               tests = 1;
                        if ( opt.after.selected !== null ) {
                                tests++;
                        }
                        QUnit.expect( tests );
 
-                       var $textarea = $( '<textarea>' );
+                       $textarea = $( '<textarea>' );
 
                        $( '#qunit-fixture' ).append( $textarea );
 
-                       //$textarea.textSelection( 'setContents', opt.before.text); // this method is actually missing atm...
-                       $textarea.val( opt.before.text ); // won't work with the WikiEditor iframe?
+                       // This method is actually missing atm...
+                       //$textarea.textSelection( 'setContents', opt.before.text);
 
-                       var start = opt.before.start,
-                               end = opt.before.end;
+                       // Won't work with the WikiEditor iframe?
+                       $textarea.val( opt.before.text );
 
-                       var options = $.extend( {}, opt.replace ); // Clone opt.replace
+                       start = opt.before.start;
+                       end = opt.before.end;
+
+                       // Clone opt.replace
+                       options = $.extend( {}, opt.replace );
                        options.selectionStart = start;
                        options.selectionEnd = end;
                        $textarea.textSelection( 'encapsulateSelection', options );
 
-                       var text = $textarea.textSelection( 'getContents' ).replace( /\r\n/g, '\n' );
+                       text = $textarea.textSelection( 'getContents' ).replace( /\r\n/g, '\n' );
 
                        assert.equal( text, opt.after.text, 'Checking full text after encapsulation' );
 
                replace: h2
        } );
 
-
        encapsulateTest( {
                description: 'ownline option: turn a partial line into new h2',
                before: {
                replace: h2
        } );
 
-
        encapsulateTest( {
                description: 'splitlines option: no selection, insert new list item',
                before: {
                replace: ulist
        } );
 
-
        function caretTest( options ) {
                QUnit.test( options.description, 2, function ( assert ) {
-                       var pos, $textarea = $( '<textarea>' ).text( options.text );
+                       var pos,
+                               $textarea = $( '<textarea>' ).text( options.text );
 
                        $( '#qunit-fixture' ).append( $textarea );
 
index 2ad7622..3be3642 100644 (file)
@@ -29,7 +29,6 @@
                } );
        } );
 
-
        QUnit.test( 'API error', function ( assert ) {
                QUnit.expect( 1 );
 
index aeefd64..f49be49 100644 (file)
                        100: 'Penguins'
                },
                wgNamespaceIds: {
-                       /*jshint camelcase: false */
-                       media: -2,
-                       special: -1,
+                       'media': -2,
+                       'special': -1,
                        '': 0,
-                       talk: 1,
-                       user: 2,
-                       user_talk: 3,
-                       wikipedia: 4,
-                       wikipedia_talk: 5,
-                       file: 6,
-                       file_talk: 7,
-                       mediawiki: 8,
-                       mediawiki_talk: 9,
-                       template: 10,
-                       template_talk: 11,
-                       help: 12,
-                       help_talk: 13,
-                       category: 14,
-                       category_talk: 15,
-                       image: 6,
-                       image_talk: 7,
-                       project: 4,
-                       project_talk: 5,
-                       /* testing custom / alias */
-                       penguins: 100,
-                       antarctic_waterfowl: 100
+                       'talk': 1,
+                       'user': 2,
+                       'user_talk': 3,
+                       'wikipedia': 4,
+                       'wikipedia_talk': 5,
+                       'file': 6,
+                       'file_talk': 7,
+                       'mediawiki': 8,
+                       'mediawiki_talk': 9,
+                       'template': 10,
+                       'template_talk': 11,
+                       'help': 12,
+                       'help_talk': 13,
+                       'category': 14,
+                       'category_talk': 15,
+                       'image': 6,
+                       'image_talk': 7,
+                       'project': 4,
+                       'project_talk': 5,
+                       // Testing custom namespaces and aliases
+                       'penguins': 100,
+                       'antarctic_waterfowl': 100
                },
                wgCaseSensitiveNamespaces: []
        },
index 24a7cc9..a2ad50d 100644 (file)
@@ -705,7 +705,6 @@ QUnit.test( 'HTML', 26, function ( assert ) {
                'Escaped attributes are parsed correctly'
        );
 
-
        mw.messages.set( 'jquerymsg-wikitext-contents-parsed', '<i>[http://example.com Example]</i>' );
        assert.htmlEqual(
                formatParse( 'jquerymsg-wikitext-contents-parsed' ),
index 594ae25..441fcbc 100644 (file)
@@ -75,7 +75,7 @@
                assert.strictEqual( conf.get( 'constructor' ), 42, 'Map.get for key "constructor"' );
 
                assert.strictEqual( conf.set( 'ImUndefined', undefined ), true, 'Map.set allows setting value to `undefined`' );
-               assert.equal( conf.get( 'ImUndefined', 'fallback' ), undefined , 'Map.get supports retreiving value of `undefined`' );
+               assert.equal( conf.get( 'ImUndefined', 'fallback' ), undefined, 'Map.get supports retreiving value of `undefined`' );
 
                assert.strictEqual( conf.set( funky, 'Funky' ), false, 'Map.set returns boolean false if key was invalid (Function)' );
                assert.strictEqual( conf.set( arry, 'Arry' ), false, 'Map.set returns boolean false if key was invalid (Array)' );
                        'notExist': null
                }, 'Map.get return includes keys that were not found as null values' );
 
-
                // Interacting with globals and accessing the values object
                assert.strictEqual( conf.get(), conf.values, 'Map.get returns the entire values object by reference (if called without arguments)' );
 
                assertMultipleFormats( ['mediawiki-test-version-entrypoints-index-php'], ['plain', 'text', 'escaped'], mw.messages.get( 'mediawiki-test-version-entrypoints-index-php' ), 'External link markup is unprocessed' );
                assert.htmlEqual( mw.message( 'mediawiki-test-version-entrypoints-index-php' ).parse(), '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>', 'External link works correctly in parse mode' );
 
-               assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'] , 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' );
+               assertMultipleFormats( ['external-link-replace', 'http://example.org/?x=y&z'], ['plain', 'text'], 'Foo [http://example.org/?x=y&z bar]', 'Parameters are substituted but external link is not processed' );
                assert.equal( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).escaped(), 'Foo [http://example.org/?x=y&amp;z bar]', 'In escaped mode, parameters are substituted and ampersand is escaped, but external link is not processed' );
                assert.htmlEqual( mw.message( 'external-link-replace', 'http://example.org/?x=y&z' ).parse(), 'Foo <a href="http://example.org/?x=y&amp;z">bar</a>', 'External link with replacement works in parse mode without double-escaping' );
 
                assertMultipleFormats( ['plural-test-msg-explicit-beginning', 6], ['text', 'parse', 'escaped'], 'Basket has half a dozen eggs', 'explicit plural given at beginning get resolved for 6' );
                assertMultipleFormats( ['plural-test-msg-explicit-beginning', 0], ['text', 'parse', 'escaped'], 'Basket has no eggs', 'explicit plural given at beginning get resolved for 0' );
 
-
                assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary'], ['plain', 'text'], mw.messages.get( 'mediawiki-test-pagetriage-del-talk-page-notify-summary' ), 'Double square brackets with no parameters unchanged' );
 
                assertMultipleFormats( ['mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName], ['plain', 'text'], 'Notifying author of deletion nomination for [[' + specialCharactersPageName + ']]', 'Double square brackets with one parameter' );
 
                assert.equal( mw.message( 'mediawiki-test-pagetriage-del-talk-page-notify-summary', specialCharactersPageName ).escaped(), 'Notifying author of deletion nomination for [[' + mw.html.escape( specialCharactersPageName ) + ']]', 'Double square brackets with one parameter, when escaped' );
 
-
                assert.ok( mw.messages.set( 'mediawiki-test-categorytree-collapse-bullet', '[<b>−</b>]' ), 'mw.messages.set: Register' );
                assert.equal( mw.message( 'mediawiki-test-categorytree-collapse-bullet' ).plain(), mw.messages.get( 'mediawiki-test-categorytree-collapse-bullet' ), 'Single square brackets unchanged in plain mode' );
 
                        'Script escaped when using parse format'
                );
 
-
        } );
 
        QUnit.test( 'mw.msg', 14, function ( assert ) {
                assert.equal( mw.msg( 'hello' ), 'Hello <b>awesome</b> world', 'Gets message with default options (existing message)' );
                assert.equal( mw.msg( 'goodbye' ), '<goodbye>', 'Gets message with default options (nonexistent message)' );
 
-               assert.ok( mw.messages.set( 'plural-item' , 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' );
+               assert.ok( mw.messages.set( 'plural-item', 'Found $1 {{PLURAL:$1|item|items}}' ), 'mw.messages.set: Register' );
                assert.equal( mw.msg( 'plural-item', 5 ), 'Found 5 items', 'Apply plural for count 5' );
                assert.equal( mw.msg( 'plural-item', 0 ), 'Found 0 items', 'Apply plural for count 0' );
                assert.equal( mw.msg( 'plural-item', 1 ), 'Found 1 item', 'Apply plural for count 1' );