Merge "Fix type hints in jobqueue related classes"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 5 Jul 2019 23:10:40 +0000 (23:10 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 5 Jul 2019 23:10:40 +0000 (23:10 +0000)
51 files changed:
includes/block/AbstractBlock.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignAPIRepo.php
includes/filerepo/file/LocalFile.php
includes/htmlform/HTMLForm.php
includes/htmlform/HTMLFormField.php
includes/htmlform/fields/HTMLCheckMatrix.php
includes/htmlform/fields/HTMLFormFieldWithButton.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/page/ImagePage.php
includes/page/WikiFilePage.php
includes/specials/SpecialUndelete.php
mw-config/config.js
package-lock.json
package.json
resources/src/jquery.tablesorter/jquery.tablesorter.js
resources/src/jquery/jquery.confirmable.js
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.suggestions.js
resources/src/mediawiki.action/mediawiki.action.history.js
resources/src/mediawiki.action/mediawiki.action.view.metadata.js
resources/src/mediawiki.htmlform.checker.js
resources/src/mediawiki.htmlform/cloner.js
resources/src/mediawiki.page.watch.ajax.js
resources/src/mediawiki.rcfilters/ui/ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/FilterTagMultiselectWidget.js
resources/src/mediawiki.searchSuggest/searchSuggest.js
resources/src/mediawiki.special.import.js
resources/src/mediawiki.special.preferences.ooui/confirmClose.js
resources/src/mediawiki.special.unwatchedPages/unwatchedPages.js
resources/src/mediawiki.special.watchlist/watchlist.js
resources/src/mediawiki.toc/toc.js
resources/src/mediawiki.util.js
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaSearchWidget.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
tests/phpunit/includes/Permissions/PermissionManagerTest.php
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/db/LoadBalancerTest.php
tests/phpunit/tests/MediaWikiTestCaseTest.php
tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js
tests/selenium/.eslintrc.json
tests/selenium/pageobjects/history.page.js
tests/selenium/specs/page.js
tests/selenium/wdio-mediawiki/Api.js
tests/selenium/wdio-mediawiki/RunJobs.js
tests/selenium/wdio.conf.js

index 0357f8d..d24a2a5 100644 (file)
@@ -513,10 +513,13 @@ abstract class AbstractBlock {
         * @return array
         */
        public function getBlockErrorParams( IContextSource $context ) {
+               $lang = $context->getLanguage();
+
                $blocker = $this->getBlocker();
                if ( $blocker instanceof User ) { // local user
                        $blockerUserpage = $blocker->getUserPage();
-                       $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerUserpage->getText()}]]";
+                       $blockerText = $lang->embedBidi( $blockerUserpage->getText() );
+                       $link = "[[{$blockerUserpage->getPrefixedText()}|{$blockerText}]]";
                } else { // foreign user
                        $link = $blocker;
                }
@@ -528,19 +531,18 @@ abstract class AbstractBlock {
 
                /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
                 * This could be a username, an IP range, or a single IP. */
-               $intended = $this->getTarget();
-               $lang = $context->getLanguage();
+               $intended = (string)$this->getTarget();
 
                return [
                        $link,
                        $reason,
                        $context->getRequest()->getIP(),
-                       $this->getByName(),
+                       $lang->embedBidi( $this->getByName() ),
                        // TODO: SystemBlock replaces this with the system block type. Clean up
                        // error params so that this is not necessary.
                        $this->getId(),
                        $lang->formatExpiry( $this->getExpiry() ),
-                       (string)$intended,
+                       $lang->embedBidi( $intended ),
                        $lang->userTimeAndDate( $this->getTimestamp(), $context->getUser() ),
                ];
        }
index 51cef81..cbbffe4 100644 (file)
@@ -121,13 +121,13 @@ class FileRepo {
        /** @var bool Whether all zones should be private (e.g. private wiki repo) */
        protected $isPrivate;
 
-       /** @var array callable Override these in the base class */
+       /** @var callable Override these in the base class */
        protected $fileFactory = [ UnregisteredLocalFile::class, 'newFromTitle' ];
-       /** @var array callable|bool Override these in the base class */
+       /** @var callable|false Override these in the base class */
        protected $oldFileFactory = false;
-       /** @var array callable|bool Override these in the base class */
+       /** @var callable|false Override these in the base class */
        protected $fileFactoryKey = false;
-       /** @var array callable|bool Override these in the base class */
+       /** @var callable|false Override these in the base class */
        protected $oldFileFactoryKey = false;
 
        /** @var string URL of where to proxy thumb.php requests to.
@@ -230,7 +230,7 @@ class FileRepo {
        /**
         * Check if a single zone or list of zones is defined for usage
         *
-        * @param array $doZones Only do a particular zones
+        * @param string[]|string $doZones Only do a particular zones
         * @throws MWException
         * @return Status
         */
@@ -734,7 +734,7 @@ class FileRepo {
        /**
         * Make an url to this repo
         *
-        * @param string $query Query string to append
+        * @param string|string[] $query Query string to append
         * @param string $entry Entry point; defaults to index
         * @return string|bool False on failure
         */
@@ -1739,7 +1739,7 @@ class FileRepo {
        /**
         * Create a new good result
         *
-        * @param null|string $value
+        * @param null|mixed $value
         * @return Status
         */
        public function newGood( $value = null ) {
index 2c6f296..8ff8143 100644 (file)
@@ -184,7 +184,7 @@ class ForeignAPIRepo extends FileRepo {
 
        /**
         * @param array $query
-        * @return string
+        * @return array|null
         */
        function fetchImageQuery( $query ) {
                global $wgLanguageCode;
index d7d6bf7..54fc251 100644 (file)
@@ -25,6 +25,7 @@ use Wikimedia\AtEase\AtEase;
 use MediaWiki\Logger\LoggerFactory;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\IResultWrapper;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -99,7 +100,7 @@ class LocalFile extends File {
        /** @var int Number of line to return by nextHistoryLine() (constructor) */
        private $historyLine;
 
-       /** @var int Result of the query for the file's history (nextHistoryLine) */
+       /** @var IResultWrapper|null Result of the query for the file's history (nextHistoryLine) */
        private $historyRes;
 
        /** @var string Major MIME type */
index 99e387a..a7cef3c 100644 (file)
@@ -184,6 +184,7 @@ class HTMLForm extends ContextSource {
        protected $mFieldTree = [];
        protected $mShowReset = false;
        protected $mShowSubmit = true;
+       /** @var string[] */
        protected $mSubmitFlags = [ 'primary', 'progressive' ];
        protected $mShowCancel = false;
        protected $mCancelTarget;
index ff805d8..590b9e7 100644 (file)
@@ -367,7 +367,7 @@ abstract class HTMLFormField {
         * or the input's default value if it has not been set.
         *
         * @param WebRequest $request
-        * @return string The value
+        * @return mixed The value
         */
        public function loadDataFromRequest( $request ) {
                if ( $request->getCheck( $this->mName ) ) {
@@ -653,7 +653,7 @@ abstract class HTMLFormField {
 
        /**
         * Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
-        * @param string $inputField
+        * @param OOUI\Widget $inputField
         * @param array $config
         * @return OOUI\FieldLayout|OOUI\ActionFieldLayout
         */
@@ -1032,7 +1032,7 @@ abstract class HTMLFormField {
         * Recursively forces values in an array to strings, because issues arise
         * with integer 0 as a value.
         *
-        * @param array $array
+        * @param array|string $array
         * @return array|string
         */
        public static function forceToStringRecursive( $array ) {
index 8ce3c83..8e51858 100644 (file)
@@ -196,7 +196,7 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
         * line above the options in the case of a checkbox matrix, i.e. it's always
         * a "vertical-label".
         *
-        * @param string $value The value to set the input to
+        * @param string|array $value The value to set the input to
         *
         * @return string Complete HTML table row
         */
index be8f7d8..4372cd1 100644 (file)
@@ -18,7 +18,7 @@ class HTMLFormFieldWithButton extends HTMLFormField {
        /** @var string $mButtonType Value for the button in this field */
        protected $mButtonValue;
 
-       /** @var string $mButtonType Value for the button in this field */
+       /** @var string[] $mButtonType Value for the button in this field */
        protected $mButtonFlags = [ 'progressive' ];
 
        public function __construct( $info ) {
index d80a718..76701f8 100644 (file)
@@ -5,8 +5,23 @@ namespace Wikimedia\Rdbms;
 use InvalidArgumentException;
 
 /**
- * Helper class to handle automatically marking connections as reusable (via RAII pattern)
- * as well handling deferring the actual network connection until the handle is used
+ * Helper class used for automatically marking an IDatabase connection as reusable (once it no
+ * longer matters which DB domain is selected) and for deferring the actual network connection
+ *
+ * This uses an RAII-style pattern where calling code is expected to keep the returned reference
+ * handle as a function variable that falls out of scope when no longer needed. This avoids the
+ * need for matching reuseConnection() calls for every "return" statement as well as the tedious
+ * use of try/finally.
+ *
+ * @par Example:
+ * @code
+ *     function getRowData() {
+ *         $conn = $this->lb->getConnectedRef( DB_REPLICA );
+ *         $row = $conn->select( ... );
+ *         return $row ? (array)$row : false;
+ *         // $conn falls out of scope and $this->lb->reuseConnection() gets called
+ *     }
+ * @endcode
  *
  * @ingroup Database
  * @since 1.22
index b086beb..2f9857f 100644 (file)
@@ -23,6 +23,7 @@
 namespace Wikimedia\Rdbms;
 
 use Exception;
+use LogicException;
 use InvalidArgumentException;
 
 /**
@@ -85,6 +86,8 @@ interface ILoadBalancer {
 
        /** @var string Domain specifier when no specific database needs to be selected */
        const DOMAIN_ANY = '';
+       /** @var bool The generic query group (bool gives b/c with 1.33 method signatures) */
+       const GROUP_GENERIC = false;
 
        /** @var int DB handle should have DBO_TRX disabled and the caller will leave it as such */
        const CONN_TRX_AUTOCOMMIT = 1;
@@ -100,25 +103,26 @@ interface ILoadBalancer {
         * Construct a manager of IDatabase connection objects
         *
         * @param array $params Parameter map with keys:
-        *  - servers : Required. Array of server info structures.
-        *  - localDomain: A DatabaseDomain or domain ID string.
-        *  - loadMonitor : Name of a class used to fetch server lag and load.
+        *  - servers : List of server info structures
+        *  - localDomain: A DatabaseDomain or domain ID string
+        *  - loadMonitor : Name of a class used to fetch server lag and load
         *  - readOnlyReason : Reason the master DB is read-only if so [optional]
         *  - waitTimeout : Maximum time to wait for replicas for consistency [optional]
         *  - maxLag: Try to avoid DB replicas with lag above this many seconds [optional]
         *  - srvCache : BagOStuff object for server cache [optional]
         *  - wanCache : WANObjectCache object [optional]
         *  - chronologyCallback: Callback to run before the first connection attempt [optional]
+        *  - defaultGroup: Default query group; the generic group if not specified [optional]
         *  - hostname : The name of the current server [optional]
-        *  - cliMode: Whether the execution context is a CLI script. [optional]
-        *  - profiler : Class name or instance with profileIn()/profileOut() methods. [optional]
-        *  - trxProfiler: TransactionProfiler instance. [optional]
-        *  - replLogger: PSR-3 logger instance. [optional]
-        *  - connLogger: PSR-3 logger instance. [optional]
-        *  - queryLogger: PSR-3 logger instance. [optional]
-        *  - perfLogger: PSR-3 logger instance. [optional]
-        *  - errorLogger : Callback that takes an Exception and logs it. [optional]
-        *  - deprecationLogger: Callback to log a deprecation warning. [optional]
+        *  - cliMode: Whether the execution context is a CLI script [optional]
+        *  - profiler : Class name or instance with profileIn()/profileOut() methods [optional]
+        *  - trxProfiler: TransactionProfiler instance [optional]
+        *  - replLogger: PSR-3 logger instance [optional]
+        *  - connLogger: PSR-3 logger instance [optional]
+        *  - queryLogger: PSR-3 logger instance [optional]
+        *  - perfLogger: PSR-3 logger instance [optional]
+        *  - errorLogger : Callback that takes an Exception and logs it [optional]
+        *  - deprecationLogger: Callback to log a deprecation warning [optional]
         *  - roundStage: STAGE_POSTCOMMIT_* class constant; for internal use [optional]
         *  - ownerId: integer ID of an LBFactory instance that manages this instance [optional]
         * @throws InvalidArgumentException
@@ -159,9 +163,9 @@ interface ILoadBalancer {
         * Subsequent calls with the same $group will not need to make new connection attempts
         * since the acquired connection for each group is preserved.
         *
-        * @param string|bool $group Query group, or false for the generic group
-        * @param string|bool $domain Domain ID, or false for the current domain
-        * @throws DBError
+        * @param string|bool $group Query group or false for the generic group
+        * @param string|bool $domain DB domain ID or false for the local domain
+        * @throws DBError If no live handle can be obtained
         * @return bool|int|string
         */
        public function getReaderIndex( $group = false, $domain = false );
@@ -204,7 +208,8 @@ interface ILoadBalancer {
         * Get any open connection to a given server index, local or foreign
         *
         * Use CONN_TRX_AUTOCOMMIT to only look for connections opened with that flag.
-        * Avoid the use of begin() or startAtomic() on any such connections.
+        * Avoid the use of transaction methods like IDatabase::begin() or IDatabase::startAtomic()
+        * on any such connections.
         *
         * @param int $i Server index or DB_MASTER/DB_REPLICA
         * @param int $flags Bitfield of CONN_* class constants
@@ -213,95 +218,136 @@ interface ILoadBalancer {
        public function getAnyOpenConnection( $i, $flags = 0 );
 
        /**
-        * Get a connection handle by server index
-        *
-        * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
-        * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
-        * can be used to check such flags beforehand.
-        *
-        * If the caller uses $domain or sets CONN_TRX_AUTOCOMMIT in $flags, then it must
-        * also call ILoadBalancer::reuseConnection() on the handle when finished using it.
-        * In all other cases, this is not necessary, though not harmful either.
-        * Avoid the use of begin() or startAtomic() on any such connections.
+        * Get a live handle for a real or virtual (DB_MASTER/DB_REPLICA) server index
+        *
+        * The server index, $i, can be one of the following:
+        *   - DB_REPLICA: a server index will be selected by the load balancer based on read
+        *      weight, connectivity, and replication lag. Note that the master server might be
+        *      configured with read weight. If $groups is empty then it means "the generic group",
+        *      in which case all servers defined with read weight will be considered. Additional
+        *      query groups can be configured, having their own list of server indexes and read
+        *      weights. If a query group list is provided in $groups, then each recognized group
+        *      will be tried, left-to-right, until server index selection succeeds or all groups
+        *      have been tried, in which case the generic group will be tried.
+        *   - DB_MASTER: the master server index will be used; the same as getWriterIndex().
+        *      The value of $groups should be [] when using this server index.
+        *   - Specific server index: a positive integer can be provided to use the server with
+        *      that index. An error will be thrown in no such server index is recognized. This
+        *      server selection method is usually only useful for internal load balancing logic.
+        *      The value of $groups should be [] when using a specific server index.
+        *
+        * Handles acquired by this method, getConnectionRef(), getLazyConnectionRef(), and
+        * getMaintenanceConnectionRef() use the same set of shared connection pools. Callers that
+        * get a *local* DB domain handle for the same server will share one handle for all of those
+        * callers using CONN_TRX_AUTOCOMMIT (via $flags) and one handle for all of those callers not
+        * using CONN_TRX_AUTOCOMMIT. Callers that get a *foreign* DB domain handle (via $domain) will
+        * share any handle that has the right CONN_TRX_AUTOCOMMIT mode and is already on the right
+        * DB domain. Otherwise, one of the "free for reuse" handles will be claimed or a new handle
+        * will be made if there are none.
+        *
+        * Handle sharing is particularly useful when callers get local DB domain (the default),
+        * transaction round aware (the default), DB_MASTER handles. All such callers will operate
+        * within a single database transaction as a consequence. Handle sharing is also useful when
+        * callers get local DB domain (the default), transaction round aware (the default), samely
+        * query grouped (the default), DB_REPLICA handles. All such callers will operate within a
+        * single database transaction as a consequence.
+        *
+        * Calling functions that use $domain must call reuseConnection() once the last query of the
+        * function is executed. This lets the load balancer share this handle with other callers
+        * requesting connections on different database domains.
+        *
+        * Use CONN_TRX_AUTOCOMMIT to use a separate pool of only auto-commit handles. This flag
+        * is ignored for databases with ATTR_DB_LEVEL_LOCKING (e.g. sqlite) in order to avoid
+        * deadlocks. getServerAttributes() can be used to check such attributes beforehand. Avoid
+        * using IDatabase::begin() and IDatabase::commit() on such handles. If it is not possible
+        * to avoid using methods like IDatabase::startAtomic() and IDatabase::endAtomic(), callers
+        * should at least make sure that the atomic sections are closed on failure via try/catch
+        * and IDatabase::cancelAtomic().
+        *
+        * @see ILoadBalancer::reuseConnection()
+        * @see ILoadBalancer::getServerAttributes()
         *
         * @param int $i Server index (overrides $groups) or DB_MASTER/DB_REPLICA
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string[]|string $groups Query group(s) or [] to use the default group
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @param int $flags Bitfield of CONN_* class constants
-        *
-        * @note This method throws DBAccessError if ILoadBalancer::disable() was called
-        *
-        * @throws DBError If any error occurs that prevents the yielding of a (live) IDatabase
-        * @return IDatabase|bool This returns false on failure if CONN_SILENCE_ERRORS is set
+        * @return IDatabase|bool Live connection handle or false on failure
+        * @throws DBError If no live handle can be obtained and CONN_SILENCE_ERRORS is not set
+        * @throws DBAccessError If disable() was previously called
         */
        public function getConnection( $i, $groups = [], $domain = false, $flags = 0 );
 
        /**
-        * Mark a foreign connection as being available for reuse under a different DB domain
+        * Mark a live handle as being available for reuse under a different database domain
+        *
+        * This mechanism is reference-counted, and must be called the same number of times as
+        * getConnection() to work. Never call this on handles acquired via getConnectionRef(),
+        * getLazyConnectionRef(), and getMaintenanceConnectionRef(), as they already manage
+        * the logic of calling this method when they fall out of scope in PHP.
         *
-        * This mechanism is reference-counted, and must be called the same number of times
-        * as getConnection() to work.
+        * @see ILoadBalancer::getConnection()
         *
         * @param IDatabase $conn
-        * @throws InvalidArgumentException
+        * @throws LogicException
         */
        public function reuseConnection( IDatabase $conn );
 
        /**
-        * Get a database connection handle reference
-        *
-        * The handle's methods simply wrap those of a Database handle
+        * Get a live database handle reference for a real or virtual (DB_MASTER/DB_REPLICA) server index
         *
         * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
-        * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+        * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
         * can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
         * on any CONN_TRX_AUTOCOMMIT connections.
         *
         * @see ILoadBalancer::getConnection() for parameter information
         *
         * @param int $i Server index or DB_MASTER/DB_REPLICA
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string[]|string $groups Query group(s) or [] to use the default group
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
         * @return DBConnRef
         */
        public function getConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
 
        /**
-        * Get a database connection handle reference without connecting yet
+        * Get a database handle reference for a real or virtual (DB_MASTER/DB_REPLICA) server index
         *
-        * The handle's methods simply wrap those of a Database handle
+        * The handle's methods simply proxy to those of an underlying IDatabase handle which
+        * takes care of the actual connection and query logic.
         *
         * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
-        * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+        * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
         * can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
         * on any CONN_TRX_AUTOCOMMIT connections.
         *
         * @see ILoadBalancer::getConnection() for parameter information
         *
         * @param int $i Server index or DB_MASTER/DB_REPLICA
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string[]|string $groups Query group(s) or [] to use the default group
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
         * @return DBConnRef
         */
        public function getLazyConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
 
        /**
-        * Get a maintenance database connection handle reference for migrations and schema changes
+        * Get a live database handle for a real or virtual (DB_MASTER/DB_REPLICA) server index
+        * that can be used for data migrations and schema changes
         *
-        * The handle's methods simply wrap those of a Database handle
+        * The handle's methods simply proxy to those of an underlying IDatabase handle which
+        * takes care of the actual connection and query logic.
         *
         * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
-        * (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
+        * (e.g. sqlite) in order to avoid deadlocks. getServerAttributes()
         * can be used to check such flags beforehand. Avoid the use of begin() or startAtomic()
         * on any CONN_TRX_AUTOCOMMIT connections.
         *
         * @see ILoadBalancer::getConnection() for parameter information
         *
         * @param int $i Server index or DB_MASTER/DB_REPLICA
-        * @param array|string|bool $groups Query group(s), or false for the generic reader
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string[]|string $groups Query group(s) or [] to use the default group
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
         * @return MaintainableDBConnRef
         */
@@ -362,7 +408,7 @@ interface ILoadBalancer {
        public function getServerName( $i );
 
        /**
-        * Return the server info structure for a given index, or false if the index is invalid.
+        * Return the server info structure for a given index or false if the index is invalid.
         * @param int $i
         * @return array|bool
         * @since 1.31
@@ -544,7 +590,7 @@ interface ILoadBalancer {
 
        /**
         * @note This method will trigger a DB connection if not yet done
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @return bool Whether the database for generic connections this request is highly "lagged"
         */
        public function getLaggedReplicaMode( $domain = false );
@@ -561,7 +607,7 @@ interface ILoadBalancer {
 
        /**
         * @note This method may trigger a DB connection if not yet done
-        * @param string|bool $domain Domain ID, or false for the current domain
+        * @param string|bool $domain DB domain ID or false for the local domain
         * @param IDatabase|null $conn DB master connection; used to avoid loops [optional]
         * @return string|bool Reason the master is read-only or false if it is not
         */
@@ -607,7 +653,7 @@ interface ILoadBalancer {
         * May attempt to open connections to replica DBs on the default DB. If there is
         * no lag, the maximum lag will be reported as -1.
         *
-        * @param bool|string $domain Domain ID, or false for the default database
+        * @param bool|string $domain Domain ID or false for the default database
         * @return array ( host, max lag, index of max lagged host )
         */
        public function getMaxLag( $domain = false );
index 44d526c..b640dc0 100644 (file)
@@ -28,6 +28,7 @@ use BagOStuff;
 use EmptyBagOStuff;
 use WANObjectCache;
 use ArrayUtils;
+use LogicException;
 use UnexpectedValueException;
 use InvalidArgumentException;
 use RuntimeException;
@@ -84,8 +85,10 @@ class LoadBalancer implements ILoadBalancer {
        private $loadMonitorConfig;
        /** @var string Alternate local DB domain instead of DatabaseDomain::getId() */
        private $localDomainIdAlias;
-       /** @var int */
+       /** @var int Amount of replication lag, in seconds, that is considered "high" */
        private $maxLag;
+       /** @var string|bool The query group list to be used by default */
+       private $defaultGroup;
 
        /** @var string Current server name */
        private $hostname;
@@ -101,11 +104,11 @@ class LoadBalancer implements ILoadBalancer {
        /** @var array[] Map of (name => callable) */
        private $trxRecurringCallbacks = [];
 
-       /** @var Database DB connection object that caused a problem */
+       /** @var Database Connection handle that caused a problem */
        private $errorConnection;
-       /** @var int The generic (not query grouped) replica DB index */
+       /** @var int The generic (not query grouped) replica server index */
        private $genericReadIndex = -1;
-       /** @var int[] The group replica DB indexes keyed by group */
+       /** @var int[] The group replica server indexes keyed by group */
        private $readIndexByGroup = [];
        /** @var bool|DBMasterPos Replication sync position or false if not set */
        private $waitForPos;
@@ -113,9 +116,9 @@ class LoadBalancer implements ILoadBalancer {
        private $laggedReplicaMode = false;
        /** @var bool Whether the generic reader fell back to a lagged replica DB */
        private $allReplicasDownMode = false;
-       /** @var string The last DB selection or connection error */
+       /** @var string The last DB domain selection or connection error */
        private $lastError = 'Unknown error';
-       /** @var string|bool Reason the LB is read-only or false if not */
+       /** @var string|bool Reason this instance is read-only or false if not */
        private $readOnlyReason = false;
        /** @var int Total number of new connections ever made with this instance */
        private $connectionCounter = 0;
@@ -131,9 +134,6 @@ class LoadBalancer implements ILoadBalancer {
        /** @var string Stage of the current transaction round in the transaction round life-cycle */
        private $trxRoundStage = self::ROUND_CURSORY;
 
-       /** @var string|null */
-       private $defaultGroup = null;
-
        /** @var int Warn when this many connection are held */
        const CONN_HELD_WARN_THRESHOLD = 10;
 
@@ -244,7 +244,7 @@ class LoadBalancer implements ILoadBalancer {
                        }
                }
 
-               $this->defaultGroup = $params['defaultGroup'] ?? null;
+               $this->defaultGroup = $params['defaultGroup'] ?? self::GROUP_GENERIC;
                $this->ownerId = $params['ownerId'] ?? null;
        }
 
@@ -274,6 +274,30 @@ class LoadBalancer implements ILoadBalancer {
                return (string)$domain;
        }
 
+       /**
+        * @param string[]|string|bool $groups Query group list or false for the default
+        * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+        * @return string[]|bool[] Query group list
+        */
+       private function resolveGroups( $groups, $i ) {
+               if ( $groups === false ) {
+                       $resolvedGroups = [ $this->defaultGroup ];
+               } elseif ( is_string( $groups ) ) {
+                       $resolvedGroups = [ $groups ];
+               } elseif ( is_array( $groups ) ) {
+                       $resolvedGroups = $groups ?: [ $this->defaultGroup ];
+               } else {
+                       throw new InvalidArgumentException( "Invalid query groups provided" );
+               }
+
+               if ( $groups && $i > 0 ) {
+                       $groupList = implode( ', ', $groups );
+                       throw new LogicException( "Got query groups ($groupList) with a server index (#$i)" );
+               }
+
+               return $resolvedGroups;
+       }
+
        /**
         * @param int $flags
         * @return bool
@@ -399,43 +423,42 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        /**
-        * @param int $i
-        * @param array $groups
+        * Get the server index to use for a specified server index and query group list
+        *
+        * @param int $i Specific server index or DB_MASTER/DB_REPLICA
+        * @param string[]|bool[] $groups Resolved query group list (non-empty)
         * @param string|bool $domain
-        * @return int The index of a specific server (replica DBs are checked for connectivity)
+        * @return int A specific server index (replica DBs are checked for connectivity)
         */
-       private function getConnectionIndex( $i, $groups, $domain ) {
-               // Check one "group" per default: the generic pool
-               $defaultGroups = $this->defaultGroup ? [ $this->defaultGroup ] : [ false ];
-
-               $groups = ( $groups === false || $groups === [] )
-                       ? $defaultGroups
-                       : (array)$groups;
-
+       private function getConnectionIndex( $i, array $groups, $domain ) {
                if ( $i === self::DB_MASTER ) {
                        $i = $this->getWriterIndex();
                } elseif ( $i === self::DB_REPLICA ) {
-                       # Try to find an available server in any the query groups (in order)
+                       // Find an available server in any of the query groups (in order)
                        foreach ( $groups as $group ) {
                                $groupIndex = $this->getReaderIndex( $group, $domain );
                                if ( $groupIndex !== false ) {
-                                       $i = $groupIndex;
+                                       $i = $groupIndex; // group connection succeeded
                                        break;
                                }
                        }
+               } elseif ( !isset( $this->servers[$i] ) ) {
+                       throw new UnexpectedValueException( "Invalid server index index #$i" );
                }
 
-               # Operation-based index
                if ( $i === self::DB_REPLICA ) {
-                       $this->lastError = 'Unknown error'; // reset error string
-                       # Try the general server pool if $groups are unavailable.
-                       $i = ( $groups === [ false ] )
-                               ? false // don't bother with this if that is what was tried above
-                               : $this->getReaderIndex( false, $domain );
-                       # Couldn't find a working server in getReaderIndex()?
+                       // No specific server was yet found
+                       $this->lastError = 'Unknown error'; // set here in case of worse failure
+                       // Either make one last connection attempt or give up
+                       $i = in_array( $this->defaultGroup, $groups, true )
+                               // Connection attempt already included the default query group; give up
+                               ? false
+                               // Connection attempt was for other query groups; try the default one
+                               : $this->getReaderIndex( $this->defaultGroup, $domain );
+
                        if ( $i === false ) {
+                               // Still coundn't find a working non-zero read load server
                                $this->lastError = 'No working replica DB server: ' . $this->lastError;
-                               // Throw an exception
                                $this->reportConnectionError();
                                return null; // unreachable due to exception
                        }
@@ -456,7 +479,7 @@ class LoadBalancer implements ILoadBalancer {
                        return $index;
                }
 
-               if ( $group !== false ) {
+               if ( $group !== self::GROUP_GENERIC ) {
                        // Use the server weight array for this load group
                        if ( isset( $this->groupLoads[$group] ) ) {
                                $loads = $this->groupLoads[$group];
@@ -492,7 +515,7 @@ class LoadBalancer implements ILoadBalancer {
                // Cache the reader index for future DB_REPLICA handles
                $this->setExistingReaderIndex( $group, $i );
                // Record whether the generic reader index is in "lagged replica DB" mode
-               if ( $group === false && $laggedReplicaMode ) {
+               if ( $group === self::GROUP_GENERIC && $laggedReplicaMode ) {
                        $this->laggedReplicaMode = true;
                }
 
@@ -509,7 +532,7 @@ class LoadBalancer implements ILoadBalancer {
         * @return int Server index or -1 if none was chosen
         */
        protected function getExistingReaderIndex( $group ) {
-               if ( $group === false ) {
+               if ( $group === self::GROUP_GENERIC ) {
                        $index = $this->genericReadIndex;
                } else {
                        $index = $this->readIndexByGroup[$group] ?? -1;
@@ -529,7 +552,7 @@ class LoadBalancer implements ILoadBalancer {
                        throw new UnexpectedValueException( "Cannot set a negative read server index" );
                }
 
-               if ( $group === false ) {
+               if ( $group === self::GROUP_GENERIC ) {
                        $this->genericReadIndex = $index;
                } else {
                        $this->readIndexByGroup[$group] = $index;
@@ -704,7 +727,9 @@ class LoadBalancer implements ILoadBalancer {
                $i = ( $i === self::DB_MASTER ) ? $this->getWriterIndex() : $i;
                $autocommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
 
+               $conn = false;
                foreach ( $this->conns as $connsByServer ) {
+                       // Get the connection array server indexes to inspect
                        if ( $i === self::DB_REPLICA ) {
                                $indexes = array_keys( $connsByServer );
                        } else {
@@ -712,18 +737,47 @@ class LoadBalancer implements ILoadBalancer {
                        }
 
                        foreach ( $indexes as $index ) {
-                               foreach ( $connsByServer[$index] as $conn ) {
-                                       if ( !$conn->isOpen() ) {
-                                               continue; // some sort of error occured?
-                                       }
-                                       if ( !$autocommit || $conn->getLBInfo( 'autoCommitOnly' ) ) {
-                                               return $conn;
-                                       }
+                               $conn = $this->pickAnyOpenConnection( $connsByServer[$index], $autocommit );
+                               if ( $conn ) {
+                                       break;
                                }
                        }
                }
 
-               return false;
+               if ( $conn ) {
+                       $this->enforceConnectionFlags( $conn, $flags );
+               }
+
+               return $conn;
+       }
+
+       /**
+        * @param IDatabase[] $candidateConns
+        * @param bool $autocommit Whether to only look for auto-commit connections
+        * @return IDatabase|false An appropriate open connection or false if none found
+        */
+       private function pickAnyOpenConnection( $candidateConns, $autocommit ) {
+               $conn = false;
+
+               foreach ( $candidateConns as $candidateConn ) {
+                       if ( !$candidateConn->isOpen() ) {
+                               continue; // some sort of error occured?
+                       } elseif (
+                               $autocommit &&
+                               (
+                                       // Connection is transaction round aware
+                                       !$candidateConn->getLBInfo( 'autoCommitOnly' ) ||
+                                       // Some sort of error left a transaction open?
+                                       $candidateConn->trxLevel()
+                               )
+                       ) {
+                               continue; // some sort of error left a transaction open?
+                       }
+
+                       $conn = $candidateConn;
+               }
+
+               return $conn;
        }
 
        /**
@@ -823,12 +877,7 @@ class LoadBalancer implements ILoadBalancer {
        }
 
        public function getConnection( $i, $groups = [], $domain = false, $flags = 0 ) {
-               if ( !is_int( $i ) ) {
-                       throw new InvalidArgumentException( "Cannot connect without a server index" );
-               } elseif ( $groups && $i > 0 ) {
-                       throw new InvalidArgumentException( "Got query groups with server index #$i" );
-               }
-
+               $groups = $this->resolveGroups( $groups, $i );
                $domain = $this->resolveDomainID( $domain );
                $flags = $this->sanitizeConnectionFlags( $flags );
                $masterOnly = ( $i === self::DB_MASTER || $i === $this->getWriterIndex() );
@@ -896,7 +945,7 @@ class LoadBalancer implements ILoadBalancer {
                        // Database instance to this method. Any caller passing in a DBConnRef is broken.
                        $this->connLogger->error(
                                __METHOD__ . ": got DBConnRef instance.\n" .
-                               ( new RuntimeException() )->getTraceAsString() );
+                               ( new LogicException() )->getTraceAsString() );
 
                        return;
                }
@@ -1154,14 +1203,9 @@ class LoadBalancer implements ILoadBalancer {
         * Test if the specified index represents an open connection
         *
         * @param int $index Server index
-        * @private
         * @return bool
         */
        private function isOpen( $index ) {
-               if ( !is_int( $index ) ) {
-                       return false;
-               }
-
                return (bool)$this->getAnyOpenConnection( $index );
        }
 
index 12cfe83..16b83d1 100644 (file)
@@ -29,7 +29,7 @@ use Wikimedia\Rdbms\ResultWrapper;
  * @ingroup Media
  */
 class ImagePage extends Article {
-       /** @var File */
+       /** @var File|false */
        private $displayImg;
 
        /** @var FileRepo */
@@ -801,7 +801,7 @@ EOT
        }
 
        /**
-        * @param string $target
+        * @param string|string[] $target
         * @param int $limit
         * @return ResultWrapper
         */
index 013dd75..acd506b 100644 (file)
@@ -29,13 +29,13 @@ use Wikimedia\Rdbms\FakeResultWrapper;
  * @ingroup Media
  */
 class WikiFilePage extends WikiPage {
-       /** @var File */
+       /** @var File|false */
        protected $mFile = false;
-       /** @var LocalRepo */
+       /** @var LocalRepo|null */
        protected $mRepo = null;
        /** @var bool */
        protected $mFileLoaded = false;
-       /** @var array */
+       /** @var array|null */
        protected $mDupes = null;
 
        public function __construct( $title ) {
index 05c622a..95563d2 100644 (file)
@@ -187,7 +187,7 @@ class SpecialUndelete extends SpecialPage {
                if ( $this->mTimestamp !== '' ) {
                        $this->showRevision( $this->mTimestamp );
                } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
-                       $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
+                       $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
                        // Check if user is allowed to see this file
                        if ( !$file->exists() ) {
                                $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
@@ -651,7 +651,7 @@ class SpecialUndelete extends SpecialPage {
                $out = $this->getOutput();
                $lang = $this->getLanguage();
                $user = $this->getUser();
-               $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
+               $file = new ArchivedFile( $this->mTargetObj, 0, $this->mFilename );
                $out->addWikiMsg( 'undelete-show-file-confirm',
                        $this->mTargetObj->getText(),
                        $lang->userDate( $file->getTimestamp(), $user ),
index 235ff4a..6f899f8 100644 (file)
@@ -23,6 +23,7 @@
                $( '.dbRadio' ).on( 'click', function () {
                        var $checked = $( '.dbRadio:checked' ),
                                $wrapper = $( document.getElementById( $checked.attr( 'rel' ) ) );
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        if ( $wrapper.is( ':hidden' ) ) {
                                // FIXME: Use CSS transition
                                // eslint-disable-next-line no-jquery/no-animate-toggle
index bb52c61..7286812 100644 (file)
       }
     },
     "eslint-config-wikimedia": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.12.0.tgz",
-      "integrity": "sha512-ZkmGLvwmoEacj55t8Z6VH6wUu4/XTlgkSCerHkj+VU4tmyCD4mlzvTeSaPzOEDmZTVWUoiKnB6mvUx06l7uIbw==",
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.13.0.tgz",
+      "integrity": "sha512-l64xMCgPE949H9rfhC0Ir+UaNAh685CE6xSnWkMU5yNryNTdL91lW8KcMFgMqmmH0Q+Jq+7DIdfGaVld6nQ80w==",
       "dev": true,
       "requires": {
         "eslint": "^5.16.0",
         "eslint-plugin-json": "^1.4.0",
-        "eslint-plugin-no-jquery": "^2.0.0",
+        "eslint-plugin-no-jquery": "^2.1.0",
         "eslint-plugin-qunit": "^4.0.0"
       },
       "dependencies": {
       }
     },
     "eslint-plugin-no-jquery": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.0.0.tgz",
-      "integrity": "sha512-aFy3fMBlc630/qeasjocb9uIqmwoyOmmTQiBaDs70Aryqi9uPH0EZLPtIOshDMcGeAkyyAkcc+WuIw6bRsoLuw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.1.0.tgz",
+      "integrity": "sha512-5sr5tOJRfuRviyAvFTe/mr80TXWxTteD/JHRuJtDN8q/bxAh16eSKoKLAevLC7wZCRN2iwnEfhQPQV4rp/gYtg==",
       "dev": true
     },
     "eslint-plugin-qunit": {
       "dev": true
     },
     "vscode-json-languageservice": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.2.1.tgz",
-      "integrity": "sha512-ee9MJ70/xR55ywvm0bZsDLhA800HCRE27AYgMNTU14RSg20Y+ngHdQnUt6OmiTXrQDI/7sne6QUOtHIN0hPQYA==",
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-3.3.0.tgz",
+      "integrity": "sha512-upq1PhwDItazdtRJ/R7uU0Fgrf9iaYa1xLK4WFLExR0DgbPojd0YgMpfyknVyXGlxsg3fJQ0H7J++QeByXHh9w==",
       "dev": true,
       "requires": {
-        "jsonc-parser": "^2.0.2",
-        "vscode-languageserver-types": "^3.13.0",
-        "vscode-nls": "^4.0.0",
-        "vscode-uri": "^1.0.6"
+        "jsonc-parser": "^2.1.0",
+        "vscode-languageserver-types": "^3.15.0-next.2",
+        "vscode-nls": "^4.1.1",
+        "vscode-uri": "^2.0.1"
       }
     },
     "vscode-languageserver-types": {
-      "version": "3.14.0",
-      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz",
-      "integrity": "sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==",
+      "version": "3.15.0-next.2",
+      "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.0-next.2.tgz",
+      "integrity": "sha512-2JkrMWWUi2rlVLSo9OFR2PIGUzdiowEM8NgNYiwLKnXTjpwpjjIrJbNNxDik7Rv4oo9KtikcFQZKXbrKilL/MQ==",
       "dev": true
     },
     "vscode-nls": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.0.tgz",
-      "integrity": "sha512-zKsFWVzL1wlCezgaI3XiN42IT8DIPM1Qr+G+RBhiU3U0bJCdC8pPELakRCtuVT4wF3gBZjBrUDQ8mowL7hmgwA==",
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.1.1.tgz",
+      "integrity": "sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==",
       "dev": true
     },
     "vscode-uri": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.6.tgz",
-      "integrity": "sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww==",
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-2.0.2.tgz",
+      "integrity": "sha512-VebpIxm9tG0fG2sBOhnsSPzDYuNUPP1UQW4K3mwthlca4e4f3d6HKq3HkITC2OPFomOaB7pHTSjcpdFWjfYTzg==",
       "dev": true
     },
     "wdio-dot-reporter": {
index 9f14c78..f9fe5cb 100644 (file)
@@ -11,7 +11,7 @@
     "selenium-test": "wdio ./tests/selenium/wdio.conf.js"
   },
   "devDependencies": {
-    "eslint-config-wikimedia": "0.12.0",
+    "eslint-config-wikimedia": "0.13.0",
     "grunt": "1.0.4",
     "grunt-banana-checker": "0.7.0",
     "grunt-contrib-copy": "1.0.0",
index d34b06c..7a131da 100644 (file)
@@ -83,6 +83,7 @@
                // eslint-disable-next-line no-jquery/no-map-util
                return $.map( node.childNodes, function ( elem ) {
                        if ( elem.nodeType === Node.ELEMENT_NODE ) {
+                               // eslint-disable-next-line no-jquery/no-class-state
                                if ( $( elem ).hasClass( 'reference' ) ) {
                                        return null;
                                }
 
                while ( i < l ) {
                        // if this is a child row, continue to the next row (as buildCache())
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( rows[ rowIndex ] && !$( rows[ rowIndex ] ).hasClass( config.cssChildRow ) ) {
                                if ( rowIndex !== lastRowIndex ) {
                                        lastRowIndex = rowIndex;
 
                        // if this is a child row, add it to the last row's children and
                        // continue to the next row
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $row.hasClass( config.cssChildRow ) ) {
                                cache.row[ cache.row.length - 1 ] = cache.row[ cache.row.length - 1 ].add( $row );
                                // go to the next for loop
                        $cell = $( this );
                        columns = [];
 
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( !$cell.hasClass( config.unsortableClass ) ) {
                                $cell
                                        .addClass( config.cssHeader )
 
                        $row = $rows.eq( i );
                        // if this is a child row, continue to the next row (as buildCache())
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $row.hasClass( config.cssChildRow ) ) {
                                // go to the next for loop
                                continue;
index 08bb601..70c32b9 100644 (file)
@@ -88,6 +88,7 @@
                        sideMargin = 'marginLeft';
                }
 
+               // eslint-disable-next-line no-jquery/no-class-state
                if ( $element.hasClass( 'jquery-confirmable-element' ) ) {
                        $wrapper = $element.closest( '.jquery-confirmable-wrapper' );
                        $interface = $wrapper.find( '.jquery-confirmable-interface' );
index b9c13f0..20bd405 100644 (file)
                if ( options.wasCollapsed !== undefined ) {
                        wasCollapsed = options.wasCollapsed;
                } else {
+                       // eslint-disable-next-line no-jquery/no-class-state
                        wasCollapsed = $collapsible.hasClass( 'mw-collapsed' );
                }
 
                        } );
 
                        // Initial state
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( options.collapsed || $collapsible.hasClass( 'mw-collapsed' ) ) {
                                // One toggler can hook to multiple elements, and one element can have
                                // multiple togglers. This is the sanest way to handle that.
index 5111295..f4aea72 100644 (file)
                                context.data.prevText = '';
                        } else if (
                                val !== context.data.prevText ||
+                               // eslint-disable-next-line no-jquery/no-sizzle
                                !context.data.$container.is( ':visible' )
                        ) {
                                context.data.prevText = val;
         */
        function keypress( e, context, key ) {
                var selected,
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        wasVisible = context.data.$container.is( ':visible' ),
                        preventDefault = false;
 
index e638108..5b43847 100644 (file)
@@ -80,15 +80,21 @@ $( function () {
                        $copyAction = $copyForm.find( '> [name="action"]' );
 
                        // Remove action=historysubmit and ids[..]=..
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $historySubmitter.hasClass( 'mw-history-compareselectedversions-button' ) ) {
                                $copyAction.remove();
                                $copyForm.find( 'input[name^="ids["]:checked' ).prop( 'checked', false );
 
                        // Remove diff=&oldid=, change action=historysubmit to revisiondelete, remove revisiondelete
-                       } else if ( $historySubmitter.hasClass( 'mw-history-revisiondelete-button' ) ||
-                                       $historySubmitter.hasClass( 'mw-history-editchangetags-button' ) ) {
+                       } else if (
+                               // eslint-disable-next-line no-jquery/no-class-state
+                               $historySubmitter.hasClass( 'mw-history-revisiondelete-button' ) ||
+                               // eslint-disable-next-line no-jquery/no-class-state
+                               $historySubmitter.hasClass( 'mw-history-editchangetags-button' )
+                       ) {
                                $copyRadios.remove();
                                $copyAction.val( $historySubmitter.attr( 'name' ) );
+                               // eslint-disable-next-line no-jquery/no-sizzle
                                $copyForm.find( ':submit' ).remove();
                        }
 
index 393846d..4db8445 100644 (file)
@@ -30,6 +30,7 @@
                                                e.type === 'click' ||
                                                e.type === 'keypress' && e.which === 13
                                        ) {
+                                               // eslint-disable-next-line no-jquery/no-class-state
                                                if ( $table.hasClass( 'collapsed' ) ) {
                                                        // From collapsed to expanded. Button will now collapse.
                                                        $( this ).text( collapseText );
@@ -37,6 +38,7 @@
                                                        // From expanded to collapsed. Button will now expand.
                                                        $( this ).text( expandText );
                                                }
+                                               // eslint-disable-next-line no-jquery/no-class-state
                                                $table.toggleClass( 'collapsed' );
                                        }
                                } );
index ecaddd8..674584b 100644 (file)
                        };
                        if (
                                $oldErrorBox !== $errorBox &&
+                               // eslint-disable-next-line no-jquery/no-class-state
                                ( $oldErrorBox.hasClass( 'error' ) || $oldErrorBox.hasClass( 'warning' ) )
                        ) {
                                // eslint-disable-next-line no-jquery/no-slide
index 8ead7a4..99eebae 100644 (file)
                        var $element = $( this ),
                                deleteButton;
 
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $element.hasClass( 'oo-ui-widget' ) ) {
                                deleteButton = OO.ui.infuse( $element );
                                deleteButton.on( 'click', function () {
                                        deleteButton.$element.closest( 'li.mw-htmlform-cloner-li' ).remove();
                                } );
                        } else {
+                               // eslint-disable-next-line no-jquery/no-sizzle
                                $element.filter( ':input' ).on( 'click', function ( e ) {
                                        e.preventDefault();
                                        $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
                        }
                } );
 
+               // eslint-disable-next-line no-jquery/no-class-state
                if ( $createElement.hasClass( 'oo-ui-widget' ) ) {
                        createButton = OO.ui.infuse( $createElement );
                        createButton.on( 'click', function () {
                                appendToCloner( createButton.$element );
                        } );
                } else {
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        $createElement.filter( ':input' ).on( 'click', function ( e ) {
                                e.preventDefault();
 
index c56aada..f550a91 100644 (file)
 
                        $link = $( this );
 
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $link.hasClass( 'loading' ) ) {
                                return;
                        }
index 78cd8f4..1a5ae6c 100644 (file)
@@ -262,6 +262,7 @@ ChangesListWrapperWidget.prototype.updateEnhancedParentHighlight = function () {
 
                // Collect the relevant classes from the first nested child
                firstChildClasses = activeHighlightClasses.filter( function ( className ) {
+                       // eslint-disable-next-line no-jquery/no-class-state
                        return $table.find( 'tr:nth-child(2)' ).hasClass( className );
                } );
                // Filter the non-head rows and see if they all have the same classes
@@ -271,6 +272,7 @@ ChangesListWrapperWidget.prototype.updateEnhancedParentHighlight = function () {
                                $this = $( this );
 
                        classesInThisRow = activeHighlightClasses.filter( function ( className ) {
+                               // eslint-disable-next-line no-jquery/no-class-state
                                return $this.hasClass( className );
                        } );
 
index ab75653..3429590 100644 (file)
@@ -711,6 +711,7 @@ FilterTagMultiselectWidget.prototype.createTagItemWidget = function ( data ) {
 
 FilterTagMultiselectWidget.prototype.emphasize = function () {
        if (
+               // eslint-disable-next-line no-jquery/no-class-state
                !this.$handle.hasClass( 'mw-rcfilters-ui-filterTagMultiselectWidget-animate' )
        ) {
                this.$handle
index df12b2e..ee70b71 100644 (file)
                                        .text( query );
                        }
 
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
                                $el.parent().attr( 'href', formData.baseHref + $.param( formData.linkParams ) + '&fulltext=1' );
                        } else {
index bc8ca37..6a1d66a 100644 (file)
@@ -6,6 +6,7 @@
        function updateImportSubprojectList() {
                var $projectField = $( '#mw-import-table-interwiki #interwiki' ),
                        $subprojectField = $projectField.parent().find( '#subproject' ),
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        $selected = $projectField.find( ':selected' ),
                        oldValue = $subprojectField.val(),
                        option, options;
index 55d7ce9..1eeedfb 100644 (file)
@@ -10,6 +10,7 @@
                // (This function could be changed to infuse and check OOUI widgets, but that would only make it
                // slower and more complicated. It works fine to treat them as HTML elements.)
                function isPrefsChanged() {
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        var inputs = $( '#mw-prefs-form :input[name]' ),
                                input, $input, inputType,
                                index, optIndex,
index 53617ca..abc8d7b 100644 (file)
@@ -17,6 +17,7 @@
                        mw.loader.load( 'mediawiki.notification' );
 
                        // Use the class to determine whether to watch or unwatch
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( !$subjectLink.hasClass( 'mw-watched-item' ) ) {
                                $link.text( mw.msg( 'watching' ) );
                                promise = api.watch( title ).done( function () {
index 2d60b1d..e8373c3 100644 (file)
 
                                // Depending on whether we are watching or unwatching, for each entry of the page (and its associated page i.e. Talk),
                                // change the text, tooltip, and non-JS href of the (un)watch button, and update the styling of the watchlist entry.
+                               // eslint-disable-next-line no-jquery/no-class-state
                                if ( $unwatchLink.hasClass( 'mw-unwatch-link' ) ) {
                                        api.unwatch( pageTitle )
                                                .done( function () {
index c1066f2..8c69047 100644 (file)
@@ -13,6 +13,7 @@
 
                        // Hide/show the table of contents element
                        function toggleToc() {
+                               // eslint-disable-next-line no-jquery/no-class-state
                                if ( $this.hasClass( 'tochidden' ) ) {
                                        // FIXME: Use CSS transitions
                                        // eslint-disable-next-line no-jquery/no-slide
index 56bfc42..66c1fe7 100644 (file)
                        $portlet.removeClass( 'emptyPortlet' );
 
                        // Setup the list item (and a span if $portlet is a Vector tab)
+                       // eslint-disable-next-line no-jquery/no-class-state
                        if ( $portlet.hasClass( 'vectorTabs' ) ) {
                                item = $( '<li>' ).append( $( '<span>' ).append( link )[ 0 ] )[ 0 ];
                        } else {
index 9fc7edb..2e78ba7 100644 (file)
        mw.widgets.MediaSearchWidget.prototype.runLayoutQueue = function () {
                var i, len;
 
+               // eslint-disable-next-line no-jquery/no-sizzle
                if ( this.$element.is( ':visible' ) ) {
                        for ( i = 0, len = this.layoutQueue.length; i < len; i++ ) {
                                this.layoutQueue.pop()();
index c25db2f..5ca39d5 100644 (file)
                if (
                        !this.isDisabled() &&
                        e.which === 1 &&
+                       // eslint-disable-next-line no-jquery/no-class-state
                        $( e.target ).hasClass( targetClass )
                ) {
                        this.deactivate( true );
index 88a3f43..3da73c1 100644 (file)
@@ -1105,8 +1105,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ] );
                $this->user->mBlock->mTimestamp = 0;
                $this->assertEquals( [ [ 'autoblockedtext',
-                       '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', null, 'infinite', '127.0.8.1',
+                       "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                       "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
                        MediaWikiServices::getInstance()->getPermissionManager()->getPermissionErrors(
                                'move-target', $this->user, $this->title ) );
@@ -1130,8 +1130,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                        'expiry' => 10,
                ] );
                $this->assertEquals( [ [ 'blockedtext',
-                       '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+                       "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                       "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
                        MediaWikiServices::getInstance()->getPermissionManager()
                                ->getPermissionErrors( 'move-target', $this->user, $this->title ) );
@@ -1150,8 +1150,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'systemblockedtext',
-                       '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', 'test', 'infinite', '127.0.8.1',
+                       "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                       "\u{202A}Useruser\u{202C}", 'test', 'infinite', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
@@ -1208,8 +1208,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'blockedtext-partial',
-                       '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+                       "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                       "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
@@ -1282,8 +1282,8 @@ class PermissionManagerTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'blockedtext',
-                       '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                       'Useruser', null, 'infinite', '127.0.8.1',
+                       "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                       "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
                        $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
index 150e2ed..e6cf8c8 100644 (file)
@@ -948,8 +948,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                ] );
                $this->user->mBlock->setTimestamp( 0 );
                $this->assertEquals( [ [ 'autoblockedtext',
-                               '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', null, 'infinite', '127.0.8.1',
+                               "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                               "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ] ],
                        $this->title->getUserPermissionsErrors( 'move-target',
                                $this->user ) );
@@ -970,8 +970,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'expiry' => 10,
                ] );
                $this->assertEquals( [ [ 'blockedtext',
-                               '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+                               "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                               "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
                        $this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
                # $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this )
@@ -988,8 +988,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'systemblockedtext',
-                               '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', 'test', 'infinite', '127.0.8.1',
+                               "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                               "\u{202A}Useruser\u{202C}", 'test', 'infinite', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
@@ -1034,8 +1034,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'blockedtext-partial',
-                               '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
+                               "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                               "\u{202A}Useruser\u{202C}", null, '23:00, 31 December 1969', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
@@ -1102,8 +1102,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                ] );
 
                $errors = [ [ 'blockedtext',
-                               '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
-                               'Useruser', null, 'infinite', '127.0.8.1',
+                               "[[User:Useruser|\u{202A}Useruser\u{202C}]]", 'no reason given', '127.0.0.1',
+                               "\u{202A}Useruser\u{202C}", null, 'infinite', '127.0.8.1',
                                $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
 
                $this->assertEquals( $errors,
index 1645b85..defa0aa 100644 (file)
@@ -112,7 +112,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
                global $wgDBserver;
 
                // Simulate web request with DBO_TRX
-               $lb = $this->newMultiServerLocalLoadBalancer( DBO_TRX );
+               $lb = $this->newMultiServerLocalLoadBalancer( [], [ 'flags' => DBO_TRX ] );
 
                $this->assertEquals( 8, $lb->getServerCount() );
                $this->assertTrue( $lb->hasReplicaServers() );
@@ -167,12 +167,12 @@ class LoadBalancerTest extends MediaWikiTestCase {
                ] );
        }
 
-       private function newMultiServerLocalLoadBalancer( $flags = DBO_DEFAULT ) {
+       private function newMultiServerLocalLoadBalancer( $lbExtra = [], $srvExtra = [] ) {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
 
                $servers = [
                        // Master DB
-                       0 => [
+                       0 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -181,10 +181,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'type' => $wgDBtype,
                                'dbDirectory' => $wgSQLiteDataDir,
                                'load' => 0,
-                               'flags' => $flags
                        ],
                        // Main replica DBs
-                       1 => [
+                       1 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -193,9 +192,8 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'type' => $wgDBtype,
                                'dbDirectory' => $wgSQLiteDataDir,
                                'load' => 100,
-                               'flags' => $flags
                        ],
-                       2 => [
+                       2 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -204,10 +202,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'type' => $wgDBtype,
                                'dbDirectory' => $wgSQLiteDataDir,
                                'load' => 100,
-                               'flags' => $flags
                        ],
                        // RC replica DBs
-                       3 => [
+                       3 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -220,10 +217,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                        'recentchanges' => 100,
                                        'watchlist' => 100
                                ],
-                               'flags' => $flags
                        ],
                        // Logging replica DBs
-                       4 => [
+                       4 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -235,9 +231,8 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'groupLoads' => [
                                        'logging' => 100
                                ],
-                               'flags' => $flags
                        ],
-                       5 => [
+                       5 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -249,10 +244,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'groupLoads' => [
                                        'logging' => 100
                                ],
-                               'flags' => $flags
                        ],
                        // Maintenance query replica DBs
-                       6 => [
+                       6 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -264,10 +258,9 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'groupLoads' => [
                                        'vslow' => 100
                                ],
-                               'flags' => $flags
                        ],
                        // Replica DB that only has a copy of some static tables
-                       7 => [
+                       7 => $srvExtra + [
                                'host' => $wgDBserver,
                                'dbname' => $wgDBname,
                                'tablePrefix' => $this->dbPrefix(),
@@ -279,12 +272,11 @@ class LoadBalancerTest extends MediaWikiTestCase {
                                'groupLoads' => [
                                        'archive' => 100
                                ],
-                               'flags' => $flags,
                                'is static' => true
                        ]
                ];
 
-               return new LoadBalancer( [
+               return new LoadBalancer( $lbExtra + [
                        'servers' => $servers,
                        'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() ),
                        'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
@@ -585,7 +577,7 @@ class LoadBalancerTest extends MediaWikiTestCase {
        }
 
        public function testQueryGroupIndex() {
-               $lb = $this->newMultiServerLocalLoadBalancer();
+               $lb = $this->newMultiServerLocalLoadBalancer( [ 'defaultGroup' => false ] );
                /** @var LoadBalancer $lbWrapper */
                $lbWrapper = TestingAccessWrapper::newFromObject( $lb );
 
index 88fc93b..37fa030 100644 (file)
@@ -167,7 +167,7 @@ class MediaWikiTestCaseTest extends MediaWikiTestCase {
 
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $lb = $lbFactory->newMainLB();
-               $db = $lb->getConnection( DB_REPLICA, DBO_TRX );
+               $db = $lb->getConnection( DB_REPLICA );
 
                // sanity
                $this->assertNotSame( $this->db, $db );
index e67af10..54f6b4e 100644 (file)
                        '<div>' + loremIpsum + '</div>'
                );
 
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.assertTrue( $collapsible.hasClass( 'mw-collapsible' ), 'mw-collapsible class present' );
        } );
 
                        { collapsed: true }
                );
 
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.assertTrue( $collapsible.hasClass( 'mw-collapsed' ), 'mw-collapsed class present' );
        } );
 
                        .appendTo( '#qunit-fixture' ).makeCollapsible();
 
                $collapsible1.on( 'afterCollapse.mw-collapsible', function () {
+                       // eslint-disable-next-line no-jquery/no-class-state
                        assert.assertTrue( $collapsible1.hasClass( 'mw-collapsed' ), 'after collapsing: parent is collapsed' );
+                       // eslint-disable-next-line no-jquery/no-class-state
                        assert.assertFalse( $collapsible2.hasClass( 'mw-collapsed' ), 'after collapsing: child is not collapsed' );
+                       // eslint-disable-next-line no-jquery/no-class-state
                        assert.assertTrue( $collapsible1.find( '> .mw-collapsible-toggle' ).hasClass( 'mw-collapsible-toggle-collapsed' ) );
+                       // eslint-disable-next-line no-jquery/no-class-state
                        assert.assertFalse( $collapsible2.find( '> .mw-collapsible-toggle' ).hasClass( 'mw-collapsible-toggle-collapsed' ) );
                } ).find( '> .mw-collapsible-toggle a' ).trigger( 'click' );
        } );
index 2711ddf..476b505 100644 (file)
                $table.find( 'tr > th' ).eq( 1 ).trigger( 'click' );
 
                assert.strictEqual(
+                       // eslint-disable-next-line no-jquery/no-class-state
                        $cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ),
                        false,
                        'after sort: no class headerSortUp or headerSortDown'
                        mw.config.set( 'wgPageContentLanguage', 'sv' );
 
                        $table.tablesorter();
+                       // eslint-disable-next-line no-jquery/no-sizzle
                        $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
index aafcd5b..738392a 100644 (file)
                // TODO abstract the double strictEquals
 
                // At first checkboxes are hidden
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
 
                // Initiate the recentchanges module
                rc.init();
 
                // By default
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
 
                // select second option...
@@ -50,7 +54,9 @@
                $( '#namespace' ).trigger( 'change' );
 
                // ... and checkboxes should be visible again
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), false );
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), false );
 
                // select first option ( 'all' namespace)...
@@ -59,7 +65,9 @@
                $( '#namespace' ).trigger( 'change' );
 
                // ... and checkboxes should now be hidden
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsinvert' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $( '#nsassociated' ).closest( '.mw-input-with-label' ).hasClass( 'mw-input-hidden' ), true );
 
                // DOM cleanup
index 6dcdb44..6dab026 100644 (file)
 
                assert.strictEqual( $toggleLink.length, 1, 'Toggle link is added to the table of contents' );
 
+               // eslint-disable-next-line no-jquery/no-class-state
                assert.strictEqual( $toc.hasClass( 'tochidden' ), false, 'The table of contents is now visible' );
 
                $toggleLink.trigger( 'click' );
                return $tocList.promise().then( function () {
+                       // eslint-disable-next-line no-jquery/no-class-state
                        assert.strictEqual( $toc.hasClass( 'tochidden' ), true, 'The table of contents is now hidden' );
                        $toggleLink.trigger( 'click' );
                        return $tocList.promise();
index dd766c8..21d230e 100644 (file)
@@ -11,6 +11,7 @@
                "mw": false
        },
        "rules": {
-               "no-console": 0
+               "no-console": "off",
+               "prefer-template": "off"
        }
 }
index 52d614f..730eff3 100644 (file)
@@ -29,7 +29,7 @@ class HistoryPage extends Page {
        }
 
        vandalizePage( name, content ) {
-               let vandalUsername = 'Evil_' + browser.options.username;
+               const vandalUsername = 'Evil_' + browser.options.username;
 
                browser.call( function () {
                        return Api.edit( name, content );
index 93e0b87..148001d 100644 (file)
@@ -48,7 +48,7 @@ describe( 'Page', function () {
        } );
 
        it( 'should be re-creatable', function () {
-               let initialContent = Util.getTestString( 'initialContent-' );
+               const initialContent = Util.getTestString( 'initialContent-' );
 
                // create
                browser.call( function () {
@@ -75,7 +75,7 @@ describe( 'Page', function () {
                } );
 
                // edit
-               let editContent = Util.getTestString( 'editContent-' );
+               const editContent = Util.getTestString( 'editContent-' );
                EditPage.edit( name, editContent );
 
                // check
index 7947ff5..6b674b9 100644 (file)
@@ -22,7 +22,7 @@ module.exports = {
                password = browser.options.password,
                baseUrl = browser.options.baseUrl
        ) {
-               let bot = new MWBot();
+               const bot = new MWBot();
 
                return bot.loginGetEditToken( {
                        apiUrl: `${baseUrl}/api.php`,
@@ -43,7 +43,7 @@ module.exports = {
         * @return {Object} Promise for API action=delete response data.
         */
        delete( title, reason ) {
-               let bot = new MWBot();
+               const bot = new MWBot();
 
                return bot.loginGetEditToken( {
                        apiUrl: `${browser.options.baseUrl}/api.php`,
@@ -64,7 +64,7 @@ module.exports = {
         * @return {Object} Promise for API action=createaccount response data.
         */
        createAccount( username, password ) {
-               let bot = new MWBot();
+               const bot = new MWBot();
 
                // Log in as admin
                return bot.loginGetCreateaccountToken( {
@@ -94,7 +94,7 @@ module.exports = {
         * @return {Object} Promise for API action=block response data.
         */
        blockUser( username, expiry ) {
-               let bot = new MWBot();
+               const bot = new MWBot();
 
                // Log in as admin
                return bot.loginGetEditToken( {
@@ -122,7 +122,7 @@ module.exports = {
         * @return {Object} Promise for API action=unblock response data.
         */
        unblockUser( username ) {
-               let bot = new MWBot();
+               const bot = new MWBot();
 
                // Log in as admin
                return bot.loginGetEditToken( {
index 070ad56..2e675c0 100644 (file)
@@ -3,7 +3,7 @@ const MWBot = require( 'mwbot' ),
        MAINPAGE_REQUESTS_MAX_RUNS = 10; // (arbitrary) safe-guard against endless execution
 
 function getJobCount() {
-       let bot = new MWBot( {
+       const bot = new MWBot( {
                apiUrl: `${browser.options.baseUrl}/api.php`
        } );
        return bot.request( {
@@ -20,7 +20,7 @@ function log( message ) {
 }
 
 function runThroughMainPageRequests( runCount = 1 ) {
-       let page = new Page();
+       const page = new Page();
        log( `through requests to the main page (run ${runCount}).` );
 
        page.openTitle( '' );
index 56e4934..214c25a 100644 (file)
@@ -160,7 +160,7 @@ exports.config = {
        beforeTest: function ( test ) {
                if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
                        var logBuffer;
-                       let videoPath = filePath( test, logPath, 'mp4' );
+                       const videoPath = filePath( test, logPath, 'mp4' );
                        const { spawn } = require( 'child_process' );
                        ffmpeg = spawn( 'ffmpeg', [
                                '-f', 'x11grab', //  grab the X11 display
@@ -173,7 +173,7 @@ exports.config = {
                        ] );
 
                        logBuffer = function ( buffer, prefix ) {
-                               let lines = buffer.toString().trim().split( '\n' );
+                               const lines = buffer.toString().trim().split( '\n' );
                                lines.forEach( function ( line ) {
                                        console.log( prefix + line );
                                } );
@@ -215,7 +215,7 @@ exports.config = {
                        return;
                }
                // save screenshot
-               let screenshotfile = filePath( test, logPath, 'png' );
+               const screenshotfile = filePath( test, logPath, 'png' );
                browser.saveScreenshot( screenshotfile );
                console.log( '\n\tScreenshot location:', screenshotfile, '\n' );
        }