mediawiki/services/parsoid: main (log #1416764)

sourcepatches

This run took 127 seconds.

$ date
--- stdout ---
Thu Jul  4 16:28:30 UTC 2024

--- end ---
$ git clone file:///srv/git/mediawiki-services-parsoid.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
--- stdout ---

--- end ---
$ git config user.name libraryupgrader
--- stdout ---

--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---

--- end ---
$ git submodule update --init
--- stdout ---

--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.

--- end ---
$ git show-ref refs/heads/master
--- stdout ---
81688d86155d3a0303f354fd78d7968b3da0661f refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {
    "braces": {
      "name": "braces",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1097496,
          "name": "braces",
          "dependency": "braces",
          "title": "Uncontrolled resource consumption in braces",
          "url": "https://github.com/advisories/GHSA-grv7-fg5c-xmjg",
          "severity": "high",
          "cwe": [
            "CWE-1050"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<3.0.3"
        }
      ],
      "effects": [],
      "range": "<3.0.3",
      "nodes": [
        "node_modules/braces"
      ],
      "fixAvailable": true
    },
    "minimatch": {
      "name": "minimatch",
      "severity": "high",
      "isDirect": false,
      "via": [
        {
          "source": 1096485,
          "name": "minimatch",
          "dependency": "minimatch",
          "title": "minimatch ReDoS vulnerability",
          "url": "https://github.com/advisories/GHSA-f8q6-p94x-37v3",
          "severity": "high",
          "cwe": [
            "CWE-400",
            "CWE-1333"
          ],
          "cvss": {
            "score": 7.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
          },
          "range": "<3.0.5"
        }
      ],
      "effects": [
        "mocha"
      ],
      "range": "<3.0.5",
      "nodes": [
        "node_modules/minimatch"
      ],
      "fixAvailable": false
    },
    "mocha": {
      "name": "mocha",
      "severity": "high",
      "isDirect": true,
      "via": [
        "minimatch"
      ],
      "effects": [],
      "range": "5.1.0 - 9.2.1",
      "nodes": [
        "node_modules/mocha"
      ],
      "fixAvailable": false
    },
    "request": {
      "name": "request",
      "severity": "moderate",
      "isDirect": true,
      "via": [
        {
          "source": 1096727,
          "name": "request",
          "dependency": "request",
          "title": "Server-Side Request Forgery in Request",
          "url": "https://github.com/advisories/GHSA-p8p7-x288-28g6",
          "severity": "moderate",
          "cwe": [
            "CWE-918"
          ],
          "cvss": {
            "score": 6.1,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"
          },
          "range": "<=2.88.2"
        },
        "tough-cookie"
      ],
      "effects": [],
      "range": "*",
      "nodes": [
        "node_modules/request"
      ],
      "fixAvailable": false
    },
    "tough-cookie": {
      "name": "tough-cookie",
      "severity": "moderate",
      "isDirect": false,
      "via": [
        {
          "source": 1097682,
          "name": "tough-cookie",
          "dependency": "tough-cookie",
          "title": "tough-cookie Prototype Pollution vulnerability",
          "url": "https://github.com/advisories/GHSA-72xf-g2v4-qvf3",
          "severity": "moderate",
          "cwe": [
            "CWE-1321"
          ],
          "cvss": {
            "score": 6.5,
            "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N"
          },
          "range": "<4.1.3"
        }
      ],
      "effects": [
        "request"
      ],
      "range": "<4.1.3",
      "nodes": [
        "node_modules/tough-cookie"
      ],
      "fixAvailable": false
    }
  },
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 2,
      "high": 3,
      "critical": 0,
      "total": 5
    },
    "dependencies": {
      "prod": 80,
      "dev": 321,
      "optional": 1,
      "peer": 1,
      "peerOptional": 0,
      "total": 400
    }
  }
}

--- end ---
$ /usr/bin/composer install
--- stderr ---
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 86 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.1.4)
  - Locking composer/semver (3.4.0)
  - Locking composer/spdx-licenses (1.5.8)
  - Locking composer/xdebug-handler (3.0.5)
  - Locking dealerdirect/phpcodesniffer-composer-installer (v1.0.0)
  - Locking doctrine/deprecations (1.1.3)
  - Locking doctrine/instantiator (2.0.0)
  - Locking felixfbecker/advanced-json-rpc (v3.2.1)
  - Locking liuggio/statsd-php-client (v1.0.18)
  - Locking mediawiki/mediawiki-codesniffer (v43.0.0)
  - Locking mediawiki/mediawiki-phan-config (0.14.0)
  - Locking mediawiki/minus-x (1.1.1)
  - Locking mediawiki/phan-taint-check-plugin (6.0.0)
  - Locking microsoft/tolerant-php-parser (v0.1.2)
  - Locking monolog/monolog (2.9.3)
  - Locking myclabs/deep-copy (1.12.0)
  - Locking netresearch/jsonmapper (v4.4.1)
  - Locking nikic/php-parser (v4.19.1)
  - Locking ockcyp/covers-validator (v1.6.0)
  - Locking phan/phan (5.4.3)
  - Locking phar-io/manifest (2.0.4)
  - Locking phar-io/version (3.2.1)
  - Locking php-parallel-lint/php-console-color (v1.0.1)
  - Locking php-parallel-lint/php-console-highlighter (v1.0.0)
  - Locking php-parallel-lint/php-parallel-lint (v1.3.2)
  - Locking phpcsstandards/phpcsextra (1.1.2)
  - Locking phpcsstandards/phpcsutils (1.0.9)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.4.1)
  - Locking phpdocumentor/type-resolver (1.8.2)
  - Locking phpstan/phpdoc-parser (1.29.1)
  - Locking phpunit/php-code-coverage (9.2.31)
  - Locking phpunit/php-file-iterator (3.0.6)
  - Locking phpunit/php-invoker (3.1.1)
  - Locking phpunit/php-text-template (2.0.4)
  - Locking phpunit/php-timer (5.0.3)
  - Locking phpunit/phpunit (9.6.16)
  - Locking psr/container (2.0.2)
  - Locking psr/log (1.1.4)
  - Locking sabre/event (5.1.4)
  - Locking sebastian/cli-parser (1.0.2)
  - Locking sebastian/code-unit (1.0.8)
  - Locking sebastian/code-unit-reverse-lookup (2.0.3)
  - Locking sebastian/comparator (4.0.8)
  - Locking sebastian/complexity (2.0.3)
  - Locking sebastian/diff (4.0.6)
  - Locking sebastian/environment (5.1.5)
  - Locking sebastian/exporter (4.0.6)
  - Locking sebastian/global-state (5.0.7)
  - Locking sebastian/lines-of-code (1.0.4)
  - Locking sebastian/object-enumerator (4.0.4)
  - Locking sebastian/object-reflector (2.0.4)
  - Locking sebastian/recursion-context (4.0.5)
  - Locking sebastian/resource-operations (3.0.4)
  - Locking sebastian/type (3.2.1)
  - Locking sebastian/version (3.0.2)
  - Locking squizlabs/php_codesniffer (3.8.1)
  - Locking symfony/console (v5.4.41)
  - Locking symfony/deprecation-contracts (v3.5.0)
  - Locking symfony/polyfill-ctype (v1.30.0)
  - Locking symfony/polyfill-intl-grapheme (v1.30.0)
  - Locking symfony/polyfill-intl-normalizer (v1.30.0)
  - Locking symfony/polyfill-mbstring (v1.30.0)
  - Locking symfony/polyfill-php73 (v1.30.0)
  - Locking symfony/polyfill-php80 (v1.30.0)
  - Locking symfony/polyfill-php81 (v1.30.0)
  - Locking symfony/service-contracts (v3.5.0)
  - Locking symfony/string (v6.4.9)
  - Locking theseer/tokenizer (1.2.3)
  - Locking tysonandre/var_representation_polyfill (0.1.3)
  - Locking webmozart/assert (1.11.0)
  - Locking wikimedia/alea (1.0.0)
  - Locking wikimedia/assert (v0.5.1)
  - Locking wikimedia/base-convert (v2.0.2)
  - Locking wikimedia/bcp-47-code (v2.0.0)
  - Locking wikimedia/idle-dom (v1.0.0)
  - Locking wikimedia/ip-utils (5.0.0)
  - Locking wikimedia/json-codec (v3.0.1)
  - Locking wikimedia/langconv (0.4.2)
  - Locking wikimedia/object-factory (v5.0.1)
  - Locking wikimedia/remex-html (4.1.0)
  - Locking wikimedia/scoped-callback (v4.0.0)
  - Locking wikimedia/testing-access-wrapper (3.0.0)
  - Locking wikimedia/utfnormal (4.0.0)
  - Locking wikimedia/wikipeg (4.0.0)
  - Locking wikimedia/zest-css (3.0.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 86 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Installing squizlabs/php_codesniffer (3.8.1): Extracting archive
  - Installing dealerdirect/phpcodesniffer-composer-installer (v1.0.0): Extracting archive
  - Installing composer/pcre (3.1.4): Extracting archive
  - Installing liuggio/statsd-php-client (v1.0.18): Extracting archive
  - Installing symfony/polyfill-php80 (v1.30.0): Extracting archive
  - Installing phpcsstandards/phpcsutils (1.0.9): Extracting archive
  - Installing phpcsstandards/phpcsextra (1.1.2): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.30.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.8): Extracting archive
  - Installing composer/semver (3.4.0): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v43.0.0): Extracting archive
  - Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.30.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.30.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.30.0): Extracting archive
  - Installing symfony/string (v6.4.9): Extracting archive
  - Installing symfony/deprecation-contracts (v3.5.0): Extracting archive
  - Installing psr/container (2.0.2): Extracting archive
  - Installing symfony/service-contracts (v3.5.0): Extracting archive
  - Installing symfony/polyfill-php73 (v1.30.0): Extracting archive
  - Installing symfony/console (v5.4.41): Extracting archive
  - Installing sabre/event (5.1.4): Extracting archive
  - Installing netresearch/jsonmapper (v4.4.1): Extracting archive
  - Installing microsoft/tolerant-php-parser (v0.1.2): Extracting archive
  - Installing webmozart/assert (1.11.0): Extracting archive
  - Installing phpstan/phpdoc-parser (1.29.1): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (1.1.3): Extracting archive
  - Installing phpdocumentor/type-resolver (1.8.2): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.4.1): Extracting archive
  - Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
  - Installing psr/log (1.1.4): Extracting archive
  - Installing composer/xdebug-handler (3.0.5): Extracting archive
  - Installing phan/phan (5.4.3): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (6.0.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.14.0): Extracting archive
  - Installing mediawiki/minus-x (1.1.1): Extracting archive
  - Installing monolog/monolog (2.9.3): Extracting archive
  - Installing sebastian/version (3.0.2): Extracting archive
  - Installing sebastian/type (3.2.1): Extracting archive
  - Installing sebastian/resource-operations (3.0.4): Extracting archive
  - Installing sebastian/recursion-context (4.0.5): Extracting archive
  - Installing sebastian/object-reflector (2.0.4): Extracting archive
  - Installing sebastian/object-enumerator (4.0.4): Extracting archive
  - Installing sebastian/global-state (5.0.7): Extracting archive
  - Installing sebastian/exporter (4.0.6): Extracting archive
  - Installing sebastian/environment (5.1.5): Extracting archive
  - Installing sebastian/diff (4.0.6): Extracting archive
  - Installing sebastian/comparator (4.0.8): Extracting archive
  - Installing sebastian/code-unit (1.0.8): Extracting archive
  - Installing sebastian/cli-parser (1.0.2): Extracting archive
  - Installing phpunit/php-timer (5.0.3): Extracting archive
  - Installing phpunit/php-text-template (2.0.4): Extracting archive
  - Installing phpunit/php-invoker (3.1.1): Extracting archive
  - Installing phpunit/php-file-iterator (3.0.6): Extracting archive
  - Installing theseer/tokenizer (1.2.3): Extracting archive
  - Installing nikic/php-parser (v4.19.1): Extracting archive
  - Installing sebastian/lines-of-code (1.0.4): Extracting archive
  - Installing sebastian/complexity (2.0.3): Extracting archive
  - Installing sebastian/code-unit-reverse-lookup (2.0.3): Extracting archive
  - Installing phpunit/php-code-coverage (9.2.31): Extracting archive
  - Installing phar-io/version (3.2.1): Extracting archive
  - Installing phar-io/manifest (2.0.4): Extracting archive
  - Installing myclabs/deep-copy (1.12.0): Extracting archive
  - Installing doctrine/instantiator (2.0.0): Extracting archive
  - Installing phpunit/phpunit (9.6.16): Extracting archive
  - Installing ockcyp/covers-validator (v1.6.0): Extracting archive
  - Installing php-parallel-lint/php-console-color (v1.0.1): Extracting archive
  - Installing php-parallel-lint/php-console-highlighter (v1.0.0): Extracting archive
  - Installing php-parallel-lint/php-parallel-lint (v1.3.2): Extracting archive
  - Installing wikimedia/alea (1.0.0): Extracting archive
  - Installing wikimedia/bcp-47-code (v2.0.0): Extracting archive
  - Installing wikimedia/idle-dom (v1.0.0): Extracting archive
  - Installing wikimedia/base-convert (v2.0.2): Extracting archive
  - Installing wikimedia/ip-utils (5.0.0): Extracting archive
  - Installing symfony/polyfill-php81 (v1.30.0): Extracting archive
  - Installing wikimedia/json-codec (v3.0.1): Extracting archive
  - Installing wikimedia/assert (v0.5.1): Extracting archive
  - Installing wikimedia/langconv (0.4.2): Extracting archive
  - Installing wikimedia/object-factory (v5.0.1): Extracting archive
  - Installing wikimedia/utfnormal (4.0.0): Extracting archive
  - Installing wikimedia/remex-html (4.1.0): Extracting archive
  - Installing wikimedia/scoped-callback (v4.0.0): Extracting archive
  - Installing wikimedia/testing-access-wrapper (3.0.0): Extracting archive
  - Installing wikimedia/wikipeg (4.0.0): Extracting archive
  - Installing wikimedia/zest-css (3.0.1): Extracting archive
  0/84 [>---------------------------]   0%
 20/84 [======>---------------------]  23%
 30/84 [==========>-----------------]  35%
 39/84 [=============>--------------]  46%
 54/84 [==================>---------]  64%
 65/84 [=====================>------]  77%
 74/84 [========================>---]  88%
 83/84 [===========================>]  98%
 84/84 [============================] 100%
17 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating optimized autoload files
44 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---
PHP CodeSniffer Config installed_paths set to ../../mediawiki/mediawiki-codesniffer,../../phpcsstandards/phpcsextra,../../phpcsstandards/phpcsutils

--- end ---
Upgrading n:eslint-config-wikimedia from 0.27.0 -> 0.28.2
$ /usr/bin/npm install
--- stderr ---
npm WARN skipping integrity check for git dependency ssh://git@github.com/arlolra/mocha.git 
npm WARN deprecated har-validator@5.1.3: this library is no longer supported
npm WARN deprecated formidable@1.2.2: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated superagent@5.1.0: Please upgrade to v7.0.2+ of superagent.  We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing.  See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>.
npm WARN deprecated core-js@2.6.11: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
--- stdout ---

added 408 packages, and audited 409 packages in 10s

85 packages are looking for funding
  run `npm fund` for details

4 vulnerabilities (2 moderate, 2 high)

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json

--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---

/src/repo/bin/benchmark.js
   84:4   warning  Don't use process.exit(); throw an error instead                            n/no-process-exit
  132:9   warning  Found existsSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  132:35  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  190:4   warning  Found writeFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  252:32  warning  Found existsSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  269:26  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  285:15  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  353:4   warning  Don't use process.exit(); throw an error instead                            n/no-process-exit

/src/repo/bin/diff.html.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                     es-x/no-hashbang
   34:22  warning  Unsafe Regular Expression                                                  security/detect-unsafe-regex
  150:16  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  151:17  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  177:27  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/bin/domdiff.test.js
   1:1   warning  ES2023 Hashbang comments are forbidden                             es-x/no-hashbang
   7:23  warning  Can't resolve '../lib/html2wt/DOMDiff.js' in '/src/repo/bin'       n/no-missing-require
  10:29  warning  Can't resolve '../lib/logger/ParsoidLogger.js' in '/src/repo/bin'  n/no-missing-require
  88:2   warning  Don't use process.exit(); throw an error instead                   n/no-process-exit

/src/repo/bin/inspectTokenizer.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                          es-x/no-hashbang
   70:10  warning  Found createWriteStream from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
   77:22  warning  Found readFileSync from package "fs" with non literal argument at index 0       security/detect-non-literal-fs-filename
  218:2   warning  Don't use process.exit(); throw an error instead                                n/no-process-exit

/src/repo/bin/langconv-test.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                   es-x/no-hashbang
   12:51  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'               n/no-missing-require
   17:41  warning  Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/bin'  n/no-missing-require
   20:37  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'               n/no-missing-require
  405:5   warning  Found non-literal argument in require                                    security/detect-non-literal-require
  625:4   warning  Don't use process.exit(); throw an error instead                         n/no-process-exit

/src/repo/bin/normalize.test.js
   1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  64:2  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/bin/roundtrip-test.js
    1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  996:4  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/lib/config/ParsoidConfig.js
  157:19  warning  Found non-literal argument in require                                     security/detect-non-literal-require
  630:8   warning  Found statSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  636:14  warning  Found readdirSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  643:9   warning  Found statSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  656:12  warning  Found non-literal argument in require                                     security/detect-non-literal-require

/src/repo/lib/html2wt/DOMNormalizer.js
  284:17  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  372:44  warning  Unsafe Regular Expression                         security/detect-unsafe-regex

/src/repo/lib/html2wt/WTSUtils.js
  13:20  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/Diff.js
  142:24  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/TokenUtils.js
  26:10  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/Util.js
  159:10  warning  Unsafe Regular Expression  security/detect-unsafe-regex
  456:25  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/WTUtils.js
  137:37  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/jsutils.js
  280:10  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tests/TestUtils.js
  150:12  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/tests/api-testing/Parsoid.js
  668:26  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  680:26  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tests/testreduce/rtTestWrapper.js
  13:9  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/ScriptUtils.js
  57:4  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/tools/build-langconv-fst.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                 es-x/no-hashbang
   84:21  warning  Can't resolve '../lib/language/FST.js' in '/src/repo/tools'            n/no-missing-require
  109:13  warning  Found openSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  302:14  warning  Found openSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/compare.linter.results.js
    1:1   warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  105:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit
  108:18  warning  Found non-literal argument in require             security/detect-non-literal-require
  112:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit
  115:18  warning  Found non-literal argument in require             security/detect-non-literal-require
  118:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/tools/fetch-parserTests.js
  1:1  warning  ES2023 Hashbang comments are forbidden  es-x/no-hashbang

/src/repo/tools/fetch-revision-data.js
   1:1   warning  ES2023 Hashbang comments are forbidden                                     es-x/no-hashbang
  20:31  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/tools'               n/no-missing-require
  22:35  warning  Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/tools'  n/no-missing-require

/src/repo/tools/fetch_stray_toc_edits.js
  138:13  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tools/gen_visualdiff_titles.js
  12:2  warning  Don't use process.exit(); throw an error instead                           n/no-process-exit
  31:3  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/runRtTests.js
  1:1  warning  ES2023 Hashbang comments are forbidden  es-x/no-hashbang

/src/repo/tools/sync-parserTests.js
    1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  261:2  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

✖ 70 problems (0 errors, 70 warnings)


--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/arwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/banwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/be-taraskwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/cawiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/cswiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/dewiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/enwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/eswiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/fawiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/fiwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/frwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/iswiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/kaawiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/lnwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/nlwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/ruwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/srwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/trwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/baseconfig/zhwiki.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/benchmark.js","messages":[{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":84,"column":4,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":84,"endColumn":20},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found existsSync from package \"fs\" with non literal argument at index 0","line":132,"column":9,"nodeType":"CallExpression","endLine":132,"endColumn":32},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":132,"column":35,"nodeType":"CallExpression","endLine":132,"endColumn":68},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found writeFileSync from package \"fs\" with non literal argument at index 0","line":190,"column":4,"nodeType":"CallExpression","endLine":190,"endColumn":50},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found existsSync from package \"fs\" with non literal argument at index 0","line":252,"column":32,"nodeType":"CallExpression","endLine":252,"endColumn":67},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":269,"column":26,"nodeType":"CallExpression","endLine":269,"endColumn":66},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":285,"column":15,"nodeType":"CallExpression","endLine":285,"endColumn":62},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":353,"column":4,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":353,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":8,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst fs = require('fs');\nconst yaml = require('js-yaml');\nrequire('../core-upgrade.js');\nconst Promise = require('../lib/utils/promise.js');\nconst request = Promise.promisify(require('request'), true);\n\n// Some semi-arbitrary list of titles\nconst sampleTitles = [\n\t{ wiki: 'enwiki', title: 'Main_Page', revid: 917272779 },\n\t{ wiki: 'enwiki', title: 'Skating', revid: 921619251 },\n\t{ wiki: 'enwiki', title: 'Hospet', revid: 913341503 },\n\t{ wiki: 'enwiki', title: 'Hampi', revid: 921528573 },\n\t{ wiki: 'enwiki', title: 'Berlin', revid: 921687210 },\n\t{ wiki: 'enwiki', title: 'Barack_Obama', revid: 921752860 },\n\t{ wiki: 'enwiki', title: 'Max_Planck_Institute_for_Physics', revid: 921775647 },\n\t{ wiki: 'enwiki', title: 'Architects & Engineers for 9/11 Truth', revid: 921775875 },\n\t{ wiki: 'itwiki', title: 'Luna', revid: 108284424 },\n\t{ wiki: 'itwiki', title: 'Metro', revid: 108262882 },\n\t{ wiki: 'frwiki', title: 'Mulholland_Drive', revid: 149562710 },\n\t{ wiki: 'frwiki', title: 'Metro', revid: 108262882 },\n\t{ wiki: 'frwiki', title: 'François_de_La_Tour_du_Pin', revid: 163623032 },\n\t{ wiki: 'frwiki', title: 'Jason_Bateman', revid: 163623075 },\n\t{ wiki: 'jawiki', title: '人類学', revid: 74657621 },\n\t{ wiki: 'jawiki', title: 'パレオ・インディアン', revid: 70817191 },\n\t{ wiki: 'mediawiki', title: 'Parsoid', revid: 3453996 },\n\t{ wiki: 'mediawiki', title: 'RESTBase', revid: 2962542 },\n\t{ wiki: 'mediawiki', title: 'VisualEditor', revid: 3408339 },\n\t{ wiki: 'dewikivoyage', title: 'Bengaluru', revid: 1224432 },\n\t{ wiki: 'dewikivoyage', title: 'Kopenhagen', revid: 1240570 },\n\t{ wiki: 'dewikivoyage', title: 'Stuttgart', revid: 1226146 },\n\t{ wiki: 'hiwiktionary', title: 'परिवर्णी', revid: 467616 },\n\t{ wiki: 'hiwiktionary', title: 'चीन', revid: 456648 },\n\t{ wiki: 'knwikisource', title: 'ಪಂಪಭಾರತ_ಪ್ರಥಮಾಶ್ವಾಸಂ', revid: 170413 },\n];\n\nlet outerConfig = {\n\t// File with \\n-separated json blobs with at least (wiki, title, oldId / revid) properties\n\t// If domain is provided, it is used, if not wiki is treated as a prefix\n\t// All other properties are ignored.\n\t// If this property is null, sampleTitles above is used\n\ttestTitles: null, // '/tmp/logs',\n\tmode: 'wt2html',\n\tjsServer: {\n\t\tbaseURI: 'http://localhost:8142',\n\t\tproxy: '',\n\t},\n\tphpServer: {\n\t\tbaseURI: 'http://DOMAIN/w/rest.php',\n\t\tproxy: '', // 'http://scandium.eqiad.wmnet:80',\n\t},\n\tmaxOutstanding: 4,\n\tmaxRequests: 25,\n\tverbose: true\n};\n\nconst state = {\n\ttimes: [],\n\tnumPendingRequests: 0,\n\toutStanding: 0\n};\n\nfunction genFullUrls(config, domain, title, revid) {\n\tlet initRestFragment, restFragment;\n\n\tswitch (config.mode || 'wt2html') {\n\t\tcase 'wt2html':\n\t\t\trestFragment = `${ domain }/v3/page/html/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\tbreak;\n\t\tcase 'wt2pb':\n\t\t\trestFragment = `${ domain }/v3/page/pagebundle/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\tbreak;\n\t\tcase 'html2wt':\n\t\t\tinitRestFragment = `${ domain }/v3/page/html/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\trestFragment = `${ domain }/v3/transform/html/to/wikitext/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\tbreak;\n\t\tcase 'pb2wt':\n\t\t\tinitRestFragment = `${ domain }/v3/page/pagebundle/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\trestFragment = `${ domain }/v3/transform/pagebundle/to/wikitext/${ encodeURIComponent(title) }/${ revid }`;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tconsole.log(\"Mode \" + config.mode + \" is not supported right now.\");\n\t\t\tprocess.exit(-1);\n\t}\n\treturn {\n\t\tjs : `${ config.jsServer.baseURI }/${ restFragment }`,\n\t\tphp: `${ config.phpServer.baseURI.replace(/DOMAIN/, domain) }/${ restFragment }`,\n\t\tinit: initRestFragment ? `${ config.phpServer.baseURI.replace(/DOMAIN/, domain) }/${ initRestFragment }` : null,\n\t\tjsTime: null,\n\t\tphpTime: null,\n\t};\n}\n\nfunction prefixToDomain(prefix) {\n\tif (prefix === 'commonswiki') {\n\t\treturn 'commons.wikimedia.org';\n\t}\n\n\tif (prefix === 'metawiki') {\n\t\treturn 'meta.wikimedia.org';\n\t}\n\n\tif (prefix === 'wikidatawiki') {\n\t\treturn 'wikidata.org';\n\t}\n\n\tif (prefix === 'mediawiki' || prefix === 'mediawikiwiki') {\n\t\treturn 'www.mediawiki.org';\n\t}\n\n\tif (/wiki$/.test(prefix)) {\n\t\treturn prefix.replace(/wiki$/, '.wikipedia.org');\n\t}\n\n\tconst project = [ 'wiktionary', 'wikisource', 'wikivoyage', 'wikibooks', 'wikiquote', 'wikinews', 'wikiversity' ].find(function(p) {\n\t\treturn prefix.endsWith(p);\n\t});\n\n\treturn project ? `${ prefix.slice(0, Math.max(0, prefix.length - project.length)) }.${ project }.org` : null;\n}\n\nfunction contentFileName(url) {\n\t// Hacky\n\tconst suffix = /.*v3\\/(page|transform)\\/pagebundle/.test(url) ? 'pb.json' : 'html';\n\tconst wiki = url.replace(/\\/v3\\/.*/, '').replace(/.*\\//, '');\n\treturn '/tmp/' + wiki + \".\" + url.replace(/.*\\//, '') + \".php.\" + suffix;\n}\n\nfunction fetchPageContent(url) {\n\tconst fileName = contentFileName(url);\n\treturn fs.existsSync(fileName) ? fs.readFileSync(fileName, 'utf8') : null;\n}\n\nfunction issueRequest(opts, url, finalizer) {\n\tconst config = opts.config;\n\tconst fromWT = opts.mode === 'wt2html' || opts.mode === 'wt2pb';\n\tconst httpOptions = {\n\t\tmethod: fromWT ? 'GET' : 'POST',\n\t\theaders: { 'User-Agent': 'Parsoid-Test' },\n\t\tproxy: opts.proxy,\n\t\turi: fromWT ? url : url.replace(/\\/\\d+$/, ''), // strip oldid to suppress selser\n\t};\n\n\tif (!fromWT) {\n\t\thttpOptions.headers['Content-Type'] = 'application/json';\n\t\tconst content = fetchPageContent(url);\n\t\tif (!content) {\n\t\t\tconsole.log(\"Aborting request! Content not found @ \" + contentFileName(url));\n\t\t\t// Abort\n\t\t\tstate.numPendingRequests--;\n\t\t\tif (state.numPendingRequests === 0 && state.outStanding === 0) {\n\t\t\t\tconsole.log('resolving after abort');\n\t\t\t\tfinalizer();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (opts.mode === 'pb2wt') {\n\t\t\tconst pb = JSON.parse(content);\n\t\t\thttpOptions.body = {\n\t\t\t\thtml: pb.html.body,\n\t\t\t\toriginal : {\n\t\t\t\t\t'data-parsoid': pb['data-parsoid']\n\t\t\t\t\t// non-selser mode, so don't need wikitext\n\t\t\t\t},\n\t\t\t};\n\t\t} else  {\n\t\t\thttpOptions.body = {\n\t\t\t\t'html': content\n\t\t\t};\n\t\t}\n\t\thttpOptions.body = JSON.stringify(httpOptions.body);\n\t}\n\n\tconst reqId = state.numPendingRequests;\n\tif (config.verbose) {\n\t\tconsole.log(`--> ID=${ reqId }; URL:${ url }; PENDING=${ state.numPendingRequests }; OUTSTANDING=${ state.outStanding }`);\n\t}\n\tstate.numPendingRequests--;\n\tstate.outStanding++;\n\tconst startTime = process.hrtime();\n\treturn request(httpOptions)\n\t.catch(function(error) {\n\t\tconsole.log(\"errrorr!\" + error);\n\t})\n\t.then(function(ret) {\n\t\tstate.outStanding--;\n\t\tif (opts.type === 'init') {\n\t\t\tfs.writeFileSync(contentFileName(url), ret[1]);\n\t\t\tif (state.numPendingRequests === 0 && state.outStanding === 0) {\n\t\t\t\tfinalizer();\n\t\t\t}\n\t\t} else {\n\t\t\tconst endTime = process.hrtime();\n\t\t\tconst reqTime = Math.round((endTime[0] * 1e9 + endTime[1]) / 1e6 - (startTime[0] * 1e9 + startTime[1]) / 1e6);\n\t\t\tif (config.verbose) {\n\t\t\t\tconsole.log(`<-- ID=${ reqId }; URL:${ url }; TIME=${ reqTime }; STATUS: ${ ret[0].statusCode }; LEN: ${ ret[1].length }`);\n\t\t\t}\n\t\t\tif (!opts.results[reqId]) {\n\t\t\t\topts.results[reqId] = {\n\t\t\t\t\turl: url,\n\t\t\t\t};\n\t\t\t}\n\t\t\topts.results[reqId][opts.type + 'Time'] = reqTime;\n\t\t\tstate.times.push(reqTime);\n\t\t\tif (state.numPendingRequests === 0 && state.outStanding === 0) {\n\t\t\t\tconst res = state.times.reduce((stats, n) => {\n\t\t\t\t\tstats.sum += n;\n\t\t\t\t\tstats.min = n < stats.min ? n : stats.min;\n\t\t\t\t\tstats.max = n > stats.max ? n : stats.max;\n\t\t\t\t\treturn stats;\n\t\t\t\t}, { sum: 0, min: 1000000, max: 0 });\n\t\t\t\tres.avg = res.sum / state.times.length;\n\t\t\t\tres.median = state.times.sort((a, b) => a - b)[Math.floor(state.times.length / 2)];\n\t\t\t\tconsole.log(`\\n${ opts.type.toUpperCase() } STATS: ${ JSON.stringify(res) }`);\n\t\t\t\tfinalizer();\n\t\t\t}\n\t\t}\n\t})\n\t.catch(function(error) {\n\t\tconsole.log(\"errrorr!\" + error);\n\t});\n}\n\nfunction computeRandomRequestStream(testUrls, config) {\n\tconst numReqs = config.maxRequests;\n\tconst reqs = [];\n\tconst n = testUrls.length;\n\tfor (let i = 0; i < numReqs; i++) {\n\t\t// Pick a random url\n\t\treqs.push(testUrls[Math.floor(Math.random() * n)]);\n\t}\n\treturn reqs;\n}\n\nfunction reset(config) {\n\tstate.times = [];\n\tstate.numPendingRequests = config.maxRequests;\n\tstate.outStanding = 0; // # outstanding reqs\n}\n\nfunction runTests(opts, finalizer) {\n\tif (state.numPendingRequests > 0) {\n\t\tif (state.outStanding < opts.config.maxOutstanding) {\n\t\t\tconst url = opts.reqs[opts.reqs.length - state.numPendingRequests][opts.type];\n\t\t\tif (opts.type === 'js') {\n\t\t\t\topts.proxy = opts.config.jsServer.proxy || '';\n\t\t\t} else { // 'php' or 'init' For init, content is always fetched from Parsoid/PHP\n\t\t\t\topts.proxy = opts.config.phpServer.proxy || '';\n\t\t\t}\n\t\t\tif (opts.type === 'init' && fs.existsSync(contentFileName(url))) {\n\t\t\t\t// Content exists. Don't fetch.\n\t\t\t\tstate.numPendingRequests--;\n\t\t\t\tif (state.numPendingRequests === 0 && state.outStanding === 0) {\n\t\t\t\t\tfinalizer();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tissueRequest(opts, url, finalizer);\n\t\t\t}\n\t\t}\n\t\tsetImmediate(() => runTests(opts, finalizer));\n\t}\n}\n\n// Override default config\nif (process.argv.length > 2) {\n\touterConfig = yaml.load(fs.readFileSync(process.argv[2], 'utf8'));\n}\n\n// CLI overrides config\nif (process.argv.length > 3) {\n\touterConfig.maxOutstanding = parseInt(process.argv[3], 10);\n}\n\n// CLI overrides config\nif (process.argv.length > 4) {\n\touterConfig.maxRequests = parseInt(process.argv[4], 10);\n}\n\nlet testUrls;\nif (outerConfig.testTitles) {\n\t// Parse production logs and generate test urls\n\tconst logs = fs.readFileSync(outerConfig.testTitles, 'utf8');\n\tconst lines = logs.split(/\\n/);\n\ttestUrls = [];\n\tlines.forEach(function(l) {\n\t\tif (l) {\n\t\t\tconst log = JSON.parse(l);\n\t\t\tconst domain = log.domain || prefixToDomain(log.wiki);\n\t\t\tif (domain) {\n\t\t\t\ttestUrls.push(genFullUrls(outerConfig, domain, log.title, log.oldId || log.revid));\n\t\t\t}\n\t\t}\n\t});\n} else {\n\ttestUrls = [];\n\tsampleTitles.forEach(function(t) {\n\t\ttestUrls.push(genFullUrls(outerConfig, t.domain || prefixToDomain(t.wiki), t.title, t.revid));\n\t});\n}\n\nconst reqStream = computeRandomRequestStream(testUrls, outerConfig);\nconst opts = {\n\tconfig: outerConfig,\n\treqs: reqStream,\n\tresults: [],\n};\n\nlet p;\nif (/2wt$/.test(outerConfig.mode)) {\n\t// Fetch pb / html as necessary and save to disk\n\t// so we can run and benchmark pb2wt or html2wt after\n\tp = new Promise(function(resolve, reject) {\n\t\topts.type = 'init';\n\t\topts.mode = outerConfig.mode === 'pb2wt' ? 'wt2pb' : 'wt2html';\n\t\tconsole.log(\"--- Initialization ---\");\n\t\treset(outerConfig);\n\t\trunTests(opts, function() {\n\t\t\tconsole.log(\"--- Initialization done---\");\n\t\t\tresolve();\n\t\t});\n\t});\n} else {\n\tp = Promise.resolve();\n}\n\np.then(function() {\n\treset(outerConfig);\n\topts.type = 'js';\n\topts.mode = outerConfig.mode;\n\tconsole.log(\"\\n\\n--- JS tests ---\");\n\trunTests(opts, function() {\n\t\tconsole.log(\"\\n\\n--- PHP tests---\");\n\t\treset(outerConfig);\n\t\topts.type = 'php';\n\t\topts.mode = outerConfig.mode;\n\t\trunTests(opts, function() {\n\t\t\tconsole.log(\"\\n--- All done---\\n\");\n\t\t\tlet numJSFaster = 0;\n\t\t\tlet numPHPFaster = 0;\n\t\t\topts.results.forEach(function(r) {\n\t\t\t\tif (r.jsTime < r.phpTime) {\n\t\t\t\t\tnumJSFaster++;\n\t\t\t\t\tconsole.log(`For ${ r.url }, Parsoid/JS was faster than Parsoid/PHP (${ r.jsTime } vs. ${ r.phpTime })`);\n\t\t\t\t} else {\n\t\t\t\t\tnumPHPFaster++;\n\t\t\t\t}\n\t\t\t});\n\t\t\tconsole.log('\\n# of reqs where Parsoid/JS was faster than Parsoid/PHP: ' + numJSFaster);\n\t\t\tconsole.log('# of reqs where Parsoid/PHP was faster than Parsoid/JS: ' + numPHPFaster);\n\t\t\tprocess.exit(0);\n\t\t});\n\t});\n}).done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/diff.html.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":34,"column":22,"nodeType":"Literal","endLine":34,"endColumn":72},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":150,"column":16,"nodeType":"CallExpression","endLine":150,"endColumn":51},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":151,"column":17,"nodeType":"CallExpression","endLine":151,"endColumn":53},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":177,"column":27,"nodeType":"CallExpression","endLine":177,"endColumn":67}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('colors');\nconst yaml = require('js-yaml');\nconst fs = require('fs');\nconst Promise = require('../lib/utils/promise.js');\nconst { Diff } = require('../lib/utils/Diff.js');\nconst { DOMUtils } = require('../lib/utils/DOMUtils.js');\nconst { ScriptUtils } = require('../tools/ScriptUtils.js');\nconst XMLSerializer = require('../lib/wt2html/XMLSerializer.js');\n\nlet jsServer = { baseURI: 'http://localhost:8142', proxy: '' };\nlet phpServer = { baseURI: 'http://localhost/rest.php', proxy: '' };\n\nfunction normalizeHTML(html, isPHP) {\n\t// Normalize about ids\n\thtml = html.replace(/#mwt[0-9]*/gm, '#mwtX');\n\n\t// Normalized unexpanded DOM fragments (T235656)\n\thtml = html.replace(/mwf[0-9]*/gm, 'mwfX');\n\n\t// Remove stray nowiki strip tags left behind by extensions\n\thtml = html.replace(/UNIQ--nowiki-.*?QINU/gm, '');\n\n\t// JS output has bad maplinks (on wikivoyages). Strip out maplink\n\t// wrapper in both JS & PHP HTML\n\thtml = html.replace(/<div class=\"magnify\" title=\"Enlarge map\">.*?<\\/div>/gm, '');\n\n\t// JS & PHP introduce differing # of significant digits after the decimal\n\t// In any case, fractional pixels don't make sense, so simply strip those out.\n\t// (T229594 -- Neither PHP nor JS should emit fractional dims here)\n\thtml = html.replace(/((?:width|height):\\s*[0-9]*)(?:\\.[0-9]*)?(px)?/gm, '$1$2');\n\n\t// Normalize minor variations in dsr (T231570 is one source)\n\t// ,null as well as ,null,null (Ex: jawiki:J-WAVE)\n\thtml = html.replace(/,null,null/gm, '');\n\thtml = html.replace(/,null/gm, ',0');\n\thtml = html.replace(/,0,0/gm, '');\n\n\t// Parse to DOM for additional normalization easier done\n\t// on the DOM vs a HTML string.\n\tconst body = DOMUtils.parseHTML(html).body;\n\t// data-parsoid:\n\t// - JS & PHP seem to be inserting some keys in different order\n\t//   (Ex: jawiki:ICalendar)\n\t//   Sort keys\n\tDOMUtils.visitDOM(body, function(node) {\n\t\tif (DOMUtils.isElt(node)) {\n\t\t\tif (node.hasAttribute('data-parsoid')) {\n\t\t\t\tconst dpStr = node.getAttribute('data-parsoid');\n\t\t\t\tconst dp = JSON.parse(dpStr);\n\t\t\t\tconst dp2 = {};\n\t\t\t\tObject.keys(dp).sort().forEach(function(k) {\n\t\t\t\t\tdp2[k] = dp[k];\n\t\t\t\t});\n\t\t\t\tnode.setAttribute('data-parsoid', JSON.stringify(dp2));\n\t\t\t}\n\t\t}\n\t});\n\n\t// Serialize back the parsed DOM - use XMLSerializer for smart quoting.\n\t// Return body inner HTML (normalize diffs in <head> and <body> attributes)\n\t// This is a lazy normalization. We can do more finer-grained normalization\n\t// and include the <head> tag once we narrow down diffs elsewhere.\n\thtml = XMLSerializer.serialize(body, { innerXML: true, smartQuote: true }).html;\n\n\t// Add copious new lines to generate finer-grained diffs\n\treturn html.replace(/</gm, '\\n<');\n}\n\nconst fetchHTML = Promise.async(function *(server, proxy, domain, title, isPHP) {\n\tconst httpOptions = {\n\t\tmethod: 'GET',\n\t\theaders: { 'User-Agent': 'Parsoid-Test' },\n\t\tproxy: proxy,\n\t\turi: server.replace(/\\/$/, '') + '/' + domain + '/v3/page/html/' + encodeURIComponent(title),\n\t};\n\n\tif (isPHP) {\n\t\t// Append a trailing / to workaround T232556\n\t\t// Request ucs2 offsets to ensure dsr offsets line up\n\t\thttpOptions.uri += '/?offsetType=ucs2';\n\t}\n\n\tconst result = yield ScriptUtils.retryingHTTPRequest(2, httpOptions);\n\treturn normalizeHTML(result[1], isPHP);\n});\n\nfunction genLineLengths(str) {\n\treturn str.split(/^/m).map(function(l) {\n\t\treturn l.length;\n\t});\n}\n\nconst fetchAllHTML = Promise.async(function *(domain, title) {\n\tconst jsHTML = yield fetchHTML(jsServer.baseURI, '', domain, title);\n\tconst phpHTML = yield fetchHTML(phpServer.baseURI.replace(/DOMAIN/, domain), phpServer.proxy, domain, title, true);\n\treturn {\n\t\tjs: {\n\t\t\thtml: jsHTML,\n\t\t\tlineLens: genLineLengths(jsHTML),\n\t\t},\n\t\tphp: {\n\t\t\thtml: phpHTML,\n\t\t\tlineLens: genLineLengths(phpHTML),\n\t\t}\n\t};\n});\n\n// Get diff slices from offsets\nfunction formatDiff(str1, str2, offset, context) {\n\treturn [\n\t\t`----- JS:[${ offset[0].start }, ${ offset[0].end }] -----`,\n\t\tstr1.slice(offset[0].start - context, offset[0].start).blue +\n\t\tstr1.slice(offset[0].start, offset[0].end).green +\n\t\tstr1.slice(offset[0].end, offset[0].end + context).blue,\n\t\t`+++++ PHP:[${ offset[1].start }, ${ offset[1].end }] +++++`,\n\t\tstr2.slice(offset[1].start - context, offset[1].start).blue +\n\t\tstr2.slice(offset[1].start, offset[1].end).red +\n\t\tstr2.slice(offset[1].end, offset[1].end + context).blue,\n\t].join('\\n');\n}\n\nfunction afterFetch(res) {\n\tconst diff = Diff.diffLines(res.js.html, res.php.html);\n\tconst offsets = Diff.convertDiffToOffsetPairs(diff, res.js.lineLens, res.php.lineLens);\n\tconst lineDiffs = [];\n\tif (offsets.length > 0) {\n\t\tfor (let i = 0; i < offsets.length; i++) {\n\t\t\tlineDiffs.push(formatDiff(res.js.html, res.php.html, offsets[i], 0));\n\t\t}\n\t}\n\treturn lineDiffs;\n}\n\nfunction htmlDiff(config, domain, title) {\n\tjsServer = config.jsServer || jsServer;\n\tphpServer = config.phpServer || phpServer;\n\n\treturn fetchAllHTML(domain, title)\n\t.then(afterFetch)\n\t.catch(function(e) {\n\t\tconsole.error(e);\n\t});\n}\n\nfunction fileDiff(jsFilename, phpFilename) {\n\tconst jsOut = fs.readFileSync(jsFilename, 'utf8');\n\tconst phpOut = fs.readFileSync(phpFilename, 'utf8');\n\tconst out = {\n\t\tjs: {\n\t\t\thtml: normalizeHTML(jsOut, false),\n\t\t},\n\t\tphp: {\n\t\t\thtml: normalizeHTML(phpOut, true),\n\t\t}\n\t};\n\tout.js.lineLens = genLineLengths(out.js.html);\n\tout.php.lineLens = genLineLengths(out.php.html);\n\treturn afterFetch(out);\n}\n\nfunction displayResult(diffs, domain, title) {\n\tdomain = domain || '<unknown>';\n\ttitle = title || '<unknown>';\n\tif (diffs.length === 0) {\n\t\tconsole.log(`${ domain }:${ title }: NO HTML DIFFS FOUND!`);\n\t} else {\n\t\tconsole.log(`Parsoid/JS vs. Parsoid/PHP HTML diffs for ${ domain }:${ title }`);\n\t\tconsole.log(diffs.join('\\n'));\n\t}\n}\n\nif (require.main === module) {\n\tconst config = yaml.load(fs.readFileSync(process.argv[2], 'utf8'));\n\tconst domain = process.argv[3];\n\tconst title = process.argv[4];\n\thtmlDiff(config, domain, title).then(function(diffs) {\n\t\tdisplayResult(diffs, domain, title);\n\t}).done();\n} else if (typeof module === \"object\") {\n\tmodule.exports.htmlDiff = htmlDiff;\n\tmodule.exports.fileDiff = fileDiff;\n\tmodule.exports.displayResult = displayResult;\n\tmodule.exports.fetchHTML = fetchHTML;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/domdiff.test.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/html2wt/DOMDiff.js' in '/src/repo/bin'","line":7,"column":23,"nodeType":"Literal","messageId":"notFound","endLine":7,"endColumn":50},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/logger/ParsoidLogger.js' in '/src/repo/bin'","line":10,"column":29,"nodeType":"Literal","messageId":"notFound","endLine":10,"endColumn":61},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":88,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":88,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\nvar DOMDiff = require('../lib/html2wt/DOMDiff.js').DOMDiff;\nvar ScriptUtils = require('../tools/ScriptUtils.js').ScriptUtils;\nvar ContentUtils = require('../lib/utils/ContentUtils.js').ContentUtils;\nvar ParsoidLogger = require('../lib/logger/ParsoidLogger.js').ParsoidLogger;\nvar MockEnv = require('../tests/MockEnv.js').MockEnv;\nvar Promise = require('../lib/utils/promise.js');\nvar yargs = require('yargs');\nvar fs = require('pn/fs');\n\nvar opts = yargs\n.usage(\"Usage: $0 [options] [old-html-file new-html-file]\\n\\nProvide either inline html OR 2 files\")\n.options({\n\thelp: {\n\t\tdescription: 'Show this message',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\toldhtml: {\n\t\tdescription: 'Old html',\n\t\t'boolean': false,\n\t\t'default': null,\n\t},\n\tnewhtml: {\n\t\tdescription: 'New html',\n\t\t'boolean': false,\n\t\t'default': null,\n\t},\n\tquiet: {\n\t\tdescription: 'Emit only the marked-up HTML',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\tdebug: {\n\t\tdescription: 'Debug mode',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n});\n\nPromise.async(function *() {\n\tvar argv = opts.argv;\n\tvar oldhtml = argv.oldhtml;\n\tvar newhtml = argv.newhtml;\n\n\tif (!oldhtml && argv._[0]) {\n\t\toldhtml = yield fs.readFile(argv._[0], 'utf8');\n\t\tnewhtml = yield fs.readFile(argv._[1], 'utf8');\n\t}\n\n\tif (ScriptUtils.booleanOption(argv.help) || !oldhtml || !newhtml) {\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\n\tconst dummyEnv = new MockEnv({\n\t\tdebug: ScriptUtils.booleanOption(argv.debug),\n\t}, null);\n\n\t// FIXME: Move to `MockEnv`\n\tif (argv.debug) {\n\t\tvar logger = new ParsoidLogger(dummyEnv);\n\t\tlogger.registerBackend(/^(trace|debug)(\\/|$)/, logger.getDefaultTracerBackend());\n\t\tdummyEnv.log = (...args) => logger.log(...args);\n\t} else {\n\t\tdummyEnv.log = function() {};\n\t}\n\n\tvar oldDOM = ContentUtils.ppToDOM(dummyEnv, oldhtml, { markNew: true });\n\tvar newDOM = ContentUtils.ppToDOM(dummyEnv, newhtml, { markNew: true });\n\n\tContentUtils.stripSectionTagsAndFallbackIds(oldDOM);\n\tContentUtils.stripSectionTagsAndFallbackIds(newDOM);\n\n\t(new DOMDiff(dummyEnv)).diff(oldDOM, newDOM);\n\n\tContentUtils.dumpDOM(newDOM, 'DIFF-marked DOM', {\n\t\tquiet: !!ScriptUtils.booleanOption(argv.quiet),\n\t\tstoreDiffMark: true,\n\t\tenv: dummyEnv,\n\t});\n\n\tprocess.exit(0);\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/inspectTokenizer.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found createWriteStream from package \"fs\" with non literal argument at index 0","line":70,"column":10,"nodeType":"CallExpression","endLine":70,"endColumn":44},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":77,"column":22,"nodeType":"CallExpression","endLine":77,"endColumn":66},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":218,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":218,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nvar yargs = require('yargs');\nvar PegTokenizer = require('../lib/wt2html/tokenizer.js').PegTokenizer;\nvar fs = require('fs');\n\nyargs.usage('Inspect the PEG.js grammar and generated source.');\n\n//\t'Inspect the PEG.js grammar and generated source');\n\nyargs.options({\n\t'source': {\n\t\tdescription: 'Show tokenizer source code',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'rules': {\n\t\tdescription: 'Show rule action source code',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'callgraph': {\n\t\tdescription: 'Write out a DOT graph of rule dependencies',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'list-orphans': {\n\t\tdescription: 'List rules that are not called by any other rule',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'outfile': {\n\t\tdescription: 'File name to write the output to',\n\t\t'boolean': false,\n\t\t'default': '-',\n\t\t'alias': 'o'\n\t},\n\n\t'php': {\n\t\tdescription: 'Use the PHP grammar',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'trace': {\n\t\tdescription: 'Generate code that logs rule transitions to stdout',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\n\t'header-comment-file': {\n\t\tdescription: 'File containing the comment to add in the header of the generated file',\n\t\t'boolean': false,\n\t\t'default': __dirname + '/grammarheader-script.txt'\n\t}\n});\n\nyargs.help();\n\nfunction getOutputStream(opts) {\n\tif (!opts.outfile || opts.outfile === '-') {\n\t\treturn process.stdout;\n\t} else {\n\t\treturn fs.createWriteStream(opts.outfile);\n\t}\n}\n\nfunction generateSource(opts) {\n\tvar file = getOutputStream(opts);\n\tvar tokenizer = new PegTokenizer();\n\tvar headerComment = fs.readFileSync(opts['header-comment-file']).toString();\n\tvar pegOpts = { trace: opts.trace, headerComment: headerComment };\n\tvar source = tokenizer.compileTokenizer(tokenizer.parseTokenizer(pegOpts), pegOpts);\n\tfile.write(source, 'utf8');\n}\n\nfunction generateRules(opts) {\n\tvar file = getOutputStream(opts);\n\tvar tokenizer = new PegTokenizer();\n\tvar pegOpts = { php: opts.php };\n\tvar ast = tokenizer.parseTokenizer(pegOpts);\n\tvar visitor = require('wikipeg/lib/compiler/visitor');\n\n\t// Current code style seems to use spaces in the tokenizer.\n\tvar tab = '    ';\n\t// Add some eslint overrides and define globals.\n\tvar rulesSource = '/* eslint-disable indent,camelcase,no-unused-vars */\\n';\n\trulesSource += \"\\n'use strict';\\n\\n\";\n\trulesSource += 'var options, location, input, text, peg$cache, peg$currPos, peg$savedPos;\\n';\n\t// Prevent redefinitions of variables involved in choice expressions\n\tvar seen = new Set();\n\tvar addVar = function(name) {\n\t\tif (!seen.has(name)) {\n\t\t\trulesSource += tab + 'var ' + name + ' = null;\\n';\n\t\t\tseen.add(name);\n\t\t}\n\t};\n\t// Collect all the code blocks in the AST.\n\tvar dumpCode = function(node) {\n\t\tif (node.code) {\n\t\t\t// remove trailing whitespace for single-line predicates\n\t\t\tvar code = node.code.replace(/[ \\t]+$/, '');\n\t\t\t// wrap with a function, to prevent spurious errors caused\n\t\t\t// by redeclarations or multiple returns in a block.\n\t\t\trulesSource += tab + '(function() {\\n' + code + '\\n' +\n\t\t\t\ttab + '})();\\n';\n\t\t}\n\t};\n\tvar visit = visitor.build({\n\t\tinitializer: function(node) {\n\t\t\tif (node.code) {\n\t\t\t\trulesSource += node.code + '\\n';\n\t\t\t}\n\t\t},\n\t\tsemantic_and: dumpCode,\n\t\tsemantic_node: dumpCode,\n\t\trule: function(node) {\n\t\t\trulesSource += 'function rule_' + node.name + '() {\\n';\n\t\t\tseen.clear();\n\t\t\tvisit(node.expression);\n\t\t\trulesSource += '}\\n';\n\t\t},\n\t\tlabeled: function(node) {\n\t\t\taddVar(node.label);\n\t\t\tvisit(node.expression);\n\t\t},\n\t\tlabeled_param: function(node) {\n\t\t\taddVar(node.label);\n\t\t},\n\t\tnamed: function(node) {\n\t\t\taddVar(node.name);\n\t\t\tvisit(node.expression);\n\t\t},\n\t\taction: function(node) {\n\t\t\tvisit(node.expression);\n\t\t\tdumpCode(node);\n\t\t},\n\t});\n\tvisit(ast);\n\t// Write rules to file.\n\tfile.write(rulesSource, 'utf8');\n}\n\nfunction generateCallgraph(opts) {\n\tvar file = getOutputStream(opts);\n\tvar tokenizer = new PegTokenizer();\n\tvar pegOpts = { php: opts.php };\n\tvar ast = tokenizer.parseTokenizer(pegOpts);\n\tvar visitor = require('wikipeg/lib/compiler/visitor');\n\tvar edges = [];\n\tvar currentRuleName;\n\n\tvar visit = visitor.build({\n\t\trule: function(node) {\n\t\t\tcurrentRuleName = node.name;\n\t\t\tvisit(node.expression);\n\t\t},\n\n\t\trule_ref: function(node) {\n\t\t\tvar edge = \"\\t\" + currentRuleName + \" -> \" + node.name + \";\";\n\t\t\tif (!edges.includes(edge)) {\n\t\t\t\tedges.push(edge);\n\t\t\t}\n\t\t}\n\t});\n\n\tvisit(ast);\n\n\tvar dot = \"digraph {\\n\" +\n\t\tedges.join(\"\\n\") + \"\\n\" +\n\t\t\"}\\n\";\n\n\tfile.write(dot, 'utf8');\n}\n\nfunction listOrphans(opts) {\n\tvar file = getOutputStream(opts);\n\tvar tokenizer = new PegTokenizer();\n\tvar pegOpts = { php: opts.php };\n\tvar ast = tokenizer.parseTokenizer(pegOpts);\n\tvar visitor = require('wikipeg/lib/compiler/visitor');\n\n\tvar rules = {};\n\n\tvisitor.build({\n\t\trule: function(node) {\n\t\t\trules[node.name] = true;\n\t\t},\n\t})(ast);\n\n\tvisitor.build({\n\t\trule_ref: function(node) {\n\t\t\tdelete rules[node.name];\n\t\t},\n\t})(ast);\n\n\tfile.write(Object.getOwnPropertyNames(rules).join('\\n') + '\\n');\n}\n\nvar opts = yargs.argv;\n\nif (opts.source) {\n\tgenerateSource(opts);\n} else if (opts.rules) {\n\tgenerateRules(opts);\n} else if (opts.callgraph) {\n\tgenerateCallgraph(opts);\n} else if (opts['list-orphans']) {\n\tlistOrphans(opts);\n} else {\n\tconsole.error(\"Either --source, --rules, --callgraph or --list-orphans must be specified\");\n\tprocess.exit(1);\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/langconv-test.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'","line":12,"column":51,"nodeType":"Literal","messageId":"notFound","endLine":12,"endColumn":76},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/bin'","line":17,"column":41,"nodeType":"Literal","messageId":"notFound","endLine":17,"endColumn":79},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'","line":20,"column":37,"nodeType":"Literal","messageId":"notFound","endLine":20,"endColumn":62},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":405,"column":5,"nodeType":"CallExpression","endLine":405,"endColumn":23},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":625,"column":4,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":625,"endColumn":26}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\nconst colors = require('colors');\nconst fs = require('pn/fs');\nconst path = require('path');\nconst yargs = require('yargs');\n\nconst { ApiRequest, DoesNotExistError } = require('../lib/mw/ApiRequest.js');\nconst { Diff } = require('../lib/utils/Diff.js');\nconst { DOMDataUtils } = require('../lib/utils/DOMDataUtils.js');\nconst { DOMTraverser } = require('../lib/utils/DOMTraverser.js');\nconst { DOMUtils } = require('../lib/utils/DOMUtils.js');\nconst { MWParserEnvironment } = require('../lib/config/MWParserEnvironment.js');\nconst { ParsoidConfig } = require('../lib/config/ParsoidConfig.js');\nconst Promise = require('../lib/utils/promise.js');\nconst { TemplateRequest } = require('../lib/mw/ApiRequest.js');\nconst { Util } = require('../lib/utils/Util.js');\nconst { ScriptUtils } = require('../tools/ScriptUtils.js');\n\nconst jsonFormat = function(error, domain, title, lang, options, results) {\n\tif (error) {\n\t\treturn { error: error.stack || error.toString() };\n\t}\n\tconst p = Diff.patchDiff(results.php, results.parsoid);\n\treturn { patch: p };\n};\n\nconst plainFormat = function(error, domain, title, lang, options, results) {\n\tif (error) {\n\t\treturn error.stack || error.toString();\n\t}\n\tconst article = `${ domain } ${ title } ${ lang || '' }`;\n\tconst diff = Diff.colorDiff(results.php, results.parsoid, {\n\t\tcontext: 1,\n\t\tnoColor: (colors.mode === 'none'),\n\t\tdiffCount: true,\n\t});\n\tif (diff.count === 0) {\n\t\treturn '';\n\t}\n\treturn `== ${ article } ==\\n${ diff.output }\\n${ diff.count } different words found.\\n`;\n};\n\nconst xmlFormat = function(error, domain, title, lang, options, results) {\n\tconst article = `${ domain } ${ title } ${ lang || '' }`;\n\tlet output = '<testsuites>\\n';\n\toutput += `<testsuite name=\"Variant ${ Util.escapeHtml(article) }\">\\n`;\n\toutput += `<testcase name=\"revision ${ results.revid }\">\\n`;\n\tif (error) {\n\t\toutput += '<error type=\"parserFailedToFinish\">';\n\t\toutput += Util.escapeHtml(error.stack || error.toString());\n\t\toutput += '</error>';\n\t} else if (results.php !== results.parsoid) {\n\t\toutput += '<failure type=\"diff\">\\n<diff class=\"html\">\\n';\n\t\toutput += Diff.colorDiff(results.php, results.parsoid, {\n\t\t\tcontext: 1,\n\t\t\thtml: true,\n\t\t\tseparator: '</diff></failure>\\n' +\n\t\t\t\t'<failure type=\"diff\"><diff class=\"html\">',\n\t\t});\n\t\toutput += '\\n</diff>\\n</failure>\\n';\n\t}\n\toutput += '</testcase>\\n';\n\toutput += '</testsuite>\\n';\n\toutput += '</testsuites>\\n';\n\treturn output;\n};\n\nconst silentFormat = function(error, domain, title, lang, options, results) {\n\treturn '';\n};\n\nclass PHPVariantRequest extends ApiRequest {\n\tconstructor(env, title, variant, revid) {\n\t\tsuper(env, title);\n\t\tthis.reqType = \"Variant Parse\";\n\n\t\tconst apiargs = {\n\t\t\tformat: 'json',\n\t\t\taction: 'parse',\n\t\t\tpage: title,\n\t\t\tprop: 'text|revid|displaytitle',\n\t\t\tuselang: 'content',\n\t\t\twrapoutputclass: '',\n\t\t\tdisableeditsection: 'true',\n\t\t\tdisabletoc: 'true',\n\t\t\tdisablelimitreport: 'true'\n\t\t};\n\t\tif (revid) {\n\t\t\t// The parameters `page` and `oldid` can't be used together\n\t\t\tapiargs.page = undefined;\n\t\t\tapiargs.oldid = revid;\n\t\t}\n\t\t// This argument to the API is not documented!  Except in\n\t\t// https://phabricator.wikimedia.org/T44356#439479 and\n\t\t// https://phabricator.wikimedia.org/T34906#381101\n\t\tif (variant) {\n\t\t\tapiargs.variant = variant;\n\t\t}\n\n\t\tconst uri = env.conf.wiki.apiURI;\n\t\tthis.requestOptions = {\n\t\t\turi,\n\t\t\tmethod: 'POST',\n\t\t\tform: apiargs, // The API arguments\n\t\t\tfollowRedirect: true,\n\t\t\ttimeout: env.conf.parsoid.timeouts.mwApi.extParse,\n\t\t};\n\n\t\tthis.request(this.requestOptions);\n\t}\n\n\t_handleJSON(error, data) {\n\t\tif (!error && !(data && data.parse)) {\n\t\t\terror = this._errorObj(data, this.text, 'Missing data.parse.');\n\t\t}\n\n\t\tif (error) {\n\t\t\tthis.env.log(\"error\", error);\n\t\t\tthis._processListeners(error, '');\n\t\t} else {\n\t\t\tthis._processListeners(error, data.parse);\n\t\t}\n\t}\n\n\t_errorObj(data, requestStr, defaultMsg) {\n\t\tif (data && data.error && data.error.code === 'missingtitle') {\n\t\t\treturn new DoesNotExistError(this.title);\n\t\t}\n\t\treturn super._errorObj(data, requestStr, defaultMsg);\n\t}\n}\n\nconst phpFetch = Promise.async(function *(env, title, revid) {\n\tconst parse = yield new Promise((resolve, reject) => {\n\t\tconst req = new PHPVariantRequest(\n\t\t\tenv, title, env.htmlVariantLanguage, revid\n\t\t);\n\t\treq.once('src', (err, src) => err ? reject(err) : resolve(src));\n\t});\n\tconst document = DOMUtils.parseHTML(parse.text['*']);\n\tconst displaytitle = parse.displaytitle;\n\trevid = parse.revid;\n\treturn {\n\t\tdocument,\n\t\trevid,\n\t\tdisplaytitle\n\t};\n});\n\nconst parsoidFetch = Promise.async(function *(env, title, options) {\n\tif (!options.useServer) {\n\t\tyield TemplateRequest.setPageSrcInfo(env, title, options.oldid);\n\t\tconst revision = env.page.meta.revision;\n\t\tconst handler = env.getContentHandler(revision.contentmodel);\n\t\tconst document = yield handler.toHTML(env);\n\t\treturn {\n\t\t\tdocument,\n\t\t\trevid: revision.revid,\n\t\t\tdisplaytitle: document.title,\n\t\t};\n\t}\n\tconst domain = options.domain;\n\tlet uri = options.uri;\n\t// Make sure the Parsoid URI ends with `/`\n\tif (!/\\/$/.test(uri)) {\n\t\turi += '/';\n\t}\n\turi += `${ domain }/v3/page/html/${ encodeURIComponent(title) }`;\n\tif (options.oldid) {\n\t\turi += `/${ options.oldid }`;\n\t}\n\tconst resp = yield ScriptUtils.retryingHTTPRequest(10, {\n\t\tmethod: 'GET',\n\t\turi,\n\t\theaders: {\n\t\t\t'User-Agent': env.userAgent,\n\t\t\t'Accept-Language': env.htmlVariantLanguage,\n\t\t}\n\t});\n\t// We may have been redirected to the latest revision. Record oldid.\n\tconst res = resp[0];\n\tconst body = resp[1];\n\tif (res.statusCode !== 200) {\n\t\tthrow new Error(`Can\\'t fetch Parsoid source: ${ uri }`);\n\t}\n\tconst oldid = res.request.path.replace(/^(.*)\\//, '');\n\tconst document = DOMUtils.parseHTML(body);\n\treturn {\n\t\tdocument,\n\t\trevid: oldid,\n\t\tdisplaytitle: document.title,\n\t};\n});\n\nconst hrefToTitle = function(href) {\n\treturn Util.decodeURIComponent(href.replace(/^(\\.\\.?|\\/wiki)\\//, ''))\n\t\t.replace(/_/g, ' ');\n};\n\nconst nodeHrefToTitle = function(node, suppressCategory) {\n\tconst href = node && node.hasAttribute('href') && node.getAttribute('href');\n\tif (!href) {\n\t\treturn null;\n\t}\n\tconst title = hrefToTitle(href);\n\tif (suppressCategory) {\n\t\tconst categoryMatch = title.match(/^([^:]+)[:]/);\n\t\tif (categoryMatch) {\n\t\t\treturn null; /* skip it */\n\t\t}\n\t}\n\treturn title;\n};\n\n/**\n * Pull a list of local titles from wikilinks in a Parsoid HTML document.\n *\n * @param env\n * @param document\n */\nconst spiderDocument = function(env, document) {\n\tconst redirect = document.querySelector('link[rel~=\"mw:PageProp/redirect\"]');\n\tconst nodes = redirect ? [ redirect ] :\n\t\tArray.from(document.querySelectorAll('a[rel~=\"mw:WikiLink\"][href]'));\n\treturn new Set(\n\t\tnodes.map(node => nodeHrefToTitle(node, true)).filter(t => t !== null)\n\t);\n};\n\n/**\n * Pull \"just the text\" from an HTML document, normalizing whitespace\n * differences and suppressing places where Parsoid and PHP output\n * deliberately differs.\n *\n * @param env\n * @param document\n */\nconst extractText = function(env, document) {\n\tvar dt = new DOMTraverser();\n\tvar sep = '';\n\tvar buf = '';\n\t/* We normalize all whitespace in text nodes to a single space. We\n\t * do insert newlines in the output, but only to delimit block\n\t * elements.  Even there, we are careful never to emit two newlines\n\t * in a row, or whitespace before or after a newline. */\n\tconst addSep = (s) => {\n\t\tif (s === '') {\n\t\t\treturn;\n\t\t}\n\t\tif (/\\n/.test(s)) {\n\t\t\tsep = '\\n'; return;\n\t\t}\n\t\tif (sep === '\\n') {\n\t\t\treturn;\n\t\t}\n\t\tsep = ' ';\n\t};\n\tconst emit = (s) => {\n\t\tif (s !== '') {\n\t\t\tbuf += sep; buf += s; sep = '';\n\t\t}\n\t};\n\tdt.addHandler('#text', (node, env2, atTopLevel, tplInfo) => {\n\t\tconst v = node.nodeValue.replace(/\\s+/g, ' ');\n\t\tconst m = /^(\\s*)(.*?)(\\s*)$/.exec(v);\n\t\taddSep(m[1]);\n\t\temit(m[2]);\n\t\taddSep(m[3]);\n\t\treturn true;\n\t});\n\tdt.addHandler('div', (node) => {\n\t\tif (node.classList.contains('magnify') &&\n\t\t\tnode.parentNode &&\n\t\t\tnode.parentNode.classList.contains('thumbcaption')) {\n\t\t\t// Skip the \"magnify\" link, which PHP has and Parsoid doesn't.\n\t\t\treturn node.nextSibling;\n\t\t}\n\t\treturn true;\n\t});\n\t/* These are the block elements which we delimit with newlines (aka,\n\t * we ensure they start on a line of their own). */\n\tvar forceBreak = () => {\n\t\taddSep('\\n'); return true;\n\t};\n\tfor (const el of ['p','li','div','table','tr','h1','h2','h3','h4','h5','h6','figure', 'figcaption']) {\n\t\tdt.addHandler(el, forceBreak);\n\t}\n\tdt.addHandler('div', (node) => {\n\t\tif (node.classList.contains('thumbcaption')) {\n\t\t\t// <figcaption> (Parsoid) is marked as forceBreak,\n\t\t\t// so thumbcaption (PHP) should be, too.\n\t\t\tforceBreak();\n\t\t}\n\t\treturn true;\n\t});\n\t/* Separate table columns with spaces */\n\tdt.addHandler('td', () => {\n\t\taddSep(' '); return true;\n\t});\n\t/* Suppress reference numbers and linkback text */\n\tdt.addHandler('sup', (node) => {\n\t\tif (\n\t\t\tnode.classList.contains('reference') /* PHP */ ||\n\t\t\tnode.classList.contains('mw-ref') /* Parsoid */\n\t\t) {\n\t\t\treturn node.nextSibling; // Skip contents of this node\n\t\t}\n\t\treturn true;\n\t});\n\tdt.addHandler('span', (node) => {\n\t\tif (\n\t\t\tnode.classList.contains('mw-cite-backlink') ||\n\t\t\t/\\bmw:referencedBy\\b/.test(node.getAttribute('rel') || '')\n\t\t) {\n\t\t\treturn node.nextSibling; // Skip contents of this node\n\t\t}\n\t\treturn true;\n\t});\n\tdt.addHandler('figcaption', (node) => {\n\t\t/* Captions are suppressed in PHP for:\n\t\t * figure[typeof~=\"mw:File/Frameless\"], figure[typeof~=\"mw:File\"]\n\t\t * See Note 5 of https://www.mediawiki.org/wiki/Specs/HTML/1.7.0#Images\n\t\t */\n\t\tif (DOMDataUtils.hasTypeOf(node.parentNode, 'mw:File/Frameless') ||\n\t\t\tDOMDataUtils.hasTypeOf(node.parentNode, 'mw:File')) {\n\t\t\t// Skip caption contents, since they don't appear in PHP output.\n\t\t\treturn node.nextSibling;\n\t\t}\n\t\treturn true;\n\t});\n\t/* Show the targets of wikilinks, since the titles should be\n\t * language-converted too. */\n\tdt.addHandler('a', (node) => {\n\t\tconst rel = node.getAttribute('rel') || '';\n\t\tif (/\\bmw:referencedBy\\b/.test(rel)) {\n\t\t\t// skip reference linkback\n\t\t\treturn node.nextSibling;\n\t\t}\n\t\tlet href = node.getAttribute('href') || '';\n\t\t// Rewrite red links as normal links\n\t\tlet m = /^\\/w\\/index\\.php\\?title=(.*?)&.*redlink=1$/.exec(href);\n\t\tif (m) {\n\t\t\thref = `/wiki/${ m[1] }`;\n\t\t}\n\t\t// Local links to this page, or self-links\n\t\tm = /^#/.test(href);\n\t\tif (m || node.classList.contains('mw-selflink')) {\n\t\t\tconst title = encodeURIComponent(env.page.name);\n\t\t\thref = `/wiki/${ title }${ href }`;\n\t\t}\n\t\t// Now look for wiki links\n\t\tif (node.classList.contains('external')) {\n\t\t\treturn true;\n\t\t}\n\t\tif (/^(\\.\\.?|\\/wiki)\\//.test(href)) {\n\t\t\tconst title = hrefToTitle(href);\n\t\t\taddSep(' ');\n\t\t\temit(`[${ title }]`);\n\t\t\taddSep(' ');\n\t\t}\n\t\treturn true;\n\t});\n\tdt.addHandler('link', (node) => {\n\t\tconst rel = node.getAttribute('rel') || '';\n\t\tif (/\\bmw:PageProp\\/redirect\\b/.test(rel)) {\n\t\t\t// Given Parsoid output, emulate PHP output for redirects.\n\t\t\tforceBreak();\n\t\t\temit('Redirect to:');\n\t\t\tforceBreak();\n\t\t\tconst title = nodeHrefToTitle(node);\n\t\t\temit(`[${ title }]`);\n\t\t\taddSep(' ');\n\t\t\temit(title);\n\t\t\treturn node.nextSibling;\n\t\t}\n\t\treturn true;\n\t});\n\tdt.traverse(document.body);\n\treturn buf;\n};\n\n// Wrap an asynchronous function in code to record/replay network requests\nconst nocksWrap = function(f) {\n\treturn Promise.async(function *(domain, title, lang, options, formatter) {\n\t\tlet nock, dir, nocksFile;\n\t\tif (options.record || options.replay) {\n\t\t\tdir = path.resolve(__dirname, '../nocks/');\n\t\t\tif (!(yield fs.exists(dir))) {\n\t\t\t\tyield fs.mkdir(dir);\n\t\t\t}\n\t\t\tdir = `${ dir }/${ domain }`;\n\t\t\tif (!(yield fs.exists(dir))) {\n\t\t\t\tyield fs.mkdir(dir);\n\t\t\t}\n\t\t\tnocksFile = `${ dir }/lc-${ encodeURIComponent(title) }-${ lang }.js`;\n\t\t\tif (options.record) {\n\t\t\t\tnock = require('nock');\n\t\t\t\tnock.recorder.rec({ dont_print: true });\n\t\t\t} else {\n\t\t\t\trequire(nocksFile);\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\treturn (yield f(domain, title, lang, options, formatter));\n\t\t} finally {\n\t\t\tif (options.record) {\n\t\t\t\tconst nockCalls = nock.recorder.play();\n\t\t\t\tyield fs.writeFile(\n\t\t\t\t\tnocksFile,\n\t\t\t\t\t`'use strict';\\nlet nock = require('nock');\\n${ nockCalls.join('\\n') }`,\n\t\t\t\t\t'utf8'\n\t\t\t\t);\n\t\t\t\tnock.recorder.clear();\n\t\t\t\tnock.restore();\n\t\t\t}\n\t\t}\n\t});\n};\n\nconst runTest = nocksWrap(Promise.async(function *(domain, title, lang, options, formatter) {\n\t// Step 0: Configuration & setup\n\tconst parsoidOptions = {\n\t\tloadWMF: true,\n\t};\n\tconst envOptions = {\n\t\tdomain,\n\t\tpageName: title,\n\t\tuserAgent: 'LangConvTest',\n\t\twtVariantLanguage: options.sourceVariant || null,\n\t\thtmlVariantLanguage: lang || null,\n\t\tlogLevels: options.verbose ? undefined : [\"fatal\", \"error\", \"warn\"],\n\t};\n\tScriptUtils.setTemplatingAndProcessingFlags(parsoidOptions, options);\n\tScriptUtils.setDebuggingFlags(parsoidOptions, options);\n\tScriptUtils.setColorFlags(options);\n\n\tconst parsoidConfig = new ParsoidConfig(null, parsoidOptions);\n\tconst env = yield MWParserEnvironment.getParserEnv(parsoidConfig, envOptions);\n\n\t// Step 1: Fetch page from PHP API\n\tconst phpDoc = yield phpFetch(env, title, options.oldid);\n\t// Step 2: Fetch page from Parsoid API\n\tconst parsoidDoc = yield parsoidFetch(env, title, {\n\t\tdomain,\n\t\turi: options.parsoidURL,\n\t\toldid: options.oldid || phpDoc.revid,\n\t\tuseServer: options.useServer,\n\t});\n\t// Step 3: Strip most markup (so we're comparing text, not markup)\n\t//  ...but eventually we'll leave <a href> since there's some title\n\t//    conversion that should be done.\n\tconst normalize = out => `TITLE: ${ out.displaytitle }\\n\\n` +\n\t\textractText(env, out.document);\n\tconst phpText = normalize(phpDoc);\n\tconst parsoidText = normalize(parsoidDoc);\n\t// Step 4: Compare (and profit!)\n\tconsole.assert(+phpDoc.revid === +parsoidDoc.revid);\n\tconst output = formatter(null, domain, title, lang, options, {\n\t\tphp: phpText,\n\t\tparsoid: parsoidText,\n\t\trevid: phpDoc.revid,\n\t});\n\tconst exitCode = (phpText === parsoidText) ? 0 : 1;\n\n\treturn {\n\t\toutput,\n\t\texitCode,\n\t\t// List of local titles, in case we are spidering test cases\n\t\tlinkedTitles: spiderDocument(env, parsoidDoc.document),\n\t};\n}));\n\nif (require.main === module) {\n\tconst standardOpts = ScriptUtils.addStandardOptions({\n\t\tsourceVariant: {\n\t\t\tdescription: 'Force conversion to assume the given variant for' +\n\t\t\t\t' the source wikitext',\n\t\t\tboolean: false,\n\t\t\tdefault: null,\n\t\t},\n\t\tdomain: {\n\t\t\tdescription: 'Which wiki to use; e.g. \"sr.wikipedia.org\" for' +\n\t\t\t\t' Serbian wikipedia',\n\t\t\tboolean: false,\n\t\t\tdefault: 'sr.wikipedia.org',\n\t\t},\n\t\toldid: {\n\t\t\tdescription: 'Optional oldid of the given page. If not given,' +\n\t\t\t\t' will use the latest revision.',\n\t\t\tboolean: false,\n\t\t\tdefault: null,\n\t\t},\n\t\tparsoidURL: {\n\t\t\tdescription: 'The URL for the Parsoid API',\n\t\t\tboolean: false,\n\t\t\tdefault: '',\n\t\t},\n\t\tapiURL: {\n\t\t\tdescription: 'http path to remote API,' +\n\t\t\t\t' e.g. http://sr.wikipedia.org/w/api.php',\n\t\t\tboolean: false,\n\t\t\tdefault: '',\n\t\t},\n\t\txml: {\n\t\t\tdescription: 'Use xml output format',\n\t\t\tboolean: true,\n\t\t\tdefault: false,\n\t\t},\n\t\tcheck: {\n\t\t\tdescription: 'Exit with non-zero exit code if differences found using selser',\n\t\t\tboolean: true,\n\t\t\tdefault: false,\n\t\t\talias: 'c',\n\t\t},\n\t\t'record': {\n\t\t\tdescription: 'Record http requests for later replay',\n\t\t\t'boolean': true,\n\t\t\t'default': false,\n\t\t},\n\t\t'replay': {\n\t\t\tdescription: 'Replay recorded http requests for later replay',\n\t\t\t'boolean': true,\n\t\t\t'default': false,\n\t\t},\n\t\t'spider': {\n\t\t\tdescription: 'Spider <number> additional pages past the given one',\n\t\t\t'boolean': false,\n\t\t\t'default': 0,\n\t\t},\n\t\t'silent': {\n\t\t\tdescription: 'Skip output (used with --record --spider to load caches)',\n\t\t\t'boolean': true,\n\t\t\t'default': false,\n\t\t},\n\t\t'verbose': {\n\t\t\tdescription: 'Log at level \"info\" as well',\n\t\t\t'boolean': true,\n\t\t\t'default': false,\n\t\t},\n\t\t'useServer': {\n\t\t\tdescription: 'Use a parsoid server',\n\t\t\t'boolean': true,\n\t\t\t'default': false,\n\t\t},\n\t});\n\n\tPromise.async(function *() {\n\t\tconst opts = yargs\n\t\t.usage(\n\t\t\t'Usage: $0 [options] <page-title> <variantLanguage>\\n' +\n\t\t\t'The page title should be the \"true title\",' +\n\t\t\t'i.e., without any url encoding which might be necessary if it appeared in wikitext.' +\n\t\t\t'\\n\\n'\n\t\t)\n\t\t.options(standardOpts)\n\t\t.strict();\n\t\tconst argv = opts.argv;\n\t\tif (!argv._.length) {\n\t\t\treturn opts.showHelp();\n\t\t}\n\t\tconst title = String(argv._[0]);\n\t\tconst lang = String(argv._[1]);\n\t\tif (argv.record || argv.replay) {\n\t\t\t// Don't fork a separate server if record/replay\n\t\t\targv.useServer = false;\n\t\t}\n\t\tif (argv.useServer && !argv.parsoidURL) {\n\t\t\tthrow new Error('No parsoidURL provided!');\n\t\t}\n\t\tconst formatter =\n\t\t\tScriptUtils.booleanOption(argv.silent) ? silentFormat :\n\t\t\tScriptUtils.booleanOption(argv.xml) ? xmlFormat :\n\t\t\tplainFormat;\n\t\tconst domain = argv.domain || 'sr.wikipedia.org';\n\t\tconst queue = [title];\n\t\tconst titlesDone = new Set();\n\t\tlet exitCode = 0;\n\t\tlet r;\n\t\tfor (let i = 0; i < queue.length; i++) {\n\t\t\tif (titlesDone.has(queue[i])) {\n\t\t\t\tcontinue; // duplicate title\n\t\t\t}\n\t\t\tif (argv.spider > 1 && argv.verbose) {\n\t\t\t\tconsole.log('%s (%d/%d)', queue[i], titlesDone.size, argv.spider);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tr = yield runTest(domain, queue[i], lang, argv, formatter);\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof DoesNotExistError && argv.spider > 1) {\n\t\t\t\t\t// Ignore page-not-found if we are spidering.\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tr = {\n\t\t\t\t\terror: true,\n\t\t\t\t\toutput: formatter(e, domain, queue[i], lang, argv),\n\t\t\t\t\texitCode: 2,\n\t\t\t\t};\n\t\t\t}\n\t\t\texitCode = Math.max(exitCode, r.exitCode);\n\t\t\tif (r.output) {\n\t\t\t\tconsole.log(r.output);\n\t\t\t}\n\t\t\t// optionally, spider\n\t\t\tif (argv.spider > 1) {\n\t\t\t\tif (!r.error) {\n\t\t\t\t\ttitlesDone.add(queue[i]);\n\t\t\t\t\tfor (const t of r.linkedTitles) {\n\t\t\t\t\t\tif (/:/.test(t)) {\n\t\t\t\t\t\t\tcontinue; /* hack: no namespaces */\n\t\t\t\t\t\t}\n\t\t\t\t\t\tqueue.push(t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (titlesDone.size >= argv.spider) {\n\t\t\t\t\tbreak; /* done! */\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (argv.check || exitCode > 1) {\n\t\t\tprocess.exit(exitCode);\n\t\t}\n\t})().done();\n} else if (typeof module === 'object') {\n\tmodule.exports.runTest = runTest;\n\n\tmodule.exports.jsonFormat = jsonFormat;\n\tmodule.exports.plainFormat = plainFormat;\n\tmodule.exports.xmlFormat = xmlFormat;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/normalize.test.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":64,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":64,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\nvar DOMNormalizer = require('../lib/html2wt/DOMNormalizer.js').DOMNormalizer;\nvar ContentUtils = require('../lib/utils/ContentUtils.js').ContentUtils;\nvar Promise = require('../lib/utils/promise.js');\nvar ScriptUtils = require('../tools/ScriptUtils.js').ScriptUtils;\nvar MockEnv = require('../tests/MockEnv.js').MockEnv;\n\nvar yargs = require('yargs');\nvar fs = require('pn/fs');\n\nvar opts = yargs\n.usage(\"Usage: $0 [options] [html-file]\\n\\nProvide either inline html OR 1 file\")\n.options({\n\thelp: {\n\t\tdescription: 'Show this message',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\tenableSelserMode: {\n\t\tdescription: [\n\t\t\t'Run in selser mode (but dom-diff markers are not loaded).',\n\t\t\t'This just forces more normalization code to run.',\n\t\t\t'So, this is \"fake selser\" mode till we are able to load diff markers from attributes'\n\t\t].join(' '),\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\thtml: {\n\t\tdescription: 'html',\n\t\t'boolean': false,\n\t\t'default': '',\n\t},\n});\n\nPromise.async(function *() {\n\tvar argv = opts.argv;\n\tvar html = argv.html;\n\tif (!html && argv._[0]) {\n\t\thtml = yield fs.readFile(argv._[0], 'utf8');\n\t}\n\n\tif (ScriptUtils.booleanOption(argv.help) || !html) {\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\n\tconst env = new MockEnv({}, null);\n\n\tvar mockState = {\n\t\tenv,\n\t\tselserMode: argv.enableSelserMode\n\t};\n\n\tconst domBody = ContentUtils.ppToDOM(env, html, { markNew: true });\n\tconst normalizedBody = (new DOMNormalizer(mockState).normalize(domBody));\n\n\tContentUtils.dumpDOM(normalizedBody, 'Normalized DOM', { env: mockState.env, storeDiffMark: true });\n\n\tprocess.exit(0);\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/bin/roundtrip-test.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":996,"column":4,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":996,"endColumn":28}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\nrequire('colors');\n\nvar entities = require('entities');\nvar fs = require('fs');\nvar yargs = require('yargs');\nvar zlib = require('pn/zlib');\n\nvar Promise = require('../lib/utils/promise.js');\nvar Util = require('../lib/utils/Util.js').Util;\nvar ScriptUtils = require('../tools/ScriptUtils.js').ScriptUtils;\nvar ContentUtils = require('../lib/utils/ContentUtils.js').ContentUtils;\nvar DOMUtils = require('../lib/utils/DOMUtils.js').DOMUtils;\nvar DOMDataUtils = require('../lib/utils/DOMDataUtils.js').DOMDataUtils;\nvar TestUtils = require('../tests/TestUtils.js').TestUtils;\nvar WTUtils = require('../lib/utils/WTUtils.js').WTUtils;\nvar ParsoidConfig = require('../lib/config/ParsoidConfig.js').ParsoidConfig;\nvar Diff = require('../lib/utils/Diff.js').Diff;\nvar JSUtils = require('../lib/utils/jsutils.js').JSUtils;\nvar MockEnv = require('../tests/MockEnv.js').MockEnv;\n\nvar defaultContentVersion = '2.8.0';\n\nvar MAX_RETRIES = 10;\n\nfunction displayDiff(type, count) {\n\tvar pad = (10 - type.length);  // Be positive!\n\ttype = type[0].toUpperCase() + type.slice(1);\n\treturn type + ' differences' + ' '.repeat(pad) + ': ' + count + '\\n';\n}\n\nvar jsonFormat = function(error, prefix, title, results, profile) {\n\tvar diffs = {\n\t\thtml2wt: { semantic: 0, syntactic: 0 },\n\t\tselser: { semantic: 0, syntactic: 0 },\n\t};\n\tif (!error) {\n\t\tresults.forEach(function(result) {\n\t\t\tvar mode = diffs[result.selser ? 'selser' : 'html2wt'];\n\t\t\tmode[result.type === 'fail' ? 'semantic' : 'syntactic']++;\n\t\t});\n\t}\n\treturn {\n\t\terror: error,\n\t\tresults: diffs,\n\t};\n};\n\nvar plainFormat = function(err, prefix, title, results, profile) {\n\tvar testDivider = '='.repeat(70) + '\\n';\n\tvar diffDivider = '-'.repeat(70) + '\\n';\n\tvar output = '';\n\n\tif (err) {\n\t\toutput += 'Parser failure!\\n\\n';\n\t\toutput += diffDivider;\n\t\toutput += err;\n\t\tif (err.stack) {\n\t\t\toutput += '\\nStack trace: ' + err.stack;\n\t\t}\n\t} else {\n\t\tvar diffs = {\n\t\t\thtml2wt: { semantic: 0, syntactic: 0 },\n\t\t\tselser: { semantic: 0, syntactic: 0 },\n\t\t};\n\t\tfor (var i = 0; i < results.length; i++) {\n\t\t\tvar result = results[i];\n\t\t\toutput += testDivider;\n\t\t\tif (result.type === 'fail') {\n\t\t\t\toutput += 'Semantic difference' +\n\t\t\t\t\t(result.selser ? ' (selser)' : '') + ':\\n\\n';\n\t\t\t\toutput += result.wtDiff + '\\n';\n\t\t\t\toutput += diffDivider + 'HTML diff:\\n\\n' +\n\t\t\t\t\tresult.htmlDiff + '\\n';\n\t\t\t\tdiffs[result.selser ? 'selser' : 'html2wt'].semantic++;\n\t\t\t} else {\n\t\t\t\toutput += 'Syntactic difference' +\n\t\t\t\t\t(result.selser ? ' (selser)' : '') + ':\\n\\n';\n\t\t\t\toutput += result.wtDiff + '\\n';\n\t\t\t\tdiffs[result.selser ? 'selser' : 'html2wt'].syntactic++;\n\t\t\t}\n\t\t}\n\t\toutput += testDivider;\n\t\toutput += testDivider;\n\t\toutput += 'SUMMARY:\\n';\n\t\toutput += diffDivider;\n\t\tvar total = 0;\n\t\tObject.keys(diffs).forEach(function(diff) {\n\t\t\toutput += diff + '\\n';\n\t\t\toutput += diffDivider;\n\t\t\tObject.keys(diffs[diff]).forEach(function(type) {\n\t\t\t\tvar count = diffs[diff][type];\n\t\t\t\ttotal += count;\n\t\t\t\toutput += displayDiff(type, count);\n\t\t\t});\n\t\t\toutput += diffDivider;\n\t\t});\n\t\toutput += displayDiff('all', total);\n\t\toutput += testDivider;\n\t\toutput += testDivider;\n\t}\n\n\treturn output;\n};\n\nvar xmlFormat = function(err, prefix, title, results, profile) {\n\tvar i, result;\n\tvar article = Util.escapeHtml(prefix + ':' + title);\n\tvar output = '<testsuites>\\n';\n\tvar outputTestSuite = function(selser) {\n\t\toutput += '<testsuite name=\"Roundtrip article ' + article;\n\t\tif (selser) {\n\t\t\toutput += ' (selser)';\n\t\t}\n\t\toutput += '\">\\n';\n\t};\n\n\tif (err) {\n\t\toutputTestSuite(false);\n\t\toutput += '<testcase name=\"entire article\">';\n\t\toutput += '<error type=\"parserFailedToFinish\">';\n\t\toutput += Util.escapeHtml(err.stack || err.toString());\n\t\toutput += '</error></testcase>';\n\t} else if (!results.length) {\n\t\toutputTestSuite(false);\n\t} else {\n\t\tvar currentSelser = results[0].selser;\n\t\toutputTestSuite(currentSelser);\n\t\tfor (i = 0; i < results.length; i++) {\n\t\t\tresult = results[i];\n\n\t\t\t// When going from normal to selser results, switch to a new\n\t\t\t// test suite.\n\t\t\tif (currentSelser !== result.selser) {\n\t\t\t\toutput += '</testsuite>\\n';\n\t\t\t\tcurrentSelser = result.selser;\n\t\t\t\toutputTestSuite(currentSelser);\n\t\t\t}\n\n\t\t\toutput += '<testcase name=\"' + article;\n\t\t\toutput += ' character ' + result.offset[0].start + '\">\\n';\n\n\t\t\tif (result.type === 'fail') {\n\t\t\t\toutput += '<failure type=\"significantHtmlDiff\">\\n';\n\n\t\t\t\toutput += '<diff class=\"wt\">\\n';\n\t\t\t\toutput += Util.escapeHtml(result.wtDiff);\n\t\t\t\toutput += '\\n</diff>\\n';\n\n\t\t\t\toutput += '<diff class=\"html\">\\n';\n\t\t\t\toutput += Util.escapeHtml(result.htmlDiff);\n\t\t\t\toutput += '\\n</diff>\\n';\n\n\t\t\t\toutput += '</failure>\\n';\n\t\t\t} else {\n\t\t\t\toutput += '<skipped type=\"insignificantWikitextDiff\">\\n';\n\t\t\t\toutput += Util.escapeHtml(result.wtDiff);\n\t\t\t\toutput += '\\n</skipped>\\n';\n\t\t\t}\n\n\t\t\toutput += '</testcase>\\n';\n\t\t}\n\t}\n\toutput += '</testsuite>\\n';\n\n\t// Output the profiling data\n\tif (profile) {\n\t\t// Delete the start time to avoid serializing it\n\t\tif (profile.time && profile.time.start) {\n\t\t\tdelete profile.time.start;\n\t\t}\n\t\toutput += '<perfstats>\\n';\n\t\tObject.keys(profile).forEach(function(type) {\n\t\t\tObject.keys(profile[type]).forEach(function(prop) {\n\t\t\t\toutput += '<perfstat type=\"' + TestUtils.encodeXml(type) + ':';\n\t\t\t\toutput += TestUtils.encodeXml(prop);\n\t\t\t\toutput += '\">';\n\t\t\t\toutput += TestUtils.encodeXml(profile[type][prop].toString());\n\t\t\t\toutput += '</perfstat>\\n';\n\t\t\t});\n\t\t});\n\t\toutput += '</perfstats>\\n';\n\t}\n\toutput += '</testsuites>';\n\n\treturn output;\n};\n\n// Find the subset of leaf/non-leaf nodes whose DSR ranges\n// span the wikitext range provided as input.\nvar findMatchingNodes = function(node, range) {\n\tconsole.assert(DOMUtils.isElt(node));\n\n\t// Skip subtrees that are outside our target range\n\tvar dp = DOMDataUtils.getDataParsoid(node);\n\tif (!Util.isValidDSR(dp.dsr) || dp.dsr[0] > range.end || dp.dsr[1] < range.start) {\n\t\treturn [];\n\t}\n\n\t// If target range subsumes the node, we are done.\n\tif (dp.dsr[0] >= range.start && dp.dsr[1] <= range.end) {\n\t\treturn [node];\n\t}\n\n\t// Cannot inspect template content subtree at a finer grained level\n\tif (WTUtils.isFirstEncapsulationWrapperNode(node)) {\n\t\treturn [node];\n\t}\n\n\t// Cannot inspect image subtree at a finer grained level\n\tvar typeOf = node.getAttribute('typeof') || '';\n\tif (/\\bmw:File(\\/|\\s|$)/.test(typeOf) && /^(FIGURE|SPAN)$/.test(node.nodeName)) {\n\t\treturn [node];\n\t}\n\n\t// We are in the target range -- examine children.\n\t// 1. Walk past nodes that are before our desired range.\n\t// 2. Collect nodes within our desired range.\n\t// 3. Stop walking once you move beyond the desired range.\n\tvar elts = [];\n\tvar offset = dp.dsr[0];\n\tvar c = node.firstChild;\n\twhile (c) {\n\t\tif (DOMUtils.isElt(c)) {\n\t\t\tdp = DOMDataUtils.getDataParsoid(c);\n\t\t\tvar dsr = dp.dsr;\n\t\t\tif (Util.isValidDSR(dsr)) {\n\t\t\t\tif (dsr[1] >= range.start) {\n\t\t\t\t\t// We have an overlap!\n\t\t\t\t\telts = elts.concat(findMatchingNodes(c, range));\n\t\t\t\t}\n\t\t\t\toffset = dp.dsr[1];\n\t\t\t} else {\n\t\t\t\t// SSS FIXME: This is defensive coding here.\n\t\t\t\t//\n\t\t\t\t// This should not happen really anymore.\n\t\t\t\t// DSR computation is fairly solid now and\n\t\t\t\t// shouldn't be leaving holes.\n\t\t\t\t//\n\t\t\t\t// If we see no errors in rt-testing runs,\n\t\t\t\t// I am going to rip this out.\n\n\t\t\t\tconsole.log(\"error/diff\", \"Bad dsr for \" + c.nodeName + \": \"\n\t\t\t\t\t+ c.outerHTML.slice(0, 50));\n\n\t\t\t\tif (dp.dsr && typeof (dsr[1]) === 'number') {\n\t\t\t\t\t// We can cope in this case\n\t\t\t\t\tif (dsr[1] >= range.start) {\n\t\t\t\t\t\t// Update dsr[0]\n\t\t\t\t\t\tdp.dsr[0] = offset;\n\n\t\t\t\t\t\t// We have an overlap!\n\t\t\t\t\t\telts = elts.concat(findMatchingNodes(c, range));\n\t\t\t\t\t}\n\t\t\t\t\toffset = dp.dsr[1];\n\t\t\t\t} else if (offset >= range.start) {\n\t\t\t\t\t// Swallow it wholesale rather than try\n\t\t\t\t\t// to find finer-grained matches in the subtree\n\t\t\t\t\telts.push(c);\n\n\t\t\t\t\t// offset will now be out-of-sync till we hit\n\t\t\t\t\t// another element with a valid DSR[1] value.\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tvar len = DOMUtils.isText(c) ? c.nodeValue.length : WTUtils.decodedCommentLength(c);\n\t\t\tif (offset + len >= range.start) {\n\t\t\t\t// We have an overlap!\n\t\t\t\telts.push(c);\n\t\t\t}\n\t\t\toffset += len;\n\t\t}\n\n\t\t// All done!\n\t\tif (offset > range.end) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// Skip over encapsulated content\n\t\tif (WTUtils.isFirstEncapsulationWrapperNode(c)) {\n\t\t\tc = WTUtils.skipOverEncapsulatedContent(c);\n\t\t} else {\n\t\t\tc = c.nextSibling;\n\t\t}\n\t}\n\n\treturn elts;\n};\n\nfunction stripTranscludedWhitespaceSpans(node) {\n\twhile (node) {\n\t\tconst sibling = node.nextSibling;\n\t\tif (DOMUtils.isElt(node)) {\n\t\t\tconst about = node.getAttribute('about');\n\t\t\tconst nTypeOf = node.getAttribute('typeof') || '';\n\n\t\t\t// remove whitespace spans that are first nodes of a transclusion,\n\t\t\t// have whitespace content, and transfer attributes to their sibling.\n\t\t\tif (node.nodeName === 'SPAN' &&\n\t\t\t\t/^\\s*$/.test(node.textContent) &&\n\t\t\t\t/mw:Transclusion/.test(nTypeOf) &&\n\t\t\t\tDOMUtils.isElt(sibling) &&\n\t\t\t\tsibling.getAttribute('about') === about\n\t\t\t) {\n\t\t\t\tvar sTypeOf = sibling.getAttribute('typeof') || '';\n\t\t\t\tif (sTypeOf) {\n\t\t\t\t\tsTypeOf = sTypeOf + ' ' + nTypeOf;\n\t\t\t\t} else {\n\t\t\t\t\tsTypeOf = nTypeOf;\n\t\t\t\t}\n\t\t\t\tsibling.setAttribute('typeof', sTypeOf);\n\t\t\t\tsibling.setAttribute('data-mw', node.getAttribute('data-mw'));\n\t\t\t\tsibling.setAttribute('data-parsoid', node.getAttribute('data-parsoid'));\n\n\t\t\t\tconst whitespace = node.ownerDocument.createTextNode(node.textContent);\n\t\t\t\tnode.parentNode.replaceChild(whitespace, node);\n\n\t\t\t\t// Skip transclusion nodes\n\t\t\t\tnode = sibling;\n\t\t\t\twhile (node && DOMUtils.isElt(node) && node.getAttribute('about') === about) {\n\t\t\t\t\tnode = node.nextSibling;\n\t\t\t\t}\n\t\t\t} else if (/mw:Transclusion/.test(nTypeOf)) {\n\t\t\t\tnode = WTUtils.skipOverEncapsulatedContent(node);\n\t\t\t\t// No skipping to nextSibling here!\n\t\t\t} else if (node.firstChild) {\n\t\t\t\tstripTranscludedWhitespaceSpans(node.firstChild);\n\t\t\t\tnode = sibling;\n\t\t\t} else {\n\t\t\t\tnode = sibling;\n\t\t\t}\n\t\t} else {\n\t\t\tnode = sibling;\n\t\t}\n\t}\n}\n\nvar getMatchingHTML = function(body, offsetRange, nlDiffs) {\n\t// If the diff context straddles a template boundary (*) and if\n\t// the HTML context includes the template content in only one\n\t// the new/old DOMs, we can falsely flag this as a semantic\n\t// diff. To improve the possibility of including the template\n\t// content in both DOMs, expand range at both ends by 1 char.\n\t//\n\t// (*) This happens because our P-wrapping code occasionally\n\t//     swallows newlines into template context.\n\t// See https://phabricator.wikimedia.org/T89628\n\tif (nlDiffs) {\n\t\toffsetRange.start -= 1;\n\t\toffsetRange.end += 1;\n\t}\n\n\tvar html = '';\n\tvar out = findMatchingNodes(body, offsetRange);\n\tfor (var i = 0; i < out.length; i++) {\n\t\t// node need not be an element always!\n\t\tconst node = out[i];\n\t\tDOMDataUtils.visitAndStoreDataAttribs(node);\n\t\thtml += ContentUtils.toXML(node, { smartQuote: false });\n\t\tDOMDataUtils.visitAndLoadDataAttribs(node);\n\t}\n\thtml = TestUtils.normalizeOut(html);\n\n\t// Normalize away <br/>'s added by Parsoid because of newlines in wikitext.\n\t// Do this always, not just when nlDiffs is true, because newline diffs\n\t// can show up at extremities of other wt diffs.\n\treturn html.replace(/<p>\\s*<br\\s*\\/?>\\s*/g, '<p>').replace(/<p><\\/p>/g, '').replace(/(^\\s+|\\s+$)/g, '');\n};\n\n/* This doesn't try to do a really thorough job of normalization and misses a number\n * of scenarios, for example, anywhere where sol-transparent markup like comments,\n * noinclude, category links, etc. are present.\n *\n * On the flip side, it can occasionally do incorrect normalization when this markup\n * is present in extension blocks (nowiki, syntaxhighlight, etc.) where this text\n * is not really interpreted as wikitext.\n */\nfunction normalizeWikitext(wt, opts) {\n\tif (opts.preDiff) {\n\t\t// Whitespace in ordered, unordered, definition lists\n\t\t// Whitespace in first table cell/header, row, and caption\n\t\twt = wt.replace(/^([*#:;]|\\|[-+|]?|!!?)[ \\t]*(.*?)[ \\t]*$/mg, \"$1$1\");\n\n\t\t// Whitespace in headings\n\t\twt = wt.replace(/^(=+)[ \\t]*([^\\n]*?)[ \\t]*(=+)[ \\t]*$/mg, \"$1$2$3\");\n\t}\n\n\tif (opts.newlines) {\n\t\t// Normalize newlines before/after headings\n\t\twt = wt.replace(/\\n*(\\n=[^\\n]*=$\\n)\\n*/mg, \"$1\");\n\n\t\t// Normalize newlines before lists\n\t\twt = wt.replace(/(^[^*][^\\n]*$\\n)\\n+([*])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[^#][^\\n]*$\\n)\\n+([#])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[^:][^\\n]*$\\n)\\n+([:])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[^;][^\\n]*$\\n)\\n+([;])/mg, \"$1$2\");\n\n\t\t// Normalize newlines after lists\n\t\twt = wt.replace(/(^[*][^\\n]*$\\n)\\n+([^*])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[#][^\\n]*$\\n)\\n+([^#])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[:][^\\n]*$\\n)\\n+([^:])/mg, \"$1$2\");\n\t\twt = wt.replace(/(^[;][^\\n]*$\\n)\\n+([^;])/mg, \"$1$2\");\n\n\t\t// Normalize newlines before/after tables\n\t\twt = wt.replace(/\\n+(\\n{\\|)/mg, \"$1\");\n\t\twt = wt.replace(/(\\|}\\n)\\n+/mg, \"$1\");\n\n\t\t// Strip leading & trailing newlines\n\t\twt = wt.replace(/^\\n+|\\n$/, '');\n\t}\n\n\tif (opts.postDiff) {\n\t\t// Ignore leading tabs vs. leading spaces\n\t\twt = wt.replace(/^\\t/, ' ');\n\t\twt = wt.replace(/\\n\\t/g, '\\n ');\n\t\t// Normalize multiple spaces to single space\n\t\twt = wt.replace(/ +/g, ' ');\n\t\t// Ignore capitalization of tags and void tag indications\n\t\twt = wt.replace(/<(\\/?)([^ >\\/]+)((?:[^>\\/]|\\/(?!>))*)\\/?>/g,\n\t\t\tfunction(match, close, name, remaining) {\n\t\t\t\treturn '<' + close + name.toLowerCase() +\n\t\t\t\t\tremaining.replace(/ $/, '') + '>';\n\t\t\t});\n\t\t// Ignore whitespace in table cell attributes\n\t\twt = wt.replace(/(^|\\n|\\|(?=\\|)|!(?=!))(\\{\\||\\|[\\-+]*|!) *([^|\\n]*?) *(?=[|\\n]|$)/g, '$1$2$3');\n\t\t// Ignore trailing semicolons and spaces in style attributes\n\t\twt = wt.replace(/style\\s*=\\s*\"[^\"]+\"/g, function(match) {\n\t\t\treturn match.replace(/\\s|;(?=\")/g, '');\n\t\t});\n\t\t// Strip double-quotes\n\t\twt = wt.replace(/\"([^\"]*?)\"/g, '$1');\n\t\t// Ignore implicit </small> and </center> in table cells or the end\n\t\t// of the wting for now\n\t\twt = wt.replace(/(^|\\n)<\\/(?:small|center)>(?=\\n[|!]|\\n?$)/g, '');\n\t\twt = wt.replace(/([|!].*?)<\\/(?:small|center)>(?=\\n[|!]|\\n?$)/gi, '$1');\n\t}\n\n\treturn wt;\n}\n\n// Get diff slices from offsets\nvar formatDiff = function(oldWt, newWt, offset, context) {\n\treturn [\n\t\t'------',\n\t\toldWt.slice(offset[0].start - context, offset[0].start).blue +\n\t\toldWt.slice(offset[0].start, offset[0].end).green +\n\t\toldWt.slice(offset[0].end, offset[0].end + context).blue,\n\t\t'++++++',\n\t\tnewWt.slice(offset[1].start - context, offset[1].start).blue +\n\t\tnewWt.slice(offset[1].start, offset[1].end).red +\n\t\tnewWt.slice(offset[1].end, offset[1].end + context).blue,\n\t].join('\\n');\n};\n\nfunction stripElementIds(node) {\n\twhile (node) {\n\t\tif (DOMUtils.isElt(node)) {\n\t\t\tvar id = node.getAttribute('id') || '';\n\t\t\tif (/^mw[\\w-]{2,}$/.test(id)) {\n\t\t\t\tnode.removeAttribute('id');\n\t\t\t}\n\t\t\tif (node.firstChild) {\n\t\t\t\tstripElementIds(node.firstChild);\n\t\t\t}\n\t\t}\n\t\tnode = node.nextSibling;\n\t}\n}\n\nfunction genSyntacticDiffs(data) {\n\tvar results = [];\n\tvar diff = Diff.diffLines(data.oldWt, data.newWt);\n\tvar offsets = Diff.convertDiffToOffsetPairs(diff, data.oldLineLengths, data.newLineLengths);\n\tfor (var i = 0; i < offsets.length; i++) {\n\t\tvar offset = offsets[i];\n\t\tresults.push({\n\t\t\ttype: 'skip',\n\t\t\toffset: offset,\n\t\t\twtDiff: formatDiff(data.oldWt, data.newWt, offset, 0),\n\t\t});\n\t}\n\treturn results;\n}\n\nfunction normalizeDocumentHTML(body) {\n\t// Strip whitspace spans that are first elements of a transclusion\n\tstripTranscludedWhitespaceSpans(body);\n\n\t// Strip 'mw..' ids from the DOMs. This matters for 2 scenarios:\n\t// * reduces noise in visual diffs\n\t// * all other things being equal after normalization, we don't\n\t//   assume DOMs are different simply because ids are different\n\tstripElementIds(body);\n\n\t// Strip section tags from the DOMs\n\tContentUtils.stripUnnecessaryWrappersAndFallbackIds(body);\n}\n\nvar checkIfSignificant = function(offsets, data) {\n\tvar oldWt = data.oldWt;\n\tvar newWt = data.newWt;\n\n\tconst dummyEnv = new MockEnv({}, null);\n\n\tvar oldBody = dummyEnv.createDocument(data.oldHTML.body).body;\n\tvar newBody = dummyEnv.createDocument(data.newHTML.body).body;\n\n\t// Merge pagebundles so that HTML nodes can be compared and diff'ed.\n\tDOMDataUtils.applyPageBundle(oldBody.ownerDocument, {\n\t\tparsoid: data.oldDp.body,\n\t\tmw: data.oldMw && data.oldMw.body,\n\t});\n\tDOMDataUtils.applyPageBundle(newBody.ownerDocument, {\n\t\tparsoid: data.newDp.body,\n\t\tmw: data.newMw && data.newMw.body,\n\t});\n\n\tnormalizeDocumentHTML(oldBody.ownerDocument.body);\n\tnormalizeDocumentHTML(newBody.ownerDocument.body);\n\n\tvar i, offset;\n\tvar results = [];\n\t// Use the full tests for fostered content.\n\t// Fostered/misnested content => semantic diffs.\n\tif (!/(\"|&quot;)(fostered|misnested)(\"|&quot;)\\s*:\\s*true\\b/.test(oldBody.outerHTML)) {\n\t\t// Quick test for no semantic diffs\n\t\t// If parsoid-normalized HTML for old and new wikitext is identical,\n\t\t// the wt-diffs are purely syntactic.\n\t\t//\n\t\t// FIXME: abstract to ensure same opts are used for parsoidPost and normalizeOut\n\t\tconst normOpts = {\n\t\t\tparsoidOnly: true,\n\t\t\t// Eliminate spurious semantic errors that may arise because\n\t\t\t// of the normalization done to new html before it got serialized.\n\t\t\t// For example,\n\t\t\t//   \"== ==\" will parse to \"<h2><h2>\" and then serialize to \"\"\n\t\t\t//\n\t\t\t// FIXME: Normally we would only run this on the old DOM since that is\n\t\t\t// sufficient. BUT, for links with trailing whitespace like [[Foo ]],\n\t\t\t// wt2wt has special handling to use syntactic variations from data-parsoid\n\t\t\t// independent of what the DOM iself says. Try wt2wt on that wikitext to verify.\n\t\t\t// But, till such time we strip data-parsoid based syntactic variations in\n\t\t\t// link handlers, run DOM normalizations on both old and new HTML\n\t\t\t// so that we don't report spurious semantic diffs because of this.\n\t\t\t// In any case, DOM normalizations are idempotent and so at best, rerunning\n\t\t\t// DOM normalization is wasteful and not harmful.\n\t\t\thackyNormalize: true\n\t\t};\n\t\tconst normalizedOld = TestUtils.normalizeOut(oldBody, normOpts);\n\t\tconst normalizedNew = TestUtils.normalizeOut(newBody, normOpts);\n\t\tif (normalizedOld === normalizedNew) {\n\t\t\treturn genSyntacticDiffs(data);\n\t\t} else {\n\t\t\t// Uncomment to log the cause of the failure.  This is often useful\n\t\t\t// for determining the root of non-determinism in rt.  See T151474\n\t\t\t// console.log(Diff.diffLines(normalizedOld, normalizedNew));\n\t\t}\n\t}\n\n\t/*\n\tconsole.log(\"---------OLD DOC HTML---------\\n\" + oldBody.ownerDocument.body.innerHTML);\n\tconsole.log(\"---------NEW DOC HTML---------\\n\" + newBody.ownerDocument.body.innerHTML);\n\t*/\n\n\t// FIXME: In this code path below, the returned diffs might\n\t// underreport syntactic diffs since these are based on\n\t// diffs on normalized wikitext. Unclear how to tackle this.\n\n\t// Do this after the quick test above because in `parsoidOnly`\n\t// normalization, data-mw is not stripped.\n\tDOMDataUtils.visitAndLoadDataAttribs(oldBody);\n\tDOMDataUtils.visitAndLoadDataAttribs(newBody);\n\n\t// Now, proceed with full blown diffs\n\tfor (i = 0; i < offsets.length; i++) {\n\t\toffset = offsets[i];\n\t\tvar thisResult = { offset: offset };\n\n\t\t// Default: syntactic diff + no diff context\n\t\tthisResult.type = 'skip';\n\t\tthisResult.wtDiff = formatDiff(oldWt, newWt, offset, 0);\n\n\t\t// Is this a newline separator diff?\n\t\tvar oldStr = oldWt.slice(offset[0].start, offset[0].end);\n\t\tvar newStr = newWt.slice(offset[1].start, offset[1].end);\n\t\tvar nlDiffs = /^\\s*$/.test(oldStr) && /^\\s*$/.test(newStr)\n\t\t\t&& (/\\n/.test(oldStr) || /\\n/.test(newStr));\n\n\t\t// Check if this is really a semantic diff\n\t\tvar oldHTML = getMatchingHTML(oldBody, offset[0], nlDiffs);\n\t\tvar newHTML = getMatchingHTML(newBody, offset[1], nlDiffs);\n\t\tvar diff = Diff.patchDiff(oldHTML, newHTML);\n\t\tif (diff !== null) {\n\t\t\t// Normalize wts to check if we really have a semantic diff\n\t\t\tvar wt1 = normalizeWikitext(oldWt.slice(offset[0].start, offset[0].end), { newlines: true, postDiff: true });\n\t\t\tvar wt2 = normalizeWikitext(newWt.slice(offset[1].start, offset[1].end), { newlines: true, postDiff: true });\n\t\t\tif (wt1 !== wt2) {\n\t\t\t\t// Syntatic diff + provide context for semantic diffs\n\t\t\t\tthisResult.type = 'fail';\n\t\t\t\tthisResult.wtDiff = formatDiff(oldWt, newWt, offset, 25);\n\n\t\t\t\t// Don't clog the rt-test server db with humongous diffs\n\t\t\t\tif (diff.length > 1000) {\n\t\t\t\t\tdiff = diff.slice(0, 1000) + \"-- TRUNCATED TO 1000 chars --\";\n\t\t\t\t}\n\t\t\t\tthisResult.htmlDiff = diff;\n\t\t\t}\n\t\t}\n\t\tresults.push(thisResult);\n\t}\n\n\treturn results;\n};\n\nvar UA = 'Roundtrip-Test';\n\nvar parsoidPost = Promise.async(function *(profile, options) {\n\tvar httpOptions = {\n\t\tmethod: 'POST',\n\t\tbody: options.data,\n\t\theaders: {\n\t\t\t'User-Agent': UA,\n\t\t},\n\t};\n\t// For compatibility with Parsoid/PHP service\n\thttpOptions.body.offsetType = 'ucs2';\n\n\tvar uri = options.uri + 'transform/';\n\tif (options.html2wt) {\n\t\turi += 'pagebundle/to/wikitext/' + options.title;\n\t\tif (options.oldid) {\n\t\t\turi += '/' + options.oldid;\n\t\t}\n\t\t// We want to encode the request but *not* decode the response.\n\t\thttpOptions.body = JSON.stringify(httpOptions.body);\n\t\thttpOptions.headers['Content-Type'] = 'application/json';\n\t} else {  // wt2html\n\t\turi += 'wikitext/to/pagebundle/' + options.title;\n\t\tif (options.oldid) {\n\t\t\turi += '/' + options.oldid;\n\t\t}\n\t\thttpOptions.headers.Accept = 'application/json; charset=utf-8; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/' + options.outputContentVersion + '\"';\n\t\t// setting json here encodes the request *and* decodes the response.\n\t\thttpOptions.json = true;\n\t}\n\thttpOptions.uri = uri;\n\thttpOptions.proxy = options.proxy;\n\n\tvar result = yield issueRequest(httpOptions);\n\tvar body = result[1];\n\n\t// FIXME: Parse time was removed from profiling when we stopped\n\t// sending the x-parsoid-performance header.\n\tif (options.recordSizes) {\n\t\tvar pre = '';\n\t\tif (options.profilePrefix) {\n\t\t\tpre += options.profilePrefix + ':';\n\t\t}\n\t\tvar str;\n\n\t\t// Detect standard error response\n\t\tif ( typeof body === 'object' && body.httpCode >= 400 ) {\n\t\t\tthrow new Error('Received error: ' + body.reason);\n\t\t}\n\n\t\tif (options.html2wt) {\n\t\t\tpre += 'wt:';\n\t\t\tstr = body;\n\t\t} else {\n\t\t\tpre += 'html:';\n\t\t\tstr = body.html.body;\n\t\t}\n\n\t\tprofile.size[pre + 'raw'] = str.length;\n\t\t// Compress to record the gzipped size\n\t\tvar gzippedbuf = yield zlib.gzip(str);\n\t\tprofile.size[pre + 'gzip'] = gzippedbuf.length;\n\t}\n\treturn body;\n});\n\nfunction genLineLengths(str) {\n\treturn str.split(/^/m).map(function(l) {\n\t\treturn l.length;\n\t});\n}\n\nvar roundTripDiff = Promise.async(function *(profile, parsoidOptions, data) {\n\tvar normOpts = { preDiff: true, newlines: true };\n\tdata.oldLineLengths = genLineLengths(data.oldWt);\n\tdata.newLineLengths = genLineLengths(data.newWt);\n\n\t// Newline normalization to see if we can get to identical wt.\n\tvar wt1 = normalizeWikitext(data.oldWt, normOpts);\n\tvar wt2 = normalizeWikitext(data.newWt, normOpts);\n\tif (wt1 === wt2) {\n\t\treturn genSyntacticDiffs(data);\n\t}\n\n\t// Do another diff without normalizations\n\t// More conservative normalization this time around\n\tnormOpts.newlines = false;\n\twt1 = normalizeWikitext(data.oldWt, normOpts);\n\twt2 = normalizeWikitext(data.newWt, normOpts);\n\tvar diff = Diff.diffLines(wt1, wt2);\n\tvar offsets = Diff.convertDiffToOffsetPairs(diff, data.oldLineLengths, data.newLineLengths);\n\tif (!offsets.length) {\n\t\t// FIXME: Can this really happen??\n\t\treturn genSyntacticDiffs(data);\n\t}\n\n\tvar contentmodel = data.contentmodel || 'wikitext';\n\tvar options = Object.assign({\n\t\twt2html: true,\n\t\tdata: { wikitext: data.newWt, contentmodel: contentmodel },\n\t}, parsoidOptions);\n\tvar body = yield parsoidPost(profile, options);\n\tdata.newHTML = body.html;\n\tdata.newDp = body['data-parsoid'];\n\tdata.newMw = body['data-mw'];\n\treturn checkIfSignificant(offsets, data);\n});\n\n// Custom httpClient global variable that can be set by ci tests\nvar httpClient;\n\nvar issueRequest = function(httpOptions) {\n\tif (httpClient) {\n\t\treturn httpClient.request(httpOptions);\n\t} else {\n\t\treturn ScriptUtils.retryingHTTPRequest(MAX_RETRIES, httpOptions);\n\t}\n};\n\n// Returns a Promise for a object containing a formatted string and an\n// exitCode.\nvar runTests = Promise.async(function *(title, options, formatter) {\n\t// Only support lookups for WMF domains.  At some point we should rid\n\t// ourselves of prefixes in this file entirely, but that'll take some\n\t// coordination in rt.\n\tvar parsoidConfig = new ParsoidConfig(null, { loadWMF: true });\n\n\tvar domain = options.domain;\n\tvar prefix = options.prefix;\n\n\tif (options.httpClient) {\n\t\thttpClient = options.httpClient;\n\t}\n\n\t// Preserve the default, but only if neither was provided.\n\tif (!prefix && !domain) {\n\t\tdomain = 'en.wikipedia.org';\n\t}\n\n\tif (domain && prefix) {\n\t\t// All good.\n\t} else if (!domain && prefix) {\n\t\t// Get the domain from the mw api map.\n\t\tif (parsoidConfig.mwApiMap.has(prefix)) {\n\t\t\tdomain = parsoidConfig.mwApiMap.get(prefix).domain;\n\t\t} else {\n\t\t\tthrow new Error('Couldn\\'t find the domain for prefix: ' + prefix);\n\t\t}\n\t} else if (!prefix && domain) {\n\t\t// Get the prefix from the reverse mw api map.\n\t\tprefix = parsoidConfig.getPrefixFor(domain);\n\t\tif (!prefix) {\n\t\t\t// Bogus, but `prefix` is only used for reporting.\n\t\t\tprefix = domain;\n\t\t}\n\t} else {\n\t\t// Should be unreachable.\n\t\tthrow new Error('No domain or prefix provided.');\n\t}\n\n\tconst uriOpts = options.parsoidURLOpts;\n\tlet uri = uriOpts.baseUrl;\n\tlet proxy;\n\tif (uriOpts.proxy) {\n\t\tproxy = uriOpts.proxy.host;\n\t\tif (uriOpts.proxy.port) {\n\t\t\tproxy += \":\" + uriOpts.proxy.port;\n\t\t}\n\t\t// Special support for the WMF cluster\n\t\turi = uri.replace(/DOMAIN/, domain);\n\t}\n\n\t// make sure the Parsoid URI ends on /\n\tif (!/\\/$/.test(uri)) {\n\t\turi += '/';\n\t}\n\tvar parsoidOptions = {\n\t\turi: uri + domain + '/v3/',\n\t\tproxy: proxy,\n\t\ttitle: encodeURIComponent(title),\n\t\toutputContentVersion: options.outputContentVersion || defaultContentVersion,\n\t};\n\tvar uri2 = parsoidOptions.uri + 'page/wikitext/' + parsoidOptions.title;\n\tif (options.oldid) {\n\t\turi2 += '/' + options.oldid;\n\t}\n\n\tvar profile = { time: { total: 0, start: 0 }, size: {} };\n\tvar data = {};\n\tvar error;\n\tvar exitCode;\n\ttry {\n\t\tvar opts;\n\t\tvar req = yield issueRequest({\n\t\t\tmethod: 'GET',\n\t\t\turi: uri2,\n\t\t\tproxy: proxy,\n\t\t\theaders: {\n\t\t\t\t'User-Agent': UA,\n\t\t\t},\n\t\t});\n\t\tprofile.time.start = JSUtils.startTime();\n\t\t// We may have been redirected to the latest revision.  Record the\n\t\t// oldid for later use in selser.\n\t\tdata.oldid = req[0].request.path.replace(/^(.*)\\//, '');\n\t\tdata.oldWt = req[1];\n\t\tdata.contentmodel = req[0].headers['x-contentmodel'] || 'wikitext';\n\t\t// First, fetch the HTML for the requested page's wikitext\n\t\topts = Object.assign({\n\t\t\twt2html: true,\n\t\t\trecordSizes: true,\n\t\t\tdata: { wikitext: data.oldWt, contentmodel: data.contentmodel },\n\t\t}, parsoidOptions);\n\t\tvar body = yield parsoidPost(profile, opts);\n\n\t\t// Check for wikitext redirects\n\t\tconst redirectMatch = body.html.body.match(/<link rel=\"mw:PageProp\\/redirect\" href=\"([^\"]*)\"/);\n\t\tif (redirectMatch) {\n\t\t\tconst target = Util.decodeURIComponent(entities.decodeHTML5(redirectMatch[1].replace(/^(\\.\\/)?/, '')));\n\t\t\t// Log this so we can collect these and update the database titles\n\t\t\tconsole.error(`REDIRECT: ${ prefix }:${ title.replace(/\"/g, '\\\\\"') } -> ${ prefix }:${ target.replace(/\"/g, '\\\\\"') }`);\n\t\t\treturn yield runTests(target, options, formatter);\n\t\t}\n\n\t\tdata.oldHTML = body.html;\n\t\tdata.oldDp = body['data-parsoid'];\n\t\tdata.oldMw = body['data-mw'];\n\t\t// Now, request the wikitext for the obtained HTML\n\t\topts = Object.assign({\n\t\t\thtml2wt: true,\n\t\t\trecordSizes: true,\n\t\t\tdata: {\n\t\t\t\thtml: data.oldHTML.body,\n\t\t\t\tcontentmodel: data.contentmodel,\n\t\t\t\toriginal: {\n\t\t\t\t\t'data-parsoid': data.oldDp,\n\t\t\t\t\t'data-mw': data.oldMw,\n\t\t\t\t},\n\t\t\t},\n\t\t}, parsoidOptions);\n\t\tdata.newWt = yield parsoidPost(profile, opts);\n\t\tdata.diffs = yield roundTripDiff(profile, parsoidOptions, data);\n\t\t// Once we have the diffs between the round-tripped wt,\n\t\t// to test rt selser we need to modify the HTML and request\n\t\t// the wt again to compare with selser, and then concat the\n\t\t// resulting diffs to the ones we got from basic rt\n\t\tvar newDocument = DOMUtils.parseHTML(data.oldHTML.body);\n\t\tvar newNode = newDocument.createComment('rtSelserEditTestComment');\n\t\tnewDocument.body.appendChild(newNode);\n\t\topts = Object.assign({\n\t\t\thtml2wt: true,\n\t\t\tuseSelser: true,\n\t\t\toldid: data.oldid,\n\t\t\tdata: {\n\t\t\t\thtml: newDocument.outerHTML,\n\t\t\t\tcontentmodel: data.contentmodel,\n\t\t\t\toriginal: {\n\t\t\t\t\t'data-parsoid': data.oldDp,\n\t\t\t\t\t'data-mw': data.oldMw,\n\t\t\t\t\twikitext: { body: data.oldWt },\n\t\t\t\t\thtml: data.oldHTML,\n\t\t\t\t},\n\t\t\t},\n\t\t\tprofilePrefix: 'selser',\n\t\t}, parsoidOptions);\n\t\tvar out = yield parsoidPost(profile, opts);\n\t\t// Finish the total time now\n\t\t// FIXME: Is the right place to end it?\n\t\tprofile.time.total = JSUtils.elapsedTime(profile.time.start);\n\t\t// Remove the selser trigger comment\n\t\tdata.newWt = out.replace(/<!--rtSelserEditTestComment-->\\n*$/, '');\n\t\tvar selserDiffs = yield roundTripDiff(profile, parsoidOptions, data);\n\t\tselserDiffs.forEach(function(diff) {\n\t\t\tdiff.selser = true;\n\t\t});\n\t\tif (selserDiffs.length) {\n\t\t\tdata.diffs = data.diffs.concat(selserDiffs);\n\t\t\texitCode = 1;\n\t\t} else {\n\t\t\texitCode = 0;\n\t\t}\n\t} catch (e) {\n\t\terror = e;\n\t\texitCode = 1;\n\t}\n\n\tvar output = formatter(error, prefix, title, data.diffs, profile);\n\n\treturn {\n\t\toutput: output,\n\t\texitCode: exitCode\n\t};\n});\n\n\nif (require.main === module) {\n\tvar standardOpts = {\n\t\txml: {\n\t\t\tdescription: 'Use xml callback',\n\t\t\tboolean: true,\n\t\t\tdefault: false,\n\t\t},\n\t\tprefix: {\n\t\t\tdescription: 'Deprecated.  Please provide a domain.',\n\t\t\tboolean: false,\n\t\t\tdefault: '',\n\t\t},\n\t\tdomain: {\n\t\t\tdescription: 'Which wiki to use; e.g. \"en.wikipedia.org\" for' +\n\t\t\t\t' English wikipedia',\n\t\t\tboolean: false,\n\t\t\tdefault: '',  // Add a default when `prefix` is removed.\n\t\t},\n\t\toldid: {\n\t\t\tdescription: 'Optional oldid of the given page. If not given,' +\n\t\t\t\t' will use the latest revision.',\n\t\t\tboolean: false,\n\t\t\tdefault: null,\n\t\t},\n\t\tparsoidURL: {\n\t\t\tdescription: 'The URL for the Parsoid API',\n\t\t\tboolean: false,\n\t\t\tdefault: '',\n\t\t},\n\t\tproxyURL: {\n\t\t\tdescription: 'URL (with protocol and port, if any) for the proxy fronting Parsoid',\n\t\t\tboolean: false,\n\t\t\tdefault: null,\n\t\t},\n\t\tapiURL: {\n\t\t\tdescription: 'http path to remote API,' +\n\t\t\t\t' e.g. http://en.wikipedia.org/w/api.php',\n\t\t\tboolean: false,\n\t\t\tdefault: '',\n\t\t},\n\t\toutputContentVersion: {\n\t\t\tdescription: 'The acceptable content version.',\n\t\t\tboolean: false,\n\t\t\tdefault: defaultContentVersion,\n\t\t},\n\t\tcheck: {\n\t\t\tdescription: 'Exit with non-zero exit code if differences found using selser',\n\t\t\tboolean: true,\n\t\t\tdefault: false,\n\t\t\talias: 'c',\n\t\t},\n\t};\n\n\tPromise.async(function *() {\n\t\tvar opts = yargs\n\t\t.usage(\n\t\t\t'Usage: $0 [options] <page-title> \\n' +\n\t\t\t'The page title should be the \"true title\",' +\n\t\t\t'i.e., without any url encoding which might be necessary if it appeared in wikitext.' +\n\t\t\t'\\n\\n'\n\t\t)\n\t\t.options(standardOpts)\n\t\t.strict();\n\n\t\tvar argv = opts.argv;\n\t\tif (!argv._.length) {\n\t\t\treturn opts.showHelp();\n\t\t}\n\t\tvar title = String(argv._[0]);\n\n\t\tif (!argv.parsoidURL) {\n\t\t\tthrow new Error('No parsoidURL provided!');\n\t\t}\n\t\targv.parsoidURLOpts = { baseUrl: argv.parsoidURL };\n\t\tif (argv.proxyURL) {\n\t\t\targv.parsoidURLOpts.proxy = { host: argv.proxyURL };\n\t\t}\n\t\tvar formatter = ScriptUtils.booleanOption(argv.xml) ? xmlFormat : plainFormat;\n\t\tvar r = yield runTests(title, argv, formatter);\n\t\tconsole.log(r.output);\n\t\tif (argv.check) {\n\t\t\tprocess.exit(r.exitCode);\n\t\t}\n\t})().done();\n} else if (typeof module === 'object') {\n\tmodule.exports.runTests = runTests;\n\tmodule.exports.jsonFormat = jsonFormat;\n\tmodule.exports.plainFormat = plainFormat;\n\tmodule.exports.xmlFormat = xmlFormat;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/composer.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/core-upgrade.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/extension.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/extension/restRoutes.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/config/ParsoidConfig.js","messages":[{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":157,"column":19,"nodeType":"CallExpression","endLine":157,"endColumn":49},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found statSync from package \"fs\" with non literal argument at index 0","line":630,"column":8,"nodeType":"CallExpression","endLine":630,"endColumn":25},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readdirSync from package \"fs\" with non literal argument at index 0","line":636,"column":14,"nodeType":"CallExpression","endLine":636,"endColumn":34},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found statSync from package \"fs\" with non literal argument at index 0","line":643,"column":9,"nodeType":"CallExpression","endLine":643,"endColumn":23},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":656,"column":12,"nodeType":"CallExpression","endLine":656,"endColumn":31}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Parsoid-specific configuration.\n * This is immutable after initialization.\n *\n * @module\n */\n\n'use strict';\n\nrequire('../../core-upgrade.js');\n\nvar fs = require('fs');\nvar path = require('path');\nvar url = require('url');\n\nvar Util = require('../utils/Util.js').Util;\nvar JSUtils = require('../utils/jsutils.js').JSUtils;\nvar wmfSiteMatrix = require('../../tools/data/wmf.sitematrix.json').sitematrix;\n\n/*\n * @property {Object} CONFIG_DEFAULTS\n *   Timeout values for various things. All values in ms.\n * @private\n */\nvar CONFIG_DEFAULTS = Object.freeze({\n\ttimeouts: {\n\t\t// How long does a request have to generate a response?\n\t\trequest: 4 * 60 * 1000,\n\n\t\t// These are timeouts for different api endpoints to the MediaWiki API\n\t\tmwApi: {\n\t\t\t// action=expandtemplates\n\t\t\tpreprocessor: 30 * 1000,\n\t\t\t// action=parse\n\t\t\textParse: 30 * 1000,\n\t\t\t// action=templateData\n\t\t\ttemplateData: 30 * 1000,\n\t\t\t// action=parsoid-batch\n\t\t\tbatch: 60 * 1000,\n\t\t\t// action=query&prop=revisions\n\t\t\tsrcFetch: 40 * 1000,\n\t\t\t// action=query&prop=imageinfo\n\t\t\timgInfo: 40 * 1000,\n\t\t\t// action=query&meta=siteinfo\n\t\t\tconfigInfo: 40 * 1000,\n\t\t\t// action=record-lint\n\t\t\tlint: 30 * 1000,\n\t\t\t// Connection timeout setting for the http agent\n\t\t\tconnect: 5 * 1000,\n\t\t},\n\t},\n\n\t// Max concurrency level for accessing the MediaWiki API\n\tmaxSockets: 15,\n\n\t// Multiple of cpu_workers number requests to queue before rejecting\n\tmaxConcurrentCalls: 5,\n\n\tretries: {\n\t\tmwApi: {\n\t\t\tall: 1,\n\t\t\t// No retrying config requests\n\t\t\t// FIXME: but why? seems like 1 retry is not a bad idea\n\t\t\tconfigInfo: 0,\n\t\t},\n\t},\n\n\t// Somewhat arbitrary numbers for starters.\n\t// If these limits are breached, we return a http 413 (Payload too large)\n\tlimits: {\n\t\twt2html: {\n\t\t\t// We won't handle pages beyond this size\n\t\t\tmaxWikitextSize: 1000000, // 1M\n\n\t\t\t// Max list items per page\n\t\t\tmaxListItems: 30000,\n\n\t\t\t// Max table cells per page\n\t\t\tmaxTableCells: 30000,\n\n\t\t\t// Max transclusions per page\n\t\t\tmaxTransclusions: 10000,\n\n\t\t\t// DISABLED for now\n\t\t\t// Max images per page\n\t\t\tmaxImages: 1000,\n\n\t\t\t// Max top-level token size\n\t\t\tmaxTokens: 1000000, // 1M\n\t\t},\n\t\thtml2wt: {\n\t\t\t// We refuse to serialize HTML strings bigger than this\n\t\t\tmaxHTMLSize: 10000000,  // 10M\n\t\t},\n\t},\n\n\tlinter: {\n\t\t// Whether to send lint errors to the MW API\n\t\t// Requires the MW Linter extension to be installed and configured.\n\t\tsendAPI: false,\n\n\t\t// Ratio at which to sample linter errors, per page.\n\t\t// This is deterministic and based on page_id.\n\t\tapiSampling: 1,\n\t},\n});\n\nvar prepareLog = function(logData) {\n\tvar log = Object.assign({ logType: logData.logType }, logData.locationData);\n\tvar flat = logData.flatLogObject();\n\tObject.keys(flat).forEach(function(k) {\n\t\t// Be sure we don't have a `type` field here since logstash\n\t\t// treats that as magical.  We created a special `location`\n\t\t// field above and bunyan will add a `level` field (from the\n\t\t// contents of our `type` field) when we call the specific\n\t\t// logger returned by `_getBunyanLogger`.\n\t\tif (/^(type|location|level)$/.test(k)) {\n\t\t\treturn;\n\t\t}\n\t\tlog[k] = flat[k];\n\t});\n\treturn log;\n};\n\n/**\n * Global Parsoid configuration object. Will hold things like debug/trace\n * options, mw api map, and local settings like fetchTemplates.\n *\n * @class\n * @param {Object} localSettings The localSettings object, probably from a localsettings.js file.\n * @param {Function} localSettings.setup The local settings setup function, which sets up our local configuration.\n * @param {ParsoidConfig} localSettings.setup.opts The setup function is passed the object under construction so it can extend the config directly.\n * @param {Object} options Any options we want to set over the defaults. See the class properties for more information.\n */\nfunction ParsoidConfig(localSettings, options) {\n\toptions = options || {};\n\n\tthis.mwApiMap = new Map();\n\tthis.reverseMwApiMap = new Map();\n\tObject.keys(CONFIG_DEFAULTS).forEach(function(k) {\n\t\tthis[k] = Util.clone(CONFIG_DEFAULTS[k]);\n\t}, this);\n\tthis._uniq = 0;\n\n\t// Don't freak out!\n\t// This happily overwrites inherited properties.\n\tObject.assign(this, options);\n\t// Trace, debug, and dump flags should be sets, but options might\n\t// include them as arrays.\n\t['traceFlags', 'debugFlags', 'dumpFlags'].forEach(function(f) {\n\t\tif (Array.isArray(this[f])) {\n\t\t\tthis[f] = new Set(this[f]);\n\t\t}\n\t}, this);\n\n\tif (!localSettings && options.localsettings) {\n\t\tlocalSettings = require(options.localsettings);\n\t}\n\n\tif (localSettings && localSettings.setup) {\n\t\tlocalSettings.setup(this);\n\t}\n\n\t// Call setMwApi for each specified API.\n\tif (Array.isArray(this.mwApis)) {\n\t\tthis.mwApis.forEach(function(api) {\n\t\t\tthis.setMwApi(api);\n\t\t}, this);\n\t}\n\n\tif (this.loadWMF) {\n\t\tthis.loadWMFApiMap();\n\t}\n\n\t// Make sure all critical required properties are present\n\tthis._sanitizeIt();\n\n\t// ParsoidConfig is used across requests. Freeze it to avoid mutation.\n\tvar ignoreFields = {\n\t\tmetrics: true,\n\t\tloggerBackend: true,\n\t\tmwApiMap: true,\n\t\treverseMwApiMap: true\n\t};\n\tJSUtils.deepFreezeButIgnore(this, ignoreFields);\n}\n\n\n/**\n * @property {boolean} debug Whether to print debugging information.\n */\nParsoidConfig.prototype.debug = false;\n\n/**\n * @property {Set} traceFlags Flags that tell us which tracing information to print.\n */\nParsoidConfig.prototype.traceFlags = null;\n\n/**\n * @property {Set} debugFlags Flags that tell us which debugging information to print.\n */\nParsoidConfig.prototype.debugFlags = null;\n\n/**\n * @property {Set} dumpFlags Flags that tell us what state to dump.\n */\nParsoidConfig.prototype.dumpFlags = null;\n\n/**\n * @property {boolean} fetchTemplates Whether we should request templates from a wiki, or just use cached versions.\n */\nParsoidConfig.prototype.fetchTemplates = true;\n\n/**\n * @property {boolean} expandExtensions Whether we should request extension tag expansions from a wiki.\n */\nParsoidConfig.prototype.expandExtensions = true;\n\n/**\n * @property {number} maxDepth The maximum depth to which we should expand templates. Only applies if we would fetch templates anyway, and if we're actually expanding templates. So #fetchTemplates must be true and #usePHPPreProcessor must be false.\n */\nParsoidConfig.prototype.maxDepth = 40;\n\n/**\n * @property {boolean} usePHPPreProcessor Whether we should use the PHP Preprocessor to expand templates, extension content, and the like. See #PHPPreProcessorRequest in lib/mediawiki.ApiRequest.js\n */\nParsoidConfig.prototype.usePHPPreProcessor = true;\n\n/**\n * @property {string} defaultWiki The wiki we should use for template, page, and configuration requests. We set this as a default because a configuration file (e.g. the API service's config.yaml) might set this, but we will still use the appropriate wiki when requests come in for a different prefix.\n */\nParsoidConfig.prototype.defaultWiki = 'enwiki';\n\n/**\n * @property {string} allowCORS Permissive CORS headers as Parsoid is full idempotent currently\n */\nParsoidConfig.prototype.allowCORS = '*';\n\n/**\n * @property {boolean} useSelser Whether to use selective serialization when serializing a DOM to Wikitext. This amounts to not serializing bits of the page that aren't marked as having changed, and requires some way of getting the original text of the page. See #SelectiveSerializer in lib/mediawiki.SelectiveSerializer.js\n */\nParsoidConfig.prototype.useSelser = false;\n\n/**\n * @property {boolean} fetchConfig\n *   Whether to fetch the wiki config from the server or use our local copy.\n */\nParsoidConfig.prototype.fetchConfig = true;\n\n/**\n * @property {boolean} fetchImageInfo\n *   Whether to fetch image info via the API or else treat all images as missing.\n */\nParsoidConfig.prototype.fetchImageInfo = true;\n\n/**\n * @property {boolean} rtTestMode\n *   Test in rt test mode (changes some parse & serialization strategies)\n */\nParsoidConfig.prototype.rtTestMode = false;\n\n/**\n * @property {boolean} addHTMLTemplateParameters\n * When processing template parameters, parse them to HTML and add it to the\n * template parameters data.\n */\nParsoidConfig.prototype.addHTMLTemplateParameters = false;\n\n/**\n * @property {boolean|Array} linting Whether to enable linter Backend.\n * Or an array of enabled lint types\n */\nParsoidConfig.prototype.linting = false;\n\n/**\n * @property {Function} loggerBackend\n * The logger output function.\n * By default, use stderr to output logs.\n */\nParsoidConfig.prototype.loggerBackend = null;\n\n/**\n * @property {Array|null} loggerSampling\n * An array of arrays of log types and sample rates, in percent.\n * Omissions imply 100.\n * For example,\n *   parsoidConfig.loggerSampling = [\n *     ['warn/dsr/inconsistent', 1],\n *   ];\n */\nParsoidConfig.prototype.loggerSampling = null;\n\n/**\n * @property {Function} tracerBackend\n * The tracer output function.\n * By default, use stderr to output traces.\n */\nParsoidConfig.prototype.tracerBackend = null;\n\n/**\n * @property {boolean} strictSSL\n * By default require SSL certificates to be valid\n * Set to false when using self-signed SSL certificates\n */\nParsoidConfig.prototype.strictSSL = true;\n\n/**\n * The default api proxy, overridden by apiConf.proxy entries.\n */\nParsoidConfig.prototype.defaultAPIProxyURI = undefined;\n\n/**\n * Server to connect to for MediaWiki API requests.\n */\nParsoidConfig.prototype.mwApiServer = undefined;\n\n/**\n * The server from which to load style modules.\n */\nParsoidConfig.prototype.modulesLoadURI = undefined;\n\n/**\n * Load WMF sites in the interwikiMap from the cached wmf.sitematrix.json\n */\nParsoidConfig.prototype.loadWMF = false;\n\n/**\n * Set to true to use the Parsoid-specific batch API from the ParsoidBatchAPI\n * extension (action=parsoid-batch).\n */\nParsoidConfig.prototype.useBatchAPI = false;\n\n/**\n * The batch size for parse/preprocess requests\n */\nParsoidConfig.prototype.batchSize = 50;\n\n/**\n * The maximum number of concurrent requests that the API request batcher will\n * allow to be active at any given time. Before this limit is reached, requests\n * will be dispatched more aggressively, giving smaller batches on average.\n * After the limit is reached, batches will be stored in a queue with\n * APIBatchSize items in each batch.\n */\nParsoidConfig.prototype.batchConcurrency = 4;\n\n/**\n * @property {Object|null} Statistics aggregator, for counting and timing.\n */\nParsoidConfig.prototype.metrics = null;\n\n/**\n * @property {string} Default user agent used for making MediaWiki API requests\n */\nParsoidConfig.prototype.userAgent = \"Parsoid/\" + (require('../../package.json').version);\n\n/**\n * @property {number} Number of outstanding event listeners waiting on MediaWiki API responses\n */\nParsoidConfig.prototype.maxListeners = 50000;\n\n/**\n * @property {number} Form size limit in bytes (default is 2M in express)\n */\nParsoidConfig.prototype.maxFormSize = 15 * 1024 * 1024;\n\n/**\n * Log warnings from the MediaWiki Api.\n */\nParsoidConfig.prototype.logMwApiWarnings = true;\n\n/**\n * Suppress some warnings by default.\n */\nParsoidConfig.prototype.suppressMwApiWarnings = /modulemessages is deprecated|Unrecognized parameter: variant/;\n\n/**\n * If enabled, bidi chars adjacent to category links will be stripped\n * in the html -> wt serialization pass.\n */\nParsoidConfig.prototype.scrubBidiChars = false;\n\n/**\n * @property {number} How often should we emit a heap sample? Time in ms.\n *\n * Only relevant if performance timing is enabled\n */\nParsoidConfig.prototype.heapUsageSampleInterval = 5 * 60 * 1000;\n\n/**\n * @property {Function | null} Allow dynamic configuration of unknown domains.\n *\n * See T100841.\n */\nParsoidConfig.prototype.dynamicConfig = null;\n\n/**\n * Initialize the mwApiMap and friends.\n */\nParsoidConfig.prototype.loadWMFApiMap = function() {\n\tvar insertInMaps = (site) => {\n\t\t// Don't use the default proxy for restricted sites.\n\t\t// private: Restricted read and write access.\n\t\t// fishbowl: Restricted write access, full read access.\n\t\t// closed: No write access.\n\t\t// nonglobal: Public but requires registration.\n\t\tconst restricted = site.hasOwnProperty(\"private\") ||\n\t\t\tsite.hasOwnProperty(\"fishbowl\") ||\n\t\t\tsite.hasOwnProperty(\"nonglobal\");\n\n\t\t// Avoid overwriting those already set in localsettings setup.\n\t\tif (!this.mwApiMap.has(site.dbname)) {\n\t\t\tvar apiConf = {\n\t\t\t\tprefix: site.dbname,\n\t\t\t\turi: site.url + \"/w/api.php\",\n\t\t\t\tproxy: {\n\t\t\t\t\turi: restricted ? null : undefined,\n\t\t\t\t\t// WMF production servers don't listen on port 443.\n\t\t\t\t\t// see mediawiki.ApiRequest for handling of this option.\n\t\t\t\t\tstrip_https: true,\n\t\t\t\t},\n\t\t\t\tnonglobal: site.hasOwnProperty(\"nonglobal\"),\n\t\t\t\trestricted,\n\t\t\t};\n\t\t\tthis.setMwApi(apiConf);\n\t\t}\n\t};\n\n\t// See getAPIProxy for the meaning of null / undefined in setMwApi.\n\n\tObject.keys(wmfSiteMatrix).forEach((key) => {\n\t\tvar val = wmfSiteMatrix[key];\n\t\tif (!Number.isNaN(Number(key))) {\n\t\t\tval.site.forEach(insertInMaps);\n\t\t} else if (key === \"specials\") {\n\t\t\tval.forEach(insertInMaps);\n\t\t}\n\t});\n};\n\n/**\n * Set up a wiki configuration.\n *\n * For backward compatibility, if there are two arguments the first is\n * taken as a prefix and the second as the configuration, and if\n * the configuration is a string it is used as the `uri` property\n * in a new empty configuration object.  This usage is deprecated;\n * we recommend users pass a configuration object as documented below.\n *\n * @param {Object} apiConf\n *   The wiki configuration object.\n * @param {string} apiConf.uri\n *   The URL to the wiki's Action API (`api.php`).\n *   This is the only mandatory argument.\n * @param {string} [apiConf.domain]\n *   The \"domain\" used to identify this wiki when using the Parsoid v2/v3 API.\n *   It defaults to the hostname portion of `apiConf.uri`.\n * @param {string} [apiConf.prefix]\n *   An arbitrary unique identifier for this wiki.  If none is provided\n *   a unique string will be generated.\n * @param {Object} [apiConf.proxy]\n *   A proxy configuration object.\n * @param {string|null} [apiConf.proxy.uri]\n *   The URL of a proxy to use for API requests, or null to explicitly\n *   disable API request proxying for this wiki. Will fall back to\n *   {@link ParsoidConfig#defaultAPIProxyURI} if `undefined` (default value).\n * @param {Object} [apiConf.proxy.headers]\n *   Headers to add when proxying.\n * @param {Array} [apiConf.extensions]\n *   A list of native extension constructors.  Otherwise, registers cite by\n *   default.\n * @param {boolean} [apiConf.strictSSL]\n */\nParsoidConfig.prototype.setMwApi = function(apiConf) {\n\tvar prefix;\n\t// Backward-compatibility with old calling conventions.\n\tif (typeof arguments[0] === 'string') {\n\t\tconsole.warn(\n\t\t\t'String arguments to ParsoidConfig#setMwApi are deprecated:',\n\t\t\targuments[0]\n\t\t);\n\t\tif (typeof arguments[1] === 'string') {\n\t\t\tapiConf = { prefix: arguments[0], uri: arguments[1] };\n\t\t} else if (typeof arguments[1] === 'object') {\n\t\t\t// Note that `apiConf` is aliased to `arguments[0]`.\n\t\t\tprefix = arguments[0];\n\t\t\tapiConf = Object.assign({}, arguments[1]);  // overwrites `arguments[0]`\n\t\t\tapiConf.prefix = prefix;\n\t\t} else {\n\t\t\tapiConf = { uri: arguments[0] };\n\t\t}\n\t} else {\n\t\tconsole.assert(typeof apiConf === 'object');\n\t\tapiConf = Object.assign({}, apiConf);  // Don't modify the passed in object\n\t}\n\tconsole.assert(apiConf.uri, \"Action API uri is mandatory.\");\n\tif (!apiConf.prefix) {\n\t\t// Pick a unique prefix.\n\t\tdo {\n\t\t\tapiConf.prefix = 'wiki$' + (this._uniq++);\n\t\t} while (this.mwApiMap.has(apiConf.prefix));\n\t}\n\tif (!apiConf.domain) {\n\t\tapiConf.domain = (new url.URL(apiConf.uri)).host;\n\t}\n\tprefix = apiConf.prefix;\n\n\t// Give them some default extensions.\n\tif (!Array.isArray(apiConf.extensions)) {\n\t\t// Native support for certain extensions (Cite, etc)\n\t\t// Note that in order to remain compatible with MediaWiki core,\n\t\t// core extensions (for example, for the JSON content model)\n\t\t// must take precedence over other extensions.\n\t\tapiConf.extensions = Util.clone(this.defaultNativeExtensions);\n\t\t/* Include global user extensions */\n\t\tParsoidConfig._collectExtensions(\n\t\t\tapiConf.extensions\n\t\t);\n\t\t/* Include wiki-specific user extensions */\n\t\t// User can specify an alternate directory here, so they can point\n\t\t// directly at their MediaWiki core install if they wish.\n\t\tParsoidConfig._collectExtensions(\n\t\t\tapiConf.extensions, apiConf.extdir || apiConf.domain\n\t\t);\n\t}\n\n\tif (this.reverseMwApiMap.has(apiConf.domain)) {\n\t\tconsole.warn(\n\t\t\t\"Domain should be unique in ParsoidConfig#setMwApi calls:\",\n\t\t\tapiConf.domain\n\t\t);\n\t\tconsole.warn(\n\t\t\t\"(It doesn't have to be an actual domain, just a unique string.)\"\n\t\t);\n\t}\n\tif (this.mwApiMap.has(prefix)) {\n\t\tconsole.warn(\n\t\t\t\"Prefix should be unique in ParsoidConfig#setMwApi calls:\",\n\t\t\tprefix\n\t\t);\n\t\tthis.reverseMwApiMap.delete(this.mwApiMap.get(prefix).domain);\n\t}\n\tthis.mwApiMap.set(prefix, apiConf);\n\tthis.reverseMwApiMap.set(apiConf.domain, prefix);\n};\n\n/**\n * Remove an wiki configuration.\n *\n * @param {Object} apiConf\n *   A wiki configuration object.  The value of `apiConf.domain`, or if\n *   that is missing `apiConf.prefix`, will be used to locate the\n *   configuration to remove.  Deprecated: if a string is passed, it\n *   is used as the prefix to remove.\n */\nParsoidConfig.prototype.removeMwApi = function(apiConf) {\n\tvar prefix, domain;\n\tif (typeof apiConf === 'string') {\n\t\tconsole.warn(\n\t\t\t\"Passing a string to ParsoidConfig#removeMwApi is deprecated:\",\n\t\t\tapiConf\n\t\t);\n\t\tapiConf = { prefix: apiConf };\n\t}\n\tprefix = apiConf.prefix;\n\tdomain = apiConf.domain;\n\tconsole.assert(prefix || domain, \"Must pass either prefix or domain\");\n\tif (domain) {\n\t\tprefix = this.reverseMwApiMap.get(domain);\n\t}\n\tif (!prefix || !this.mwApiMap.has(prefix)) {\n\t\treturn;\n\t}\n\tif (!domain) {\n\t\tdomain = this.mwApiMap.get(prefix).domain;\n\t}\n\tthis.reverseMwApiMap.delete(domain);\n\tthis.mwApiMap.delete(prefix);\n};\n\n/**\n * Return the internal prefix used to index configuration information for\n * the given domain string.  If the prefix is not present, attempts\n * dynamic configuration using the `dynamicConfig` hook before returning.\n *\n * XXX: We should eventually move the dynamic configuration to lookups on\n * the mwApiMap, once we remove `prefix` from our codebase: T206764.\n *\n * @param {string} domain\n * @return {string} Internal prefix\n */\nParsoidConfig.prototype.getPrefixFor = function(domain) {\n\t// Support dynamic configuration\n\tif (!this.reverseMwApiMap.has(domain) && this.dynamicConfig) {\n\t\tthis.dynamicConfig(domain);\n\t}\n\treturn this.reverseMwApiMap.get(domain);\n};\n\n/**\n * Figure out the proxy to use for API requests for a given wiki.\n *\n * @param {string} prefix\n * @return {Object}\n */\nParsoidConfig.prototype.getAPIProxy = function(prefix) {\n\tvar apiProxy = { uri: undefined, headers: undefined };\n\t// Don't update the stored proxy object, otherwise subsequent calls\n\t// with the same prefix may do the wrong thing. (ex. null -> undefined ->\n\t// defaultAPIProxyURI)\n\tObject.assign(apiProxy, this.mwApiMap.get(prefix).proxy);\n\tif (apiProxy.uri === null ||\n\t\tthis.mwApiMap.get(prefix).proxy === null) {\n\t\t// Explicitly disable the proxy if null was set for this prefix\n\t\tapiProxy.uri = undefined;\n\t} else if (apiProxy.uri === undefined) {\n\t\t// No specific api proxy set. Fall back to generic API proxy.\n\t\tapiProxy.uri = this.defaultAPIProxyURI;\n\t}\n\treturn apiProxy;\n};\n\n// Collect extensions from a directory.\nParsoidConfig._collectExtensions = function(arr, dir, isNative) {\n\tvar base = path.join(__dirname, '..', '..', 'extensions');\n\tif (dir) {\n\t\tbase = path.resolve(base, dir);\n\t}\n\ttry {\n\t\tif (!fs.statSync(base).isDirectory()) {\n\t\t\treturn; /* not dir */\n\t\t}\n\t} catch (e) {\n\t\treturn; /* no file there */\n\t}\n\tvar files = fs.readdirSync(base);\n\t// Sort! To ensure that we have a repeatable order in which we load\n\t// and process extensions.\n\tfiles.sort();\n\tfiles.forEach(function(d) {\n\t\tvar p = isNative ? path.join(base, d) : path.join(base, d, 'parsoid');\n\t\ttry {\n\t\t\tif (!fs.statSync(p).isDirectory()) {\n\t\t\t\treturn; /* not dir */\n\t\t\t}\n\t\t} catch (e) {\n\t\t\treturn; /* no file there */\n\t\t}\n\t\t// Make sure that exceptions here are visible to user.\n\t\tarr.push(ParsoidConfig.loadExtension(p));\n\t});\n};\n\nParsoidConfig.loadExtension = function(modulePath) {\n\t// The extension will load the extension API relative to this module.\n\tvar ext = require(modulePath);\n\tconsole.assert(\n\t\ttypeof ext === 'function',\n\t\t\"Extension is not a function when loading \" + modulePath\n\t);\n\treturn ext;\n};\n\n// Useful internal function for testing\nParsoidConfig.prototype._sanitizeIt = function() {\n\tthis.sanitizeConfig(this, CONFIG_DEFAULTS);\n};\n\nParsoidConfig.prototype.sanitizeConfig = function(obj, defaults) {\n\t// Make sure that all critical required values are set and\n\t// that localsettings.js mistakes don't leave holes in the settings.\n\t//\n\t// Ex: parsoidConfig.timeouts = {}\n\n\tObject.keys(defaults).forEach((key) => {\n\t\tif (obj[key] === null || obj[key] === undefined || typeof obj[key] !== typeof defaults[key]) {\n\t\t\tif (obj[key] !== undefined) {\n\t\t\t\tconsole.warn(\"WARNING: For config property \" + key + \", required a value of type: \" + (typeof defaults[key]));\n\t\t\t\tconsole.warn(\"Found \" + JSON.stringify(obj[key]) + \"; Resetting it to: \" + JSON.stringify(defaults[key]));\n\t\t\t}\n\t\t\tobj[key] = Util.clone(defaults[key]);\n\t\t} else if (typeof defaults[key] === 'object') {\n\t\t\tthis.sanitizeConfig(obj[key], defaults[key]);\n\t\t}\n\t});\n};\n\nParsoidConfig.prototype.defaultNativeExtensions = [];\nParsoidConfig._collectExtensions(\n\tParsoidConfig.prototype.defaultNativeExtensions,\n\tpath.resolve(__dirname, '../ext'),\n\ttrue /* don't require a 'parsoid' subdirectory */\n);\n\n/**\n * @property {boolean} Expose development routes in the HTTP API.\n */\nParsoidConfig.prototype.devAPI = false;\n\n/**\n * @property {boolean} Enable editing galleries via HTML, instead of extsrc.\n */\nParsoidConfig.prototype.nativeGallery = true;\n\nif (typeof module === \"object\") {\n\tmodule.exports.ParsoidConfig = ParsoidConfig;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/config/WikitextConstants.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/html2wt/DOMNormalizer.js","messages":[{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":284,"column":17,"nodeType":"NewExpression","endLine":284,"endColumn":52},{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":372,"column":44,"nodeType":"Literal","endLine":372,"endColumn":83}],"suppressedMessages":[{"ruleId":"no-unreachable","severity":2,"message":"Unreachable code.","line":729,"column":3,"nodeType":"ExpressionStatement","messageId":"unreachableCode","endLine":729,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * DOM normalization.\n *\n * DOM normalizations are performed after DOMDiff is run.\n * So, normalization routines should update diff markers appropriately.\n *\n * SSS FIXME: Once we simplify WTS to get rid of rt-test mode,\n * we should be able to get rid of the 'children-changed' diff marker\n * and just use the more generic 'subtree-changed' marker.\n *\n * @module\n */\n\n'use strict';\n\nrequire('../../core-upgrade.js');\n\nconst { WikitextConstants: Consts } = require('../config/WikitextConstants.js');\nconst { ContentUtils } = require('../utils/ContentUtils.js');\nconst { DiffUtils } = require('./DiffUtils.js');\nconst { DOMDataUtils } = require('../utils/DOMDataUtils.js');\nconst { DOMUtils } = require('../utils/DOMUtils.js');\nconst { JSUtils } = require('../utils/jsutils.js');\nconst { WTSUtils } = require('./WTSUtils.js');\nconst { WTUtils } = require('../utils/WTUtils.js');\n\nconst wtIgnorableAttrs = new Set(['data-parsoid', 'id', 'title', DOMDataUtils.DataObjectAttrName()]);\nconst htmlIgnorableAttrs = new Set(['data-parsoid', DOMDataUtils.DataObjectAttrName()]);\nconst specializedAttribHandlers = JSUtils.mapObject({\n\t'data-mw': function(nodeA, dmwA, nodeB, dmwB, options) {\n\t\treturn JSUtils.deepEquals(dmwA, dmwB);\n\t},\n});\n\nfunction similar(a, b) {\n\tif (a.nodeName === 'A') {\n\t\t// FIXME: Similar to 1ce6a98, DOMUtils.nextNonDeletedSibling is being\n\t\t// used in this file where maybe DOMUtils.nextNonSepSibling belongs.\n\t\treturn DOMUtils.isElt(b) && DiffUtils.attribsEquals(a, b, wtIgnorableAttrs, specializedAttribHandlers);\n\t} else {\n\t\tvar aIsHtml = WTUtils.isLiteralHTMLNode(a);\n\t\tvar bIsHtml = WTUtils.isLiteralHTMLNode(b);\n\t\tvar ignorableAttrs = aIsHtml ? htmlIgnorableAttrs : wtIgnorableAttrs;\n\n\t\t// FIXME: For non-HTML I/B tags, we seem to be dropping all attributes\n\t\t// in our tag handlers (which seems like a bug). Till that is fixed,\n\t\t// we'll preserve existing functionality here.\n\t\treturn (!aIsHtml && !bIsHtml) ||\n\t\t\t(aIsHtml && bIsHtml && DiffUtils.attribsEquals(a, b, ignorableAttrs, specializedAttribHandlers));\n\t}\n}\n\n/**\n * Can a and b be merged into a single node?\n *\n * @param a\n * @param b\n */\nfunction mergable(a, b) {\n\treturn a.nodeName === b.nodeName && similar(a, b);\n}\n\n/**\n * Can a and b be combined into a single node\n * if we swap a and a.firstChild?\n *\n * For example: A='<b><i>x</i></b>' b='<i>y</i>' => '<i><b>x</b>y</i>'.\n *\n * @param a\n * @param b\n */\nfunction swappable(a, b) {\n\treturn DOMUtils.numNonDeletedChildNodes(a) === 1 &&\n\t\tsimilar(a, DOMUtils.firstNonDeletedChild(a)) &&\n\t\tmergable(DOMUtils.firstNonDeletedChild(a), b);\n}\n\nfunction firstChildFunc(node, rtl) {\n\treturn rtl ? DOMUtils.lastNonDeletedChild(node) : DOMUtils.firstNonDeletedChild(node);\n}\n\nfunction isInsertedContent(node, env) {\n\twhile (true) {\n\t\tif (DiffUtils.hasInsertedDiffMark(node, env)) {\n\t\t\treturn true;\n\t\t}\n\t\tif (DOMUtils.isBody(node)) {\n\t\t\treturn false;\n\t\t}\n\t\tnode = node.parentNode;\n\t}\n}\n\n/*\n * Tag minimization\n * ----------------\n * Minimize a pair of tags in the dom tree rooted at node.\n *\n * This function merges adjacent nodes of the same type\n * and swaps nodes where possible to enable further merging.\n *\n * See examples below:\n *\n * 1. <b>X</b><b>Y</b>\n *    ==> <b>XY</b>\n *\n * 2. <i>A</i><b><i>X</i></b><b><i>Y</i></b><i>Z</i>\n *    ==> <i>A<b>XY</b>Z</i>\n *\n * 3. <a href=\"Football\">Foot</a><a href=\"Football\">ball</a>\n *    ==> <a href=\"Football\">Football</a>\n */\n\nfunction rewriteablePair(env, a, b) {\n\tif (Consts.WTQuoteTags.has(a.nodeName)) {\n\t\t// For <i>/<b> pair, we need not check whether the node being transformed\n\t\t// are new / edited, etc. since these minimization scenarios can\n\t\t// never show up in HTML that came from parsed wikitext.\n\t\t//\n\t\t// <i>..</i><i>..</i> can never show up without a <nowiki/> in between.\n\t\t// Similarly for <b>..</b><b>..</b> and <b><i>..</i></b><i>..</i>.\n\t\t//\n\t\t// This is because a sequence of 4 quotes is not parsed as ..</i><i>..\n\t\t// Neither is a sequence of 7 quotes parsed as ..</i></b><i>..\n\t\t//\n\t\t// So, if we see a minimizable pair of nodes, it is because the HTML\n\t\t// didn't originate from wikitext OR the HTML has been subsequently edited.\n\t\t// In both cases, we want to transform the DOM.\n\n\t\treturn Consts.WTQuoteTags.has(b.nodeName);\n\t} else if (a.nodeName === 'A') {\n\t\t// For <a> tags, we require at least one of the two tags\n\t\t// to be a newly created element.\n\t\treturn b.nodeName === 'A' && (WTUtils.isNewElt(a) || WTUtils.isNewElt(b));\n\t}\n}\n\n/**\n * @class\n * @param {SerializerState} state\n */\nclass DOMNormalizer {\n\tconstructor(state) {\n\t\tthis.env = state.env;\n\t\tthis.inSelserMode = state.selserMode;\n\t\tthis.inRtTestMode = state.rtTestMode;\n\t\tthis.inInsertedContent = false;\n\t}\n\n\taddDiffMarks(node, mark, dontRecurse) {\n\t\tvar env = this.env;\n\t\tif (!this.inSelserMode || DiffUtils.hasDiffMark(node, env, mark)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Don't introduce nested inserted markers\n\t\tif (this.inInsertedContent && mark === 'inserted') {\n\t\t\treturn;\n\t\t}\n\n\t\t// Newly added elements don't need diff marks\n\t\tif (!WTUtils.isNewElt(node)) {\n\t\t\tDiffUtils.addDiffMark(node, env, mark);\n\t\t\tif (mark === 'inserted' || mark === 'deleted') {\n\t\t\t\tDiffUtils.addDiffMark(node.parentNode, env, 'children-changed');\n\t\t\t}\n\t\t}\n\n\t\tif (dontRecurse) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Walk up the subtree and add 'subtree-changed' markers\n\t\tnode = node.parentNode;\n\t\twhile (DOMUtils.isElt(node) && !DOMUtils.isBody(node)) {\n\t\t\tif (DiffUtils.hasDiffMark(node, env, 'subtree-changed')) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!WTUtils.isNewElt(node)) {\n\t\t\t\tDiffUtils.setDiffMark(node, env, 'subtree-changed');\n\t\t\t}\n\t\t\tnode = node.parentNode;\n\t\t}\n\t}\n\n\t/**\n\t * Transfer all of b's children to a and delete b.\n\t *\n\t * @param a\n\t * @param b\n\t */\n\tmerge(a, b) {\n\t\tvar sentinel = b.firstChild;\n\n\t\t// Migrate any intermediate nodes (usually 0 / 1 diff markers)\n\t\t// present between a and b to a\n\t\tvar next = a.nextSibling;\n\t\tif (next !== b) {\n\t\t\ta.appendChild(next);\n\t\t}\n\n\t\t// The real work of merging\n\t\tDOMUtils.migrateChildren(b, a);\n\t\tb.parentNode.removeChild(b);\n\n\t\t// Normalize the node to merge any adjacent text nodes\n\t\ta.normalize();\n\n\t\t// Update diff markers\n\t\tif (sentinel) {\n\t\t\t// Nodes starting at 'sentinal' were inserted into 'a'\n\t\t\t// b, which was a's sibling was deleted\n\t\t\t// Only addDiffMarks to sentinel, if it is still part of the dom\n\t\t\t// (and hasn't been deleted by the call to a.normalize() )\n\t\t\tif (sentinel.parentNode) {\n\t\t\t\tthis.addDiffMarks(sentinel, 'moved', true);\n\t\t\t}\n\t\t\tthis.addDiffMarks(a, 'children-changed', true);\n\t\t}\n\t\tif (a.nextSibling) {\n\t\t\t// FIXME: Hmm .. there is an API hole here\n\t\t\t// about ability to add markers after last child\n\t\t\tthis.addDiffMarks(a.nextSibling, 'moved', true);\n\t\t}\n\t\tthis.addDiffMarks(a.parentNode, 'children-changed');\n\n\t\treturn a;\n\t}\n\n\t/**\n\t * b is a's sole non-deleted child.  Switch them around.\n\t *\n\t * @param a\n\t * @param b\n\t */\n\tswap(a, b) {\n\t\tDOMUtils.migrateChildren(b, a);\n\t\ta.parentNode.insertBefore(b, a);\n\t\tb.appendChild(a);\n\n\t\t// Mark a's subtree, a, and b as all having moved\n\t\tif (a.firstChild !== null) {\n\t\t\tthis.addDiffMarks(a.firstChild, 'moved', true);\n\t\t}\n\t\tthis.addDiffMarks(a, 'moved', true);\n\t\tthis.addDiffMarks(b, 'moved', true);\n\t\tthis.addDiffMarks(a, 'children-changed', true);\n\t\tthis.addDiffMarks(b, 'children-changed', true);\n\t\tthis.addDiffMarks(b.parentNode, 'children-changed');\n\n\t\treturn b;\n\t}\n\n\thoistLinks(node, rtl) {\n\t\tvar sibling = firstChildFunc(node, rtl);\n\t\tvar hasHoistableContent = false;\n\n\t\twhile (sibling) {\n\t\t\tvar next = rtl ? DOMUtils.previousNonDeletedSibling(sibling) : DOMUtils.nextNonDeletedSibling(sibling);\n\t\t\tif (!DOMUtils.isContentNode(sibling)) {\n\t\t\t\tsibling = next;\n\t\t\t\tcontinue;\n\t\t\t} else if (!WTUtils.isRenderingTransparentNode(sibling)\n\t\t\t\t|| WTUtils.isEncapsulationWrapper(sibling)) {\n\t\t\t\t// Don't venture into templated content\n\t\t\t\tbreak;\n\t\t\t} else {\n\t\t\t\thasHoistableContent = true;\n\t\t\t}\n\t\t\tsibling = next;\n\t\t}\n\n\t\tif (hasHoistableContent) {\n\t\t\t// soak up all the non-content nodes (exclude sibling)\n\t\t\tvar move = firstChildFunc(node, rtl);\n\t\t\tvar firstNode = move;\n\t\t\twhile (move !== sibling) {\n\t\t\t\tnode.parentNode.insertBefore(move, rtl ? DOMUtils.nextNonDeletedSibling(node) : node);\n\t\t\t\tmove = firstChildFunc(node, rtl);\n\t\t\t}\n\n\t\t\t// and drop any leading whitespace\n\t\t\tif (DOMUtils.isText(sibling)) {\n\t\t\t\tvar space = new RegExp(rtl ? '\\\\s*$' : '^\\\\s*');\n\t\t\t\tsibling.nodeValue = sibling.nodeValue.replace(space, '');\n\t\t\t}\n\n\t\t\t// Update diff markers\n\t\t\tthis.addDiffMarks(firstNode, 'moved', true);\n\t\t\tif (sibling) {\n\t\t\t\tthis.addDiffMarks(sibling, 'moved', true);\n\t\t\t}\n\t\t\tthis.addDiffMarks(node, 'children-changed', true);\n\t\t\tthis.addDiffMarks(node.parentNode, 'children-changed');\n\t\t}\n\t}\n\n\tstripIfEmpty(node) {\n\t\tvar next = DOMUtils.nextNonDeletedSibling(node);\n\t\tvar dp = DOMDataUtils.getDataParsoid(node);\n\t\tvar strict = this.inRtTestMode;\n\t\tvar autoInserted = dp.autoInsertedStart || dp.autoInsertedEnd;\n\n\t\t// In rtTestMode, let's reduce noise by requiring the node to be fully\n\t\t// empty (ie. exclude whitespace text) and not having auto-inserted tags.\n\t\tvar strippable = !(this.inRtTestMode && autoInserted) &&\n\t\t\tDOMUtils.nodeEssentiallyEmpty(node, strict) &&\n\t\t\t// Ex: \"<a..>..</a><b></b>bar\"\n\t\t\t// From [[Foo]]<b/>bar usage found on some dewiki pages.\n\t\t\t// FIXME: Should this always than just in rt-test mode\n\t\t\t!(this.inRtTestMode && dp.stx === 'html');\n\n\t\tif (strippable) {\n\t\t\t// Update diff markers (before the deletion)\n\t\t\tthis.addDiffMarks(node, 'deleted', true);\n\t\t\tnode.parentNode.removeChild(node);\n\t\t\treturn next;\n\t\t} else {\n\t\t\treturn node;\n\t\t}\n\t}\n\n\tmoveTrailingSpacesOut(node) {\n\t\tvar next = DOMUtils.nextNonDeletedSibling(node);\n\t\tvar last = DOMUtils.lastNonDeletedChild(node);\n\t\tvar endsInSpace = DOMUtils.isText(last) && last.nodeValue.match(/\\s+$/);\n\t\t// Conditional on rtTestMode to reduce the noise in testing.\n\t\tif (!this.inRtTestMode && endsInSpace) {\n\t\t\tlast.nodeValue = last.nodeValue.slice(0, Math.max(0, endsInSpace.index));\n\t\t\t// Try to be a little smarter and drop the spaces if possible.\n\t\t\tif (next && (!DOMUtils.isText(next) || !/^\\s+/.test(next.nodeValue))) {\n\t\t\t\tif (!DOMUtils.isText(next)) {\n\t\t\t\t\tvar txt = node.ownerDocument.createTextNode('');\n\t\t\t\t\tnode.parentNode.insertBefore(txt, next);\n\t\t\t\t\tnext = txt;\n\t\t\t\t}\n\t\t\t\tnext.nodeValue = endsInSpace[0] + next.nodeValue;\n\t\t\t\t// next (a text node) is new / had new content added to it\n\t\t\t\tthis.addDiffMarks(next, 'inserted', true);\n\t\t\t}\n\t\t\tthis.addDiffMarks(last, 'inserted', true);\n\t\t\tthis.addDiffMarks(node.parentNode, 'children-changed');\n\t\t}\n\t}\n\n\tstripBRs(node) {\n\t\tvar child = node.firstChild;\n\t\twhile (child) {\n\t\t\tvar next = child.nextSibling;\n\t\t\tif (child.nodeName === 'BR') {\n\t\t\t\t// replace <br/> with a single space\n\t\t\t\tnode.removeChild(child);\n\t\t\t\tnode.insertBefore(node.ownerDocument.createTextNode(' '), next);\n\t\t\t} else if (DOMUtils.isElt(child)) {\n\t\t\t\tthis.stripBRs(child);\n\t\t\t}\n\t\t\tchild = next;\n\t\t}\n\t}\n\n\tstripBidiCharsAroundCategories(node) {\n\t\tif (!DOMUtils.isText(node) ||\n\t\t\t(!WTUtils.isCategoryLink(node.previousSibling) && !WTUtils.isCategoryLink(node.nextSibling))) {\n\t\t\t// Not a text node and not adjacent to a category link\n\t\t\treturn node;\n\t\t}\n\n\t\tvar next = node.nextSibling;\n\t\tif (!next || WTUtils.isCategoryLink(next)) {\n\t\t\t// The following can leave behind an empty text node.\n\t\t\tvar oldLength = node.nodeValue.length;\n\t\t\tnode.nodeValue = node.nodeValue.replace(/([\\u200e\\u200f]+\\n)?[\\u200e\\u200f]+$/g, '');\n\t\t\tvar newLength = node.nodeValue.length;\n\n\t\t\tif (oldLength !== newLength) {\n\t\t\t\t// Log changes for editors benefit\n\t\t\t\tthis.env.log('warn/html2wt/bidi',\n\t\t\t\t\t'LRM/RLM unicode chars stripped around categories');\n\t\t\t}\n\n\t\t\tif (newLength === 0) {\n\t\t\t\t// Remove empty text nodes to keep DOM in normalized form\n\t\t\t\tvar ret = DOMUtils.nextNonDeletedSibling(node);\n\t\t\t\tnode.parentNode.removeChild(node);\n\t\t\t\tthis.addDiffMarks(node, 'deleted');\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Treat modified node as having been newly inserted\n\t\t\tthis.addDiffMarks(node, 'inserted');\n\t\t}\n\t\treturn node;\n\t}\n\n\t// When an A tag is encountered, if there are format tags inside, move them outside\n\t// Also merge a single sibling A tag that is mergable\n\t// The link href and text must match for this normalization to take effect\n\tmoveFormatTagOutsideATag(node) {\n\t\tif (this.inRtTestMode || node.nodeName !== 'A') {\n\t\t\treturn node;\n\t\t}\n\n\t\tvar sibling = DOMUtils.nextNonDeletedSibling(node);\n\t\tif (sibling) {\n\t\t\tthis.normalizeSiblingPair(node, sibling);\n\t\t}\n\n\t\tvar firstChild = DOMUtils.firstNonDeletedChild(node);\n\t\tvar fcNextSibling = null;\n\t\tif (firstChild) {\n\t\t\tfcNextSibling = DOMUtils.nextNonDeletedSibling(firstChild);\n\t\t}\n\n\t\tvar blockingAttrs = [ 'color', 'style', 'class' ];\n\n\t\tif (!node.hasAttribute('href')) {\n\t\t\tthis.env.log(\"error/normalize\", \"href is missing from a tag\", node.outerHTML);\n\t\t\treturn node;\n\t\t}\n\t\tvar nodeHref = node.getAttribute('href');\n\n\t\t// If there are no tags to swap, we are done\n\t\tif (firstChild && DOMUtils.isElt(firstChild) &&\n\t\t\t// No reordering possible with multiple children\n\t\t\tfcNextSibling === null &&\n\t\t\t// Do not normalize WikiLinks with these attributes\n\t\t\t!blockingAttrs.some(function(attr) {\n\t\t\t\treturn firstChild.hasAttribute(attr);\n\t\t\t}) &&\n\t\t\t// Compare textContent to the href, noting that this matching doesn't handle all\n\t\t\t// possible simple-wiki-link scenarios that isSimpleWikiLink in link handler tackles\n\t\t\tnode.textContent === nodeHref.replace(/\\.\\//, '')\n\t\t) {\n\t\t\tvar child;\n\t\t\twhile ((child = DOMUtils.firstNonDeletedChild(node)) && DOMUtils.isFormattingElt(child)) {\n\t\t\t\tthis.swap(node, child);\n\t\t\t}\n\t\t\treturn firstChild;\n\t\t}\n\n\t\treturn node;\n\t}\n\n\t/**\n\t * Wikitext normalizations implemented right now:\n\t *\n\t * 1. Tag minimization (I/B tags) in normalizeSiblingPair\n\t * 2. Strip empty headings and style tags\n\t * 3. Force SOL transparent links to serialize before/after heading\n\t * 4. Trailing spaces are migrated out of links\n\t * 5. Space is added before escapable prefixes in table cells\n\t * 6. Strip <br/> from headings\n\t * 7. Strip bidi chars around categories\n\t * 8. When an A tag is encountered, if there are format tags inside, move them outside\n\t *\n\t * The return value from this function should respect the\n\t * following contract:\n\t * - if input node is unmodified, return it.\n\t * - if input node is modified, return the new node\n\t *   that it transforms into.\n\t * If you return a node other than this, normalizations may not\n\t * apply cleanly and may be skipped.\n\t *\n\t * @param {Node} node the node to normalize\n\t * @return {Node} the normalized node\n\t */\n\tnormalizeNode(node) {\n\t\tvar dp;\n\t\tif (node.nodeName === 'TH' || node.nodeName === 'TD') {\n\t\t\tdp = DOMDataUtils.getDataParsoid(node);\n\t\t\t// Table cells (td/th) previously used the stx_v flag for single-row syntax.\n\t\t\t// Newer code uses stx flag since that is used everywhere else.\n\t\t\t// While we still have old HTML in cache / storage, accept\n\t\t\t// the stx_v flag as well.\n\t\t\t// TODO: We are at html version 1.5.0 now. Once storage\n\t\t\t// no longer has version 1.5.0 content, we can get rid of\n\t\t\t// this b/c code.\n\t\t\tif (dp.stx_v) {\n\t\t\t\t// HTML (stx='html') elements will not have the stx_v flag set\n\t\t\t\t// since the single-row syntax only applies to native-wikitext.\n\t\t\t\t// So, we can safely override it here.\n\t\t\t\tdp.stx = dp.stx_v;\n\t\t\t}\n\t\t}\n\n\t\tvar next;\n\n\t\tif (this.env.conf.parsoid.scrubBidiChars) {\n\t\t\t// Strip bidirectional chars around categories\n\t\t\t// Note that this is being done everywhere,\n\t\t\t// not just in selser mode\n\t\t\tnext = this.stripBidiCharsAroundCategories(node);\n\t\t\tif (next !== node) {\n\t\t\t\treturn next;\n\t\t\t}\n\t\t}\n\n\t\t// Skip unmodified content\n\t\tif (this.inSelserMode && !DOMUtils.isBody(node) &&\n\t\t\t!this.inInsertedContent && !DiffUtils.hasDiffMarkers(node, this.env) &&\n\t\t\t// If orig-src is not valid, this in effect becomes\n\t\t\t// an edited node and needs normalizations applied to it.\n\t\t\tWTSUtils.origSrcValidInEditedContext(this.env, node)) {\n\t\t\treturn node;\n\t\t}\n\n\t\t// Headings\n\t\tif (/^H[1-6]$/.test(node.nodeName)) {\n\t\t\tthis.hoistLinks(node, false);\n\t\t\tthis.hoistLinks(node, true);\n\t\t\tthis.stripBRs(node);\n\t\t\treturn this.stripIfEmpty(node);\n\n\t\t// Quote tags\n\t\t} else if (Consts.WTQuoteTags.has(node.nodeName)) {\n\t\t\treturn this.stripIfEmpty(node);\n\n\t\t// Anchors\n\t\t} else if (node.nodeName === 'A') {\n\t\t\tnext = DOMUtils.nextNonDeletedSibling(node);\n\t\t\t// We could have checked for !mw:ExtLink but in\n\t\t\t// the case of links without any annotations,\n\t\t\t// the positive test is semantically safer than the\n\t\t\t// negative test.\n\t\t\tif (/^mw:WikiLink$/.test(node.getAttribute('rel') || '') && this.stripIfEmpty(node) !== node) {\n\t\t\t\treturn next;\n\t\t\t}\n\t\t\tthis.moveTrailingSpacesOut(node);\n\t\t\treturn this.moveFormatTagOutsideATag(node);\n\n\t\t// Table cells\n\t\t} else if (node.nodeName === 'TD') {\n\t\t\tdp = DOMDataUtils.getDataParsoid(node);\n\t\t\t// * HTML <td>s won't have escapable prefixes\n\t\t\t// * First cell should always be checked for escapable prefixes\n\t\t\t// * Second and later cells in a wikitext td row (with stx='row' flag)\n\t\t\t//   won't have escapable prefixes.\n\t\t\tif (dp.stx === 'html' ||\n\t\t\t\t(DOMUtils.firstNonSepChild(node.parentNode) !== node && dp.stx === 'row')) {\n\t\t\t\treturn node;\n\t\t\t}\n\n\t\t\tvar first = DOMUtils.firstNonDeletedChild(node);\n\t\t\t// Emit a space before escapable prefix\n\t\t\t// This is preferable to serializing with a nowiki.\n\t\t\tif (DOMUtils.isText(first) && /^[\\-+}]/.test(first.nodeValue)) {\n\t\t\t\tfirst.nodeValue = ' ' + first.nodeValue;\n\t\t\t\tthis.addDiffMarks(first, 'inserted', true);\n\t\t\t}\n\t\t\treturn node;\n\n\t\t// Font tags without any attributes\n\t\t} else if (node.nodeName === 'FONT' && DOMDataUtils.noAttrs(node)) {\n\t\t\tnext = DOMUtils.nextNonDeletedSibling(node);\n\t\t\tDOMUtils.migrateChildren(node, node.parentNode, node);\n\t\t\tnode.parentNode.removeChild(node);\n\t\t\treturn next;\n\n\t\t// T184755: Convert sequences of <p></p> nodes to sequences of\n\t\t// <br/>, <p><br/>..other content..</p>, <p><br/><p/> to ensure\n\t\t// they serialize to as many newlines as the count of <p></p> nodes.\n\t\t} else if (node.nodeName === 'P' && !WTUtils.isLiteralHTMLNode(node) &&\n\t\t\t// Don't normalize empty p-nodes that came from source\n\t\t\t// FIXME: See T210647\n\t\t\t!/\\bmw-empty-elt\\b/.test(node.getAttribute('class') || '') &&\n\t\t\t// Don't apply normalization to <p></p> nodes that\n\t\t\t// were generated through deletions or other normalizations.\n\t\t\t// FIXME: This trick fails for non-selser mode since\n\t\t\t// diff markers are only added in selser mode.\n\t\t\tDOMUtils.hasNChildren(node, 0, true) &&\n\t\t\t// FIXME: Also, skip if this is the only child.\n\t\t\t// Eliminates spurious test failures in non-selser mode.\n\t\t\t!DOMUtils.hasNChildren(node.parentNode, 1)\n\t\t) {\n\t\t\tlet brParent, brSibling;\n\t\t\tconst br = node.ownerDocument.createElement('br');\n\t\t\tnext = DOMUtils.nextNonSepSibling(node);\n\t\t\tif (next && next.nodeName === 'P' && !WTUtils.isLiteralHTMLNode(next)) {\n\t\t\t\t// Replace 'node' (<p></p>) with a <br/> and make it the\n\t\t\t\t// first child of 'next' (<p>..</p>). If 'next' was actually\n\t\t\t\t// a <p></p> (i.e. empty), 'next' becomes <p><br/></p>\n\t\t\t\t// which will serialize to 2 newlines.\n\t\t\t\tbrParent = next;\n\t\t\t\tbrSibling = next.firstChild;\n\t\t\t} else {\n\t\t\t\t// We cannot merge the <br/> with 'next' because it\n\t\t\t\t// is not a <p>..</p>.\n\t\t\t\tbrParent = node.parentNode;\n\t\t\t\tbrSibling = node;\n\t\t\t}\n\n\t\t\t// Insert <br/>\n\t\t\tbrParent.insertBefore(br, brSibling);\n\t\t\t// Avoid nested insertion markers\n\t\t\tif (brParent === next && !isInsertedContent(brParent, this.env)) {\n\t\t\t\tthis.addDiffMarks(br, 'inserted');\n\t\t\t}\n\n\t\t\t// Delete node\n\t\t\tthis.addDiffMarks(node.parentNode, 'deleted');\n\t\t\tnode.parentNode.removeChild(node);\n\n\t\t\treturn next;\n\n\t\t// Default\n\t\t} else {\n\t\t\treturn node;\n\t\t}\n\t}\n\n\tnormalizeSiblingPair(a, b) {\n\t\tif (!rewriteablePair(this.env, a, b)) {\n\t\t\treturn b;\n\t\t}\n\n\t\t// Since 'a' and 'b' make a rewriteable tag-pair, we are good to go.\n\t\tif (mergable(a, b)) {\n\t\t\ta = this.merge(a, b);\n\t\t\t// The new a's children have new siblings. So let's look\n\t\t\t// at a again. But their grandkids haven't changed,\n\t\t\t// so we don't need to recurse further.\n\t\t\tthis.processSubtree(a, false);\n\t\t\treturn a;\n\t\t}\n\n\t\tif (swappable(a, b)) {\n\t\t\ta = this.merge(this.swap(a, DOMUtils.firstNonDeletedChild(a)), b);\n\t\t\t// Again, a has new children, but the grandkids have already\n\t\t\t// been minimized.\n\t\t\tthis.processSubtree(a, false);\n\t\t\treturn a;\n\t\t}\n\n\t\tif (swappable(b, a)) {\n\t\t\ta = this.merge(a, this.swap(b, DOMUtils.firstNonDeletedChild(b)));\n\t\t\t// Again, a has new children, but the grandkids have already\n\t\t\t// been minimized.\n\t\t\tthis.processSubtree(a, false);\n\t\t\treturn a;\n\t\t}\n\n\t\treturn b;\n\t}\n\n\tprocessSubtree(node, recurse) {\n\t\t// Process the first child outside the loop.\n\t\tvar a = DOMUtils.firstNonDeletedChild(node);\n\t\tif (!a) {\n\t\t\treturn;\n\t\t}\n\n\t\ta = this.processNode(a, recurse);\n\t\twhile (a) {\n\t\t\t// We need a pair of adjacent siblings for tag minimization.\n\t\t\tvar b = DOMUtils.nextNonDeletedSibling(a);\n\t\t\tif (!b) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Process subtree rooted at 'b'.\n\t\t\tb = this.processNode(b, recurse);\n\n\t\t\t// If we skipped over a bunch of nodes in the middle,\n\t\t\t// we no longer have a pair of adjacent siblings.\n\t\t\tif (b && DOMUtils.previousNonDeletedSibling(b) === a) {\n\t\t\t\t// Process the pair.\n\t\t\t\ta = this.normalizeSiblingPair(a, b);\n\t\t\t} else {\n\t\t\t\ta = b;\n\t\t\t}\n\t\t}\n\t}\n\n\tprocessNode(node, recurse) {\n\t\t// Normalize 'node' and the subtree rooted at 'node'\n\t\t// recurse = true  => recurse and normalize subtree\n\t\t// recurse = false => assume the subtree is already normalized\n\n\t\t// Normalize node till it stabilizes\n\t\tvar next;\n\t\twhile (true) {\n\t\t\t// Skip templated content\n\t\t\twhile (node && WTUtils.isFirstEncapsulationWrapperNode(node)) {\n\t\t\t\tnode = WTUtils.skipOverEncapsulatedContent(node);\n\t\t\t}\n\n\t\t\tif (!node) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Set insertion marker\n\t\t\tvar insertedSubtree = DiffUtils.hasInsertedDiffMark(node, this.env);\n\t\t\tif (insertedSubtree) {\n\t\t\t\tif (this.inInsertedContent) {\n\t\t\t\t\t// Dump debugging info\n\t\t\t\t\tconsole.warn(\"--- Nested inserted dom-diff flags ---\");\n\t\t\t\t\tconsole.warn(\"Node:\", DOMUtils.isElt(node) ? ContentUtils.ppToXML(node) : node.textContent);\n\t\t\t\t\tconsole.warn(\"Node's parent:\", ContentUtils.ppToXML(node.parentNode));\n\t\t\t\t\tContentUtils.dumpDOM(node.ownerDocument.body,\n\t\t\t\t\t\t'-- DOM triggering nested inserted dom-diff flags --',\n\t\t\t\t\t\t{ storeDiffMark: true, env: this.env });\n\t\t\t\t}\n\t\t\t\t// FIXME: If this assert is removed, the above dumping code should\n\t\t\t\t// either be removed OR fixed up to remove uses of ContentUtils.ppToXML\n\t\t\t\tconsole.assert(!this.inInsertedContent, 'Found nested inserted dom-diff flags!');\n\t\t\t\tthis.inInsertedContent = true;\n\t\t\t}\n\n\t\t\t// Post-order traversal: Process subtree first, and current node after.\n\t\t\t// This lets multiple normalizations take effect cleanly.\n\t\t\tif (recurse && DOMUtils.isElt(node)) {\n\t\t\t\tthis.processSubtree(node, true);\n\t\t\t}\n\n\t\t\tnext = this.normalizeNode(node);\n\n\t\t\t// Clear insertion marker\n\t\t\tif (insertedSubtree) {\n\t\t\t\tthis.inInsertedContent = false;\n\t\t\t}\n\n\t\t\tif (next === node) {\n\t\t\t\treturn node;\n\t\t\t} else {\n\t\t\t\tnode = next;\n\t\t\t}\n\t\t}\n\n\t\tconsole.assert(false, \"Control should never get here!\");  // eslint-disable-line\n\t}\n\n\tnormalize(body) {\n\t\treturn this.processNode(body, true);\n\t}\n}\n\nif (typeof module === 'object') {\n\tmodule.exports.DOMNormalizer = DOMNormalizer;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/html2wt/DiffUtils.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/html2wt/WTSUtils.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":13,"column":20,"nodeType":"Literal","endLine":13,"endColumn":53}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/** @module */\n\n\"use strict\";\n\nconst { DOMDataUtils } = require('../utils/DOMDataUtils.js');\nconst { DOMUtils } = require('../utils/DOMUtils.js');\nconst { DiffUtils } = require('./DiffUtils.js');\nconst { WTUtils } = require('../utils/WTUtils.js');\n\n/** @namespace */\nclass WTSUtils {\n\tstatic isValidSep(sep) {\n\t\treturn sep.match(/^(\\s|<!--([^\\-]|-(?!->))*-->)*$/);\n\t}\n\n\tstatic hasValidTagWidths(dsr) {\n\t\treturn dsr &&\n\t\t\ttypeof (dsr[2]) === 'number' && dsr[2] >= 0 &&\n\t\t\ttypeof (dsr[3]) === 'number' && dsr[3] >= 0;\n\t}\n\n\t/**\n\t * For new elements, attrs are always considered modified.  However, For\n\t * old elements, we only consider an attribute modified if we have shadow\n\t * info for it and it doesn't match the current value.\n\t *\n\t * @param node\n\t * @param name\n\t * @param curVal\n\t * @return {Object}\n\t *   @return {any} return.value\n\t *   @return {boolean} return.modified If the value of the attribute changed since we parsed the wikitext.\n\t *   @return {boolean} return.fromsrc Whether we got the value from source-based roundtripping.\n\t */\n\tstatic getShadowInfo(node, name, curVal) {\n\t\tvar dp = DOMDataUtils.getDataParsoid(node);\n\n\t\t// Not the case, continue regular round-trip information.\n\t\tif (dp.a === undefined || dp.a[name] === undefined) {\n\t\t\treturn {\n\t\t\t\tvalue: curVal,\n\t\t\t\t// Mark as modified if a new element\n\t\t\t\tmodified: WTUtils.isNewElt(node),\n\t\t\t\tfromsrc: false,\n\t\t\t};\n\t\t} else if (dp.a[name] !== curVal) {\n\t\t\treturn {\n\t\t\t\tvalue: curVal,\n\t\t\t\tmodified: true,\n\t\t\t\tfromsrc: false,\n\t\t\t};\n\t\t} else if (dp.sa === undefined || dp.sa[name] === undefined) {\n\t\t\treturn {\n\t\t\t\tvalue: curVal,\n\t\t\t\tmodified: false,\n\t\t\t\tfromsrc: false,\n\t\t\t};\n\t\t} else {\n\t\t\treturn {\n\t\t\t\tvalue: dp.sa[name],\n\t\t\t\tmodified: false,\n\t\t\t\tfromsrc: true,\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Get shadowed information about an attribute on a node.\n\t *\n\t * @param {Node} node\n\t * @param {string} name\n\t * @return {Object}\n\t *   @return {any} return.value\n\t *   @return {boolean} return.modified If the value of the attribute changed since we parsed the wikitext.\n\t *   @return {boolean} return.fromsrc Whether we got the value from source-based roundtripping.\n\t */\n\tstatic getAttributeShadowInfo(node, name) {\n\t\treturn this.getShadowInfo(node, name, node.hasAttribute(name) ? node.getAttribute(name) : null);\n\t}\n\n\tstatic commentWT(comment) {\n\t\treturn '<!--' + WTUtils.decodeComment(comment) + '-->';\n\t}\n\n\t/**\n\t * In wikitext, did origNode occur next to a block node which has been\n\t * deleted? While looking for next, we look past DOM nodes that are\n\t * transparent in rendering. (See emitsSolTransparentSingleLineWT for\n\t * which nodes.)\n\t *\n\t * @param origNode\n\t * @param before\n\t */\n\tstatic nextToDeletedBlockNodeInWT(origNode, before) {\n\t\tif (!origNode || DOMUtils.isBody(origNode)) {\n\t\t\treturn false;\n\t\t}\n\n\t\twhile (true) {\n\t\t\t// Find the nearest node that shows up in HTML (ignore nodes that show up\n\t\t\t// in wikitext but don't affect sol-state or HTML rendering -- note that\n\t\t\t// whitespace is being ignored, but that whitespace occurs between block nodes).\n\t\t\tvar node = origNode;\n\t\t\tdo {\n\t\t\t\tnode = before ? node.previousSibling : node.nextSibling;\n\t\t\t\tif (DiffUtils.maybeDeletedNode(node)) {\n\t\t\t\t\treturn DiffUtils.isDeletedBlockNode(node);\n\t\t\t\t}\n\t\t\t} while (node && WTUtils.emitsSolTransparentSingleLineWT(node));\n\n\t\t\tif (node) {\n\t\t\t\treturn false;\n\t\t\t} else {\n\t\t\t\t// Walk up past zero-width wikitext parents\n\t\t\t\tnode = origNode.parentNode;\n\t\t\t\tif (!WTUtils.isZeroWidthWikitextElt(node)) {\n\t\t\t\t\t// If the parent occupies space in wikitext,\n\t\t\t\t\t// clearly, we are not next to a deleted block node!\n\t\t\t\t\t// We'll eventually hit BODY here and return.\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\torigNode = node;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Check if whitespace preceding this node would NOT trigger an indent-pre.\n\t *\n\t * @param node\n\t * @param sepNode\n\t */\n\tstatic precedingSpaceSuppressesIndentPre(node, sepNode) {\n\t\tif (node !== sepNode && DOMUtils.isText(node)) {\n\t\t\t// if node is the same as sepNode, then the separator text\n\t\t\t// at the beginning of it has been stripped out already, and\n\t\t\t// we cannot use it to test it for indent-pre safety\n\t\t\treturn node.nodeValue.match(/^[ \\t]*\\n/);\n\t\t} else if (node.nodeName === 'BR') {\n\t\t\treturn true;\n\t\t} else if (WTUtils.isFirstEncapsulationWrapperNode(node)) {\n\t\t\t// Dont try any harder than this\n\t\t\treturn (!node.hasChildNodes()) || node.innerHTML.match(/^\\n/);\n\t\t} else {\n\t\t\treturn WTUtils.isBlockNodeWithVisibleWT(node);\n\t\t}\n\t}\n\n\tstatic traceNodeName(node) {\n\t\tswitch (node.nodeType) {\n\t\t\tcase node.ELEMENT_NODE:\n\t\t\t\treturn DOMUtils.isDiffMarker(node) ?\n\t\t\t\t\t\"DIFF_MARK\" : \"NODE: \" + node.nodeName;\n\t\t\tcase node.TEXT_NODE:\n\t\t\t\treturn \"TEXT: \" + JSON.stringify(node.nodeValue);\n\t\t\tcase node.COMMENT_NODE:\n\t\t\t\treturn \"CMT : \" + JSON.stringify(WTSUtils.commentWT(node.nodeValue));\n\t\t\tdefault:\n\t\t\t\treturn node.nodeName;\n\t\t}\n\t}\n\n\t/**\n\t * In selser mode, check if an unedited node's wikitext from source wikitext\n\t * is reusable as is.\n\t *\n\t * @param {MWParserEnvironment} env\n\t * @param {Node} node\n\t * @return {boolean}\n\t */\n\tstatic origSrcValidInEditedContext(env, node) {\n\t\tvar prev;\n\n\t\tif (WTUtils.isRedirectLink(node)) {\n\t\t\treturn DOMUtils.isBody(node.parentNode) && !node.previousSibling;\n\t\t} else if (node.nodeName === 'TH' || node.nodeName === 'TD') {\n\t\t\t// The wikitext representation for them is dependent\n\t\t\t// on cell position (first cell is always single char).\n\n\t\t\t// If there is no previous sibling, nothing to worry about.\n\t\t\tprev = node.previousSibling;\n\t\t\tif (!prev) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// If previous sibling is unmodified, nothing to worry about.\n\t\t\tif (!DOMUtils.isDiffMarker(prev) &&\n\t\t\t\t!DiffUtils.hasInsertedDiffMark(prev, env) &&\n\t\t\t\t!DiffUtils.directChildrenChanged(prev, env)) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\t// If it didn't have a stx marker that indicated that the cell\n\t\t\t// showed up on the same line via the \"||\" or \"!!\" syntax, nothing\n\t\t\t// to worry about.\n\t\t\treturn DOMDataUtils.getDataParsoid(node).stx !== 'row';\n\t\t} else if (node.nodeName === 'TR' && !DOMDataUtils.getDataParsoid(node).startTagSrc) {\n\t\t\t// If this <tr> didn't have a startTagSrc, it would have been\n\t\t\t// the first row of a table in original wikitext. So, it is safe\n\t\t\t// to reuse the original source for the row (without a \"|-\") as long as\n\t\t\t// it continues to be the first row of the table.  If not, since we need to\n\t\t\t// insert a \"|-\" to separate it from the newly added row (in an edit),\n\t\t\t// we cannot simply reuse orig. wikitext for this <tr>.\n\t\t\treturn !DOMUtils.previousNonSepSibling(node);\n\t\t} else if (DOMUtils.isNestedListOrListItem(node)) {\n\t\t\t// If there are no previous siblings, bullets were assigned to\n\t\t\t// containing elements in the ext.core.ListHandler. For example,\n\t\t\t//\n\t\t\t//   *** a\n\t\t\t//\n\t\t\t// Will assign bullets as,\n\t\t\t//\n\t\t\t//   <ul><li-*>\n\t\t\t//     <ul><li-*>\n\t\t\t//       <ul><li-*> a</li></ul>\n\t\t\t//     </li></ul>\n\t\t\t//   </li></ul>\n\t\t\t//\n\t\t\t// If we reuse the src for the inner li with the a, we'd be missing\n\t\t\t// two bullets because the tag handler for lists in the serializer only\n\t\t\t// emits start tag src when it hits a first child that isn't a list\n\t\t\t// element. We need to walk up and get them.\n\t\t\tprev = node.previousSibling;\n\t\t\tif (!prev) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// If a previous sibling was modified, we can't reuse the start dsr.\n\t\t\twhile (prev) {\n\t\t\t\tif (DOMUtils.isDiffMarker(prev) ||\n\t\t\t\t\tDiffUtils.hasInsertedDiffMark(prev, env)\n\t\t\t\t) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tprev = prev.previousSibling;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t/**\n\t * @param {Object} dataMw\n\t * @param {string} key\n\t * @param {boolean} keep\n\t * @return {Array|null}\n\t */\n\tstatic getAttrFromDataMw(dataMw, key, keep) {\n\t\tconst arr = dataMw.attribs || [];\n\t\tconst i = arr.findIndex(a => (a[0] === key || a[0].txt === key));\n\t\tif (i < 0) {\n\t\t\treturn null;\n\t\t}\n\t\tconst ret = arr[i];\n\t\tif (!keep && ret[1].html === undefined) {\n\t\t\tarr.splice(i, 1);\n\t\t}\n\t\treturn ret;\n\t}\n}\n\nif (typeof module === \"object\") {\n\tmodule.exports.WTSUtils = WTSUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/ContentUtils.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/DOMDataUtils.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/DOMTraverser.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/DOMUtils.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/Diff.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":142,"column":24,"nodeType":"Literal","endLine":142,"endColumn":68}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Diff tools.\n *\n * @module\n */\n\n'use strict';\n\nvar simpleDiff = require('simplediff');\n\nvar Util = require('./Util.js').Util;\n\n/** @namespace */\nvar Diff = {};\n\n/**\n * @param diff\n * @param srcLengths\n * @param outLengths\n */\nDiff.convertDiffToOffsetPairs = function(diff, srcLengths, outLengths) {\n\tvar currentPair;\n\tvar pairs = [];\n\tvar srcOff = 0;\n\tvar outOff = 0;\n\tvar srcIndex = 0;\n\tvar outIndex = 0;\n\tdiff.forEach(function(change) {\n\t\tvar pushPair = function(pair, start) {\n\t\t\tif (!pair.added) {\n\t\t\t\tpair.added = { start: start, end: start };\n\t\t\t} else if (!pair.removed) {\n\t\t\t\tpair.removed = { start: start, end: start };\n\t\t\t}\n\t\t\tpairs.push([ pair.removed, pair.added ]);\n\t\t\tcurrentPair = {};\n\t\t};\n\n\t\t// Use original line lengths;\n\t\tvar srcLen = 0;\n\t\tvar outLen = 0;\n\t\tchange[1].forEach(function() {\n\t\t\tif (change[0] === '+') {\n\t\t\t\toutLen += outLengths[outIndex];\n\t\t\t\toutIndex++;\n\t\t\t} else if (change[0] === '-') {\n\t\t\t\tsrcLen += srcLengths[srcIndex];\n\t\t\t\tsrcIndex++;\n\t\t\t} else {\n\t\t\t\tsrcLen += srcLengths[srcIndex];\n\t\t\t\toutLen += outLengths[outIndex];\n\t\t\t\tsrcIndex++;\n\t\t\t\toutIndex++;\n\t\t\t}\n\t\t});\n\n\t\tif (!currentPair) {\n\t\t\tcurrentPair = {};\n\t\t}\n\n\t\tif (change[0] === '+') {\n\t\t\tif (currentPair.added) {\n\t\t\t\tpushPair(currentPair, srcOff); // srcOff used for adding pair.removed\n\t\t\t}\n\n\t\t\tcurrentPair.added = { start: outOff };\n\t\t\toutOff += outLen;\n\t\t\tcurrentPair.added.end = outOff;\n\n\t\t\tif (currentPair.removed) {\n\t\t\t\tpushPair(currentPair);\n\t\t\t}\n\t\t} else if (change[0] === '-') {\n\t\t\tif (currentPair.removed) {\n\t\t\t\tpushPair(currentPair, outOff); // outOff used for adding pair.added\n\t\t\t}\n\n\t\t\tcurrentPair.removed = { start: srcOff };\n\t\t\tsrcOff += srcLen;\n\t\t\tcurrentPair.removed.end = srcOff;\n\n\t\t\tif (currentPair.added) {\n\t\t\t\tpushPair(currentPair);\n\t\t\t}\n\t\t} else {\n\t\t\tif (currentPair.added || currentPair.removed) {\n\t\t\t\tpushPair(currentPair, currentPair.added ? srcOff : outOff);\n\t\t\t}\n\n\t\t\tsrcOff += srcLen;\n\t\t\toutOff += outLen;\n\t\t}\n\t});\n\treturn pairs;\n};\n\n/**\n * @param changes\n */\nDiff.convertChangesToXML = function(changes) {\n\tvar result = [];\n\tfor (var i = 0; i < changes.length; i++) {\n\t\tvar change = changes[i];\n\t\tif (change[0] === '+') {\n\t\t\tresult.push('<ins>');\n\t\t} else if (change[0] === '-') {\n\t\t\tresult.push('<del>');\n\t\t}\n\n\t\tresult.push(Util.escapeHtml(change[1].join('')));\n\n\t\tif (change[0] === '+') {\n\t\t\tresult.push('</ins>');\n\t\t} else if (change[0] === '-') {\n\t\t\tresult.push('</del>');\n\t\t}\n\t}\n\treturn result.join('');\n};\n\nvar diffTokens = function(oldString, newString, tokenize) {\n\tif (oldString === newString) {\n\t\treturn [['=', [newString]]];\n\t} else {\n\t\treturn simpleDiff.diff(tokenize(oldString), tokenize(newString));\n\t}\n};\n\n/**\n * @param oldString\n * @param newString\n */\nDiff.diffWords = function(oldString, newString) {\n\t// This is a complicated regexp, but it improves on the naive \\b by:\n\t// * keeping tag-like things (<pre>, <a, </a>, etc) together\n\t// * keeping possessives and contractions (don't, etc) together\n\t// * ensuring that newlines always stay separate, so we don't\n\t//   have diff chunks that contain multiple newlines\n\t//   (ie, \"remove \\n\\n\" followed by \"add \\n\", instead of\n\t//   \"keep \\n\", \"remove \\n\")\n\tvar wordTokenize =\n\t\tvalue => value.split(/((?:<\\/?)?\\w+(?:'\\w+|>)?|\\s(?:(?!\\n)\\s)*)/g).filter(\n\t\t\t// For efficiency, filter out zero-length strings from token list\n\t\t\t// UGLY HACK: simplediff trips if one of tokenized words is\n\t\t\t// 'constructor'. Since this failure breaks parserTests.js runs,\n\t\t\t// work around that by hiding that diff for now.\n\t\t\ts => s !== '' && s !== 'constructor'\n\t\t);\n\treturn diffTokens(oldString, newString, wordTokenize);\n};\n\n/**\n * @param oldString\n * @param newString\n */\nDiff.diffLines = function(oldString, newString) {\n\tvar lineTokenize = function(value) {\n\t\treturn value.split(/^/m).map(function(line) {\n\t\t\treturn line.replace(/\\r$/g, '\\n');\n\t\t});\n\t};\n\treturn diffTokens(oldString, newString, lineTokenize);\n};\n\n/**\n * @param a\n * @param b\n * @param options\n */\nDiff.colorDiff = function(a, b, options) {\n\tconst context = options && options.context;\n\tlet diffs = 0;\n\tlet buf = '';\n\tlet before = '';\n\tconst visibleWs = s => s.replace(/[ \\xA0]/g,'\\u2423');\n\tconst funcs = (options && options.html) ? {\n\t\t'+': s => '<font color=\"green\">' + Util.escapeHtml(visibleWs(s)) + '</font>',\n\t\t'-': s => '<font color=\"red\">' + Util.escapeHtml(visibleWs(s)) + '</font>',\n\t\t'=': s => Util.escapeHtml(s),\n\t} : (options && options.noColor) ? {\n\t\t'+': s => '{+' + s + '+}',\n\t\t'-': s => '{-' + s + '-}',\n\t\t'=': s => s,\n\t} : {\n\t\t// add '' to workaround color bug; make spaces visible\n\t\t'+': s => visibleWs(s).green + '',\n\t\t'-': s => visibleWs(s).red + '',\n\t\t'=': s => s,\n\t};\n\tconst NL = (options && options.html) ? '<br/>\\n' : '\\n';\n\tconst DIFFSEP = (options && options.separator) || NL;\n\tconst visibleNL = '\\u21b5';\n\tfor (const change of Diff.diffWords(a, b)) {\n\t\tconst op = change[0];\n\t\tconst value = change[1].join('');\n\t\tif (op !== '=') {\n\t\t\tdiffs++;\n\t\t\tbuf += before;\n\t\t\tbefore = '';\n\t\t\tbuf += value.split('\\n').map((s,i,arr) => {\n\t\t\t\tif (i !== (arr.length - 1)) {\n\t\t\t\t\ts += visibleNL;\n\t\t\t\t}\n\t\t\t\treturn s ? funcs[op](s) : s;\n\t\t\t}).join(NL);\n\t\t} else {\n\t\t\tif (context) {\n\t\t\t\tconst lines = value.split('\\n');\n\t\t\t\tif (lines.length > 2 * (context + 1)) {\n\t\t\t\t\tconst first = lines.slice(0, context + 1).join(NL);\n\t\t\t\t\tconst last = lines.slice(lines.length - context - 1).join(NL);\n\t\t\t\t\tif (diffs > 0) {\n\t\t\t\t\t\tbuf += first + NL;\n\t\t\t\t\t}\n\t\t\t\t\tbefore = (diffs > 0 ? DIFFSEP : '') + last;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbuf += value;\n\t\t}\n\t}\n\tif (options && options.diffCount) {\n\t\treturn { count: diffs, output: buf };\n\t}\n\treturn (diffs > 0) ? buf : '';\n};\n\n/**\n * This is essentially lifted from jsDiff@1.4.0, but using our diff and\n * without the header and no newline warning.\n *\n * @param diff\n * @private\n */\nvar createPatch = function(diff) {\n\tvar ret = [];\n\n\tdiff.push({ value: '', lines: [] });  // Append an empty value to make cleanup easier\n\n\t// Formats a given set of lines for printing as context lines in a patch\n\tfunction contextLines(lines) {\n\t\treturn lines.map(function(entry) {\n\t\t\treturn ' ' + entry;\n\t\t});\n\t}\n\n\tvar oldRangeStart = 0;\n\tvar newRangeStart = 0;\n\tvar curRange = [];\n\tvar oldLine = 1;\n\tvar newLine = 1;\n\n\tfor (var i = 0; i < diff.length; i++) {\n\t\tvar current = diff[i];\n\t\tvar lines = current.lines || current.value.replace(/\\n$/, '').split('\\n');\n\t\tcurrent.lines = lines;\n\n\t\tif (current.added || current.removed) {\n\t\t\t// If we have previous context, start with that\n\t\t\tif (!oldRangeStart) {\n\t\t\t\tvar prev = diff[i - 1];\n\t\t\t\toldRangeStart = oldLine;\n\t\t\t\tnewRangeStart = newLine;\n\n\t\t\t\tif (prev) {\n\t\t\t\t\tcurRange = contextLines(prev.lines.slice(-4));\n\t\t\t\t\toldRangeStart -= curRange.length;\n\t\t\t\t\tnewRangeStart -= curRange.length;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Output our changes\n\t\t\tcurRange.push.apply(curRange, lines.map(function(entry) {\n\t\t\t\treturn (current.added ? '+' : '-') + entry;\n\t\t\t}));\n\n\t\t\t// Track the updated file position\n\t\t\tif (current.added) {\n\t\t\t\tnewLine += lines.length;\n\t\t\t} else {\n\t\t\t\toldLine += lines.length;\n\t\t\t}\n\t\t} else {\n\t\t\t// Identical context lines. Track line changes\n\t\t\tif (oldRangeStart) {\n\t\t\t\t// Close out any changes that have been output (or join overlapping)\n\t\t\t\tif (lines.length <= 8 && i < diff.length - 2) {\n\t\t\t\t\t// Overlapping\n\t\t\t\t\tcurRange.push.apply(curRange, contextLines(lines));\n\t\t\t\t} else {\n\t\t\t\t\t// end the range and output\n\t\t\t\t\tvar contextSize = Math.min(lines.length, 4);\n\t\t\t\t\tret.push(\n\t\t\t\t\t\t'@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize)\n\t\t\t\t\t\t+ ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize)\n\t\t\t\t\t\t+ ' @@');\n\t\t\t\t\tret.push.apply(ret, curRange);\n\t\t\t\t\tret.push.apply(ret, contextLines(lines.slice(0, contextSize)));\n\n\t\t\t\t\toldRangeStart = 0;\n\t\t\t\t\tnewRangeStart = 0;\n\t\t\t\t\tcurRange = [];\n\t\t\t\t}\n\t\t\t}\n\t\t\toldLine += lines.length;\n\t\t\tnewLine += lines.length;\n\t\t}\n\t}\n\n\treturn ret.join('\\n') + '\\n';\n};\n\n/**\n * @param a\n * @param b\n */\nDiff.patchDiff = function(a, b) {\n\t// Essentially lifted from jsDiff@1.4.0's PatchDiff.tokenize\n\tvar patchTokenize = function(value) {\n\t\tvar ret = [];\n\t\tvar linesAndNewlines = value.split(/(\\n|\\r\\n)/);\n\t\t// Ignore the final empty token that occurs if the string ends with a new line\n\t\tif (!linesAndNewlines[linesAndNewlines.length - 1]) {\n\t\t\tlinesAndNewlines.pop();\n\t\t}\n\t\t// Merge the content and line separators into single tokens\n\t\tfor (var i = 0; i < linesAndNewlines.length; i++) {\n\t\t\tvar line = linesAndNewlines[i];\n\t\t\tif (i % 2) {\n\t\t\t\tret[ret.length - 1] += line;\n\t\t\t} else {\n\t\t\t\tret.push(line);\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t};\n\tvar diffs = 0;\n\tvar diff = diffTokens(a, b, patchTokenize)\n\t.map(function(change) {\n\t\tvar value = change[1].join('');\n\t\tswitch (change[0]) {\n\t\t\tcase '+':\n\t\t\t\tdiffs++;\n\t\t\t\treturn { value: value, added: true };\n\t\t\tcase '-':\n\t\t\t\tdiffs++;\n\t\t\t\treturn { value: value, removed: true };\n\t\t\tdefault:\n\t\t\t\treturn { value: value };\n\t\t}\n\t});\n\tif (!diffs) {\n\t\treturn null;\n\t}\n\treturn createPatch(diff);\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.Diff = Diff;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/TokenUtils.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":26,"column":10,"nodeType":"Literal","endLine":26,"endColumn":58}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * @module\n */\n\n'use strict';\n\nrequire('../../core-upgrade.js');\n\nvar Consts = require('../config/WikitextConstants.js').WikitextConstants;\n\nvar TokenUtils = {\n\t/**\n\t * Determine if a tag is block-level or not.\n\t *\n\t * `<video>` is removed from block tags, since it can be phrasing content.\n\t * This is necessary for it to render inline.\n\t *\n\t * @param name\n\t */\n\tisBlockTag: function(name) {\n\t\tname = name.toUpperCase();\n\t\treturn name !== 'VIDEO' && Consts.HTML.HTML4BlockTags.has(name);\n\t},\n\n\tisDOMFragmentType: function(typeOf) {\n\t\treturn /(?:^|\\s)mw:DOMFragment(\\/sealed\\/\\w+)?(?=$|\\s)/.test(typeOf);\n\t},\n\n\t/** @property {RegExp} */\n\tsolTransparentLinkRegexp: /(?:^|\\s)mw:PageProp\\/(?:Category|redirect|Language)(?=$|\\s)/,\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.TokenUtils = TokenUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/Util.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":159,"column":10,"nodeType":"Literal","endLine":159,"endColumn":9539},{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":456,"column":25,"nodeType":"Literal","endLine":456,"endColumn":58}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * This file contains general utilities for token transforms.\n *\n * @module\n */\n\n'use strict';\n\nrequire('../../core-upgrade.js');\n\nvar crypto = require('crypto');\nvar entities = require('entities');\nvar Consts = require('../config/WikitextConstants.js').WikitextConstants;\n\n/**\n * @namespace\n */\nvar Util = {\n\n\t// Non-global and global versions of regexp for use everywhere\n\tCOMMENT_REGEXP: /<!--(?:[^-]|-(?!->))*-->/,\n\tCOMMENT_REGEXP_G: /<!--(?:[^-]|-(?!->))*-->/g,\n\n\t/**\n\t * Update only those properties that are undefined or null in the target.\n\t *\n\t * @param {Object} tgt The object to modify.\n\t * @param {...Object} subject The object to extend tgt with. Add more arguments to the function call to chain more extensions.\n\t * @return {Object} The modified object.\n\t */\n\textendProps: function(tgt, subject /* FIXME: use spread operator */) {\n\t\tfunction internalExtend(target, obj) {\n\t\t\tvar allKeys = [].concat(Object.keys(target), Object.keys(obj));\n\t\t\tfor (var i = 0, numKeys = allKeys.length; i < numKeys; i++) {\n\t\t\t\tvar k = allKeys[i];\n\t\t\t\tif (target[k] === undefined || target[k] === null) {\n\t\t\t\t\ttarget[k] = obj[k];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn target;\n\t\t}\n\t\tvar n = arguments.length;\n\t\tfor (var j = 1; j < n; j++) {\n\t\t\tinternalExtend(tgt, arguments[j]);\n\t\t}\n\t\treturn tgt;\n\t},\n\n\tstripParsoidIdPrefix: function(aboutId) {\n\t\t// 'mwt' is the prefix used for new ids in mediawiki.parser.environment#newObjectId\n\t\treturn aboutId.replace(/^#?mwt/, '');\n\t},\n\n\tisParsoidObjectId: function(aboutId) {\n\t\t// 'mwt' is the prefix used for new ids in mediawiki.parser.environment#newObjectId\n\t\treturn aboutId.match(/^#mwt/);\n\t},\n\n\t/**\n\t * Determine if the named tag is void (can not have content).\n\t *\n\t * @param name\n\t */\n\tisVoidElement: function(name) {\n\t\treturn Consts.HTML.VoidTags.has(name.toUpperCase());\n\t},\n\n\t// deep clones by default.\n\tclone: function(obj, deepClone) {\n\t\tif (deepClone === undefined) {\n\t\t\tdeepClone = true;\n\t\t}\n\t\tif (Array.isArray(obj)) {\n\t\t\tif (deepClone) {\n\t\t\t\treturn obj.map(function(el) {\n\t\t\t\t\treturn Util.clone(el, true);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\treturn obj.slice();\n\t\t\t}\n\t\t} else if (obj instanceof Object && // only \"plain objects\"\n\t\t\t\t\tObject.getPrototypeOf(obj) === Object.prototype) {\n\t\t\t/* This definition of \"plain object\" comes from jquery,\n\t\t\t * via zepto.js.  But this is really a big hack; we should\n\t\t\t * probably put a console.assert() here and more precisely\n\t\t\t * delimit what we think is legit to clone. (Hint: not\n\t\t\t * DOM trees.) */\n\t\t\tif (deepClone) {\n\t\t\t\treturn Object.keys(obj).reduce(function(nobj, key) {\n\t\t\t\t\tnobj[key] = Util.clone(obj[key], true);\n\t\t\t\t\treturn nobj;\n\t\t\t\t}, {});\n\t\t\t} else {\n\t\t\t\treturn Object.assign({}, obj);\n\t\t\t}\n\t\t} else {\n\t\t\treturn obj;\n\t\t}\n\t},\n\n\t// Just a copy `Util.clone` used in *testing* to reverse the effects of\n\t// freezing an object.  Works with more that just \"plain objects\"\n\tunFreeze: function(obj, deepClone) {\n\t\tif (deepClone === undefined) {\n\t\t\tdeepClone = true;\n\t\t}\n\t\tif (Array.isArray(obj)) {\n\t\t\tif (deepClone) {\n\t\t\t\treturn obj.map(function(el) {\n\t\t\t\t\treturn Util.unFreeze(el, true);\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\treturn obj.slice();\n\t\t\t}\n\t\t} else if (obj instanceof Object) {\n\t\t\tif (deepClone) {\n\t\t\t\treturn Object.keys(obj).reduce(function(nobj, key) {\n\t\t\t\t\tnobj[key] = Util.unFreeze(obj[key], true);\n\t\t\t\t\treturn nobj;\n\t\t\t\t}, new obj.constructor());\n\t\t\t} else {\n\t\t\t\treturn Object.assign({}, obj);\n\t\t\t}\n\t\t} else {\n\t\t\treturn obj;\n\t\t}\n\t},\n\n\t/**\n\t * Extract the last *unicode* character of the string.\n\t * This might be more than one javascript character, if the\n\t * last character is a martian.\n\t *\n\t * @param str\n\t * @param idx\n\t */\n\tlastUniChar: function(str, idx) {\n\t\tif (idx === undefined) {\n\t\t\tidx = str.length;\n\t\t}\n\t\tif (idx <= 0 || idx > str.length) {\n\t\t\treturn '';\n\t\t}\n\t\tlet s = str[--idx];\n\t\tif (/[\\uDC00-\\uDFFF]/.test(s)) {\n\t\t\ts = str[--idx] + s;\n\t\t}\n\t\treturn s;\n\t},\n\n\t// Arguably we shouldn't be using this; see:\n\t// https://phabricator.wikimedia.org/T238022#5665580\n\tisUniWord: function(c) {\n\t\ttry {\n\t\t\t// Have to hide this regexp from eslint (!)\n\t\t\treturn (new RegExp(\"^[\\\\p{L}\\\\p{N}_]\", \"u\")).test(c);\n\t\t} catch (e) { /* oh, well, we have to do this the hard way */ }\n\t\t// Courtesy of https://mothereff.in/regexpu for the above\n\t\treturn /^[0-9A-Z_a-z\\xAA\\xB2\\xB3\\xB5\\xB9\\xBA\\xBC-\\xBE\\xC0-\\xD6\\xD8-\\xF6\\xF8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0588\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u064A\\u0660-\\u0669\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07C0-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u0860-\\u086A\\u08A0-\\u08B4\\u08B6-\\u08BD\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0966-\\u096F\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09E6-\\u09F1\\u09F4-\\u09F9\\u09FC\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A66-\\u0A6F\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0AE6-\\u0AEF\\u0AF9\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B66-\\u0B6F\\u0B71-\\u0B77\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0BE6-\\u0BF2\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58-\\u0C5A\\u0C60\\u0C61\\u0C66-\\u0C6F\\u0C78-\\u0C7E\\u0C80\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CE6-\\u0CEF\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D54-\\u0D56\\u0D58-\\u0D61\\u0D66-\\u0D78\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0DE6-\\u0DEF\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E50-\\u0E59\\u0E81\\u0E82\\u0E84\\u0E86-\\u0E8A\\u0E8C-\\u0EA3\\u0EA5\\u0EA7-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0ED0-\\u0ED9\\u0EDC-\\u0EDF\\u0F00\\u0F20-\\u0F33\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F-\\u1049\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u1090-\\u1099\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1369-\\u137C\\u1380-\\u138F\\u13A0-\\u13F5\\u13F8-\\u13FD\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F8\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u17E0-\\u17E9\\u17F0-\\u17F9\\u1810-\\u1819\\u1820-\\u1878\\u1880-\\u1884\\u1887-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1946-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19B0-\\u19C9\\u19D0-\\u19DA\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B50-\\u1B59\\u1B83-\\u1BA0\\u1BAE-\\u1BE5\\u1C00-\\u1C23\\u1C40-\\u1C49\\u1C4D-\\u1C7D\\u1C80-\\u1C88\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1CE9-\\u1CEC\\u1CEE-\\u1CF3\\u1CF5\\u1CF6\\u1CFA\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2070\\u2071\\u2074-\\u2079\\u207F-\\u2089\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2150-\\u2189\\u2460-\\u249B\\u24EA-\\u24FF\\u2776-\\u2793\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2CFD\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312F\\u3131-\\u318E\\u3192-\\u3195\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3220-\\u3229\\u3248-\\u324F\\u3251-\\u325F\\u3280-\\u3289\\u32B1-\\u32BF\\u3400-\\u4DB5\\u4E00-\\u9FEF\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA7BF\\uA7C2-\\uA7C6\\uA7F7-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA830-\\uA835\\uA840-\\uA873\\uA882-\\uA8B3\\uA8D0-\\uA8D9\\uA8F2-\\uA8F7\\uA8FB\\uA8FD\\uA8FE\\uA900-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF-\\uA9D9\\uA9E0-\\uA9E4\\uA9E6-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA50-\\uAA59\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB67\\uAB70-\\uABE2\\uABF0-\\uABF9\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF10-\\uFF19\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC\\u{10000}-\\u{1000B}\\u{1000D}-\\u{10026}\\u{10028}-\\u{1003A}\\u{1003C}\\u{1003D}\\u{1003F}-\\u{1004D}\\u{10050}-\\u{1005D}\\u{10080}-\\u{100FA}\\u{10107}-\\u{10133}\\u{10140}-\\u{10178}\\u{1018A}\\u{1018B}\\u{10280}-\\u{1029C}\\u{102A0}-\\u{102D0}\\u{102E1}-\\u{102FB}\\u{10300}-\\u{10323}\\u{1032D}-\\u{1034A}\\u{10350}-\\u{10375}\\u{10380}-\\u{1039D}\\u{103A0}-\\u{103C3}\\u{103C8}-\\u{103CF}\\u{103D1}-\\u{103D5}\\u{10400}-\\u{1049D}\\u{104A0}-\\u{104A9}\\u{104B0}-\\u{104D3}\\u{104D8}-\\u{104FB}\\u{10500}-\\u{10527}\\u{10530}-\\u{10563}\\u{10600}-\\u{10736}\\u{10740}-\\u{10755}\\u{10760}-\\u{10767}\\u{10800}-\\u{10805}\\u{10808}\\u{1080A}-\\u{10835}\\u{10837}\\u{10838}\\u{1083C}\\u{1083F}-\\u{10855}\\u{10858}-\\u{10876}\\u{10879}-\\u{1089E}\\u{108A7}-\\u{108AF}\\u{108E0}-\\u{108F2}\\u{108F4}\\u{108F5}\\u{108FB}-\\u{1091B}\\u{10920}-\\u{10939}\\u{10980}-\\u{109B7}\\u{109BC}-\\u{109CF}\\u{109D2}-\\u{10A00}\\u{10A10}-\\u{10A13}\\u{10A15}-\\u{10A17}\\u{10A19}-\\u{10A35}\\u{10A40}-\\u{10A48}\\u{10A60}-\\u{10A7E}\\u{10A80}-\\u{10A9F}\\u{10AC0}-\\u{10AC7}\\u{10AC9}-\\u{10AE4}\\u{10AEB}-\\u{10AEF}\\u{10B00}-\\u{10B35}\\u{10B40}-\\u{10B55}\\u{10B58}-\\u{10B72}\\u{10B78}-\\u{10B91}\\u{10BA9}-\\u{10BAF}\\u{10C00}-\\u{10C48}\\u{10C80}-\\u{10CB2}\\u{10CC0}-\\u{10CF2}\\u{10CFA}-\\u{10D23}\\u{10D30}-\\u{10D39}\\u{10E60}-\\u{10E7E}\\u{10F00}-\\u{10F27}\\u{10F30}-\\u{10F45}\\u{10F51}-\\u{10F54}\\u{10FE0}-\\u{10FF6}\\u{11003}-\\u{11037}\\u{11052}-\\u{1106F}\\u{11083}-\\u{110AF}\\u{110D0}-\\u{110E8}\\u{110F0}-\\u{110F9}\\u{11103}-\\u{11126}\\u{11136}-\\u{1113F}\\u{11144}\\u{11150}-\\u{11172}\\u{11176}\\u{11183}-\\u{111B2}\\u{111C1}-\\u{111C4}\\u{111D0}-\\u{111DA}\\u{111DC}\\u{111E1}-\\u{111F4}\\u{11200}-\\u{11211}\\u{11213}-\\u{1122B}\\u{11280}-\\u{11286}\\u{11288}\\u{1128A}-\\u{1128D}\\u{1128F}-\\u{1129D}\\u{1129F}-\\u{112A8}\\u{112B0}-\\u{112DE}\\u{112F0}-\\u{112F9}\\u{11305}-\\u{1130C}\\u{1130F}\\u{11310}\\u{11313}-\\u{11328}\\u{1132A}-\\u{11330}\\u{11332}\\u{11333}\\u{11335}-\\u{11339}\\u{1133D}\\u{11350}\\u{1135D}-\\u{11361}\\u{11400}-\\u{11434}\\u{11447}-\\u{1144A}\\u{11450}-\\u{11459}\\u{1145F}\\u{11480}-\\u{114AF}\\u{114C4}\\u{114C5}\\u{114C7}\\u{114D0}-\\u{114D9}\\u{11580}-\\u{115AE}\\u{115D8}-\\u{115DB}\\u{11600}-\\u{1162F}\\u{11644}\\u{11650}-\\u{11659}\\u{11680}-\\u{116AA}\\u{116B8}\\u{116C0}-\\u{116C9}\\u{11700}-\\u{1171A}\\u{11730}-\\u{1173B}\\u{11800}-\\u{1182B}\\u{118A0}-\\u{118F2}\\u{118FF}\\u{119A0}-\\u{119A7}\\u{119AA}-\\u{119D0}\\u{119E1}\\u{119E3}\\u{11A00}\\u{11A0B}-\\u{11A32}\\u{11A3A}\\u{11A50}\\u{11A5C}-\\u{11A89}\\u{11A9D}\\u{11AC0}-\\u{11AF8}\\u{11C00}-\\u{11C08}\\u{11C0A}-\\u{11C2E}\\u{11C40}\\u{11C50}-\\u{11C6C}\\u{11C72}-\\u{11C8F}\\u{11D00}-\\u{11D06}\\u{11D08}\\u{11D09}\\u{11D0B}-\\u{11D30}\\u{11D46}\\u{11D50}-\\u{11D59}\\u{11D60}-\\u{11D65}\\u{11D67}\\u{11D68}\\u{11D6A}-\\u{11D89}\\u{11D98}\\u{11DA0}-\\u{11DA9}\\u{11EE0}-\\u{11EF2}\\u{11FC0}-\\u{11FD4}\\u{12000}-\\u{12399}\\u{12400}-\\u{1246E}\\u{12480}-\\u{12543}\\u{13000}-\\u{1342E}\\u{14400}-\\u{14646}\\u{16800}-\\u{16A38}\\u{16A40}-\\u{16A5E}\\u{16A60}-\\u{16A69}\\u{16AD0}-\\u{16AED}\\u{16B00}-\\u{16B2F}\\u{16B40}-\\u{16B43}\\u{16B50}-\\u{16B59}\\u{16B5B}-\\u{16B61}\\u{16B63}-\\u{16B77}\\u{16B7D}-\\u{16B8F}\\u{16E40}-\\u{16E96}\\u{16F00}-\\u{16F4A}\\u{16F50}\\u{16F93}-\\u{16F9F}\\u{16FE0}\\u{16FE1}\\u{16FE3}\\u{17000}-\\u{187F7}\\u{18800}-\\u{18AF2}\\u{1B000}-\\u{1B11E}\\u{1B150}-\\u{1B152}\\u{1B164}-\\u{1B167}\\u{1B170}-\\u{1B2FB}\\u{1BC00}-\\u{1BC6A}\\u{1BC70}-\\u{1BC7C}\\u{1BC80}-\\u{1BC88}\\u{1BC90}-\\u{1BC99}\\u{1D2E0}-\\u{1D2F3}\\u{1D360}-\\u{1D378}\\u{1D400}-\\u{1D454}\\u{1D456}-\\u{1D49C}\\u{1D49E}\\u{1D49F}\\u{1D4A2}\\u{1D4A5}\\u{1D4A6}\\u{1D4A9}-\\u{1D4AC}\\u{1D4AE}-\\u{1D4B9}\\u{1D4BB}\\u{1D4BD}-\\u{1D4C3}\\u{1D4C5}-\\u{1D505}\\u{1D507}-\\u{1D50A}\\u{1D50D}-\\u{1D514}\\u{1D516}-\\u{1D51C}\\u{1D51E}-\\u{1D539}\\u{1D53B}-\\u{1D53E}\\u{1D540}-\\u{1D544}\\u{1D546}\\u{1D54A}-\\u{1D550}\\u{1D552}-\\u{1D6A5}\\u{1D6A8}-\\u{1D6C0}\\u{1D6C2}-\\u{1D6DA}\\u{1D6DC}-\\u{1D6FA}\\u{1D6FC}-\\u{1D714}\\u{1D716}-\\u{1D734}\\u{1D736}-\\u{1D74E}\\u{1D750}-\\u{1D76E}\\u{1D770}-\\u{1D788}\\u{1D78A}-\\u{1D7A8}\\u{1D7AA}-\\u{1D7C2}\\u{1D7C4}-\\u{1D7CB}\\u{1D7CE}-\\u{1D7FF}\\u{1E100}-\\u{1E12C}\\u{1E137}-\\u{1E13D}\\u{1E140}-\\u{1E149}\\u{1E14E}\\u{1E2C0}-\\u{1E2EB}\\u{1E2F0}-\\u{1E2F9}\\u{1E800}-\\u{1E8C4}\\u{1E8C7}-\\u{1E8CF}\\u{1E900}-\\u{1E943}\\u{1E94B}\\u{1E950}-\\u{1E959}\\u{1EC71}-\\u{1ECAB}\\u{1ECAD}-\\u{1ECAF}\\u{1ECB1}-\\u{1ECB4}\\u{1ED01}-\\u{1ED2D}\\u{1ED2F}-\\u{1ED3D}\\u{1EE00}-\\u{1EE03}\\u{1EE05}-\\u{1EE1F}\\u{1EE21}\\u{1EE22}\\u{1EE24}\\u{1EE27}\\u{1EE29}-\\u{1EE32}\\u{1EE34}-\\u{1EE37}\\u{1EE39}\\u{1EE3B}\\u{1EE42}\\u{1EE47}\\u{1EE49}\\u{1EE4B}\\u{1EE4D}-\\u{1EE4F}\\u{1EE51}\\u{1EE52}\\u{1EE54}\\u{1EE57}\\u{1EE59}\\u{1EE5B}\\u{1EE5D}\\u{1EE5F}\\u{1EE61}\\u{1EE62}\\u{1EE64}\\u{1EE67}-\\u{1EE6A}\\u{1EE6C}-\\u{1EE72}\\u{1EE74}-\\u{1EE77}\\u{1EE79}-\\u{1EE7C}\\u{1EE7E}\\u{1EE80}-\\u{1EE89}\\u{1EE8B}-\\u{1EE9B}\\u{1EEA1}-\\u{1EEA3}\\u{1EEA5}-\\u{1EEA9}\\u{1EEAB}-\\u{1EEBB}\\u{1F100}-\\u{1F10C}\\u{20000}-\\u{2A6D6}\\u{2A700}-\\u{2B734}\\u{2B740}-\\u{2B81D}\\u{2B820}-\\u{2CEA1}\\u{2CEB0}-\\u{2EBE0}\\u{2F800}-\\u{2FA1D}]/u.test(c);\n\t},\n\n\t/**\n\t * Emulate PHP's trim, which is almost-but-not-quite like JS's trim.\n\t *\n\t * PHP: https://www.php.net/manual/en/function.trim.php\n\t *\n\t * JS: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim\n\t *\n\t * @param str\n\t */\n\tphpTrim: function(str) {\n\t\treturn str.replace(/(?:^[ \\t\\n\\r\\0\\x0B]+)|(?:[ \\t\\n\\r\\0\\x0B]+$)/g, '');\n\t},\n\n\t/**\n\t * Emulate PHP's urlencode by patching results of\n\t * JS's `encodeURIComponent`.\n\t *\n\t * PHP: https://secure.php.net/manual/en/function.urlencode.php\n\t *\n\t * JS:  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent\n\t *\n\t * Spaces to '+' is a PHP peculiarity as well.\n\t *\n\t * @param txt\n\t */\n\tphpURLEncode: function(txt) {\n\t\treturn encodeURIComponent(txt)\n\t\t\t.replace(/!/g, '%21')\n\t\t\t.replace(/'/g, '%27')\n\t\t\t.replace(/\\(/g, '%28')\n\t\t\t.replace(/\\)/g, '%29')\n\t\t\t.replace(/\\*/g, '%2A')\n\t\t\t.replace(/~/g, '%7E')\n\t\t\t.replace(/%20/g, '+');\n\t},\n\n\t/*\n\t * Wraps `decodeURI` in a try/catch to suppress throws from malformed URI\n\t * sequences.  Distinct from `decodeURIComponent` in that certain\n\t * sequences aren't decoded if they result in (un)reserved characters.\n\t */\n\tdecodeURI: function(s) {\n\t\t// Most of the time we should have valid input\n\t\ttry {\n\t\t\treturn decodeURI(s);\n\t\t} catch (e) {\n\t\t\t// Fall through\n\t\t}\n\n\t\t// Extract each encoded character and decode it individually\n\t\treturn s.replace(\n\t\t\t/%[0-7][0-9A-F]|%[CD][0-9A-F]%[89AB][0-9A-F]|%E[0-9A-F](?:%[89AB][0-9A-F]){2}|%F[0-4](?:%[89AB][0-9A-F]){3}/gi,\n\t\t\tfunction(m) {\n\t\t\t\ttry {\n\t\t\t\t\treturn decodeURI(m);\n\t\t\t\t} catch (e) {\n\t\t\t\t\treturn m;\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t},\n\n\t/*\n\t * Wraps `decodeURIComponent` in a try/catch to suppress throws from\n\t * malformed URI sequences.\n\t */\n\tdecodeURIComponent: function(s) {\n\t\t// Most of the time we should have valid input\n\t\ttry {\n\t\t\treturn decodeURIComponent(s);\n\t\t} catch (e) {\n\t\t\t// Fall through\n\t\t}\n\n\t\t// Extract each encoded character and decode it individually\n\t\treturn s.replace(\n\t\t\t/%[0-7][0-9A-F]|%[CD][0-9A-F]%[89AB][0-9A-F]|%E[0-9A-F](?:%[89AB][0-9A-F]){2}|%F[0-4](?:%[89AB][0-9A-F]){3}/gi,\n\t\t\tfunction(m) {\n\t\t\t\ttry {\n\t\t\t\t\treturn decodeURIComponent(m);\n\t\t\t\t} catch (e) {\n\t\t\t\t\treturn m;\n\t\t\t\t}\n\t\t\t}\n\t\t);\n\t},\n\n\textractExtBody: function(token) {\n\t\tvar extSrc = token.getAttribute('source');\n\t\tvar extTagOffsets = token.dataAttribs.extTagOffsets;\n\t\treturn extSrc.slice(extTagOffsets[2], -extTagOffsets[3]);\n\t},\n\n\tisValidDSR: function(dsr, all) {\n\t\tconst isValidOffset = n => typeof (n) === 'number' && n >= 0;\n\t\treturn dsr &&\n\t\t\tisValidOffset(dsr[0]) && isValidOffset(dsr[1]) &&\n\t\t\t(!all || (isValidOffset(dsr[2]) && isValidOffset(dsr[3])));\n\t},\n\n\t/**\n\t * Quickly hash an array or string.\n\t *\n\t * @param {Array|string} arr\n\t */\n\tmakeHash: function(arr) {\n\t\tvar md5 = crypto.createHash('MD5');\n\t\tvar i;\n\t\tif (Array.isArray(arr)) {\n\t\t\tfor (i = 0; i < arr.length; i++) {\n\t\t\t\tif (arr[i] instanceof String) {\n\t\t\t\t\tmd5.update(arr[i]);\n\t\t\t\t} else {\n\t\t\t\t\tmd5.update(arr[i].toString());\n\t\t\t\t}\n\t\t\t\tmd5.update(\"\\0\");\n\t\t\t}\n\t\t} else {\n\t\t\tmd5.update(arr);\n\t\t}\n\t\treturn md5.digest('hex');\n\t},\n\n\t/**\n\t * Cannonicalizes a namespace name.\n\t *\n\t * Used by {@link WikiConfig}.\n\t *\n\t * @param {string} name Non-normalized namespace name.\n\t * @return {string}\n\t */\n\tnormalizeNamespaceName: function(name) {\n\t\treturn name.toLowerCase().replace(' ', '_');\n\t},\n\n\t/**\n\t * Compare two titles for equality.\n\t *\n\t * @param {Title} t1\n\t * @param {Title} t2\n\t * @return {boolean}\n\t */\n\ttitleEquals: function(t1, t2) {\n\t\t// See: https://github.com/wikimedia/mediawiki-title/pull/43\n\t\treturn (t1 === t2) || (\n\t\t\tt1 !== null && t2 !== null && t1.getKey() === t2.getKey() &&\n\t\t\tUtil.namespaceEquals(t1.getNamespace(), t2.getNamespace())\n\t\t);\n\t},\n\n\t/**\n\t * Compare two namespaces for equality.\n\t *\n\t * @param {Namespace} n1\n\t * @param {Namespace} n2\n\t * @return {boolean}\n\t */\n\tnamespaceEquals: function(n1, n2) {\n\t\t// We shouldn't have to access the private _id field of namespace :(\n\t\t// See: https://github.com/wikimedia/mediawiki-title/pull/43\n\t\treturn (n1 === n2) || (n1 !== null && n2 !== null && n1._id === n2._id);\n\t},\n\n\t/**\n\t * Decode HTML5 entities in wikitext.\n\t *\n\t * NOTE that wikitext only allows semicolon-terminated entities, while\n\t * HTML allows a number of \"legacy\" entities to be decoded without\n\t * a terminating semicolon.  This function deliberately does not\n\t * decode these HTML-only entity forms.\n\t *\n\t * @param {string} text\n\t * @return {string}\n\t */\n\tdecodeWtEntities: function(text) {\n\t\t// HTML5 allows semicolon-less entities which wikitext does not:\n\t\t// in wikitext all entities must end in a semicolon.\n\t\treturn text.replace(\n\t\t\t/&[#0-9a-zA-Z]+;/g,\n\t\t\t(match) => {\n\t\t\t\t// Be careful: `&ampamp;` can get through the above, which\n\t\t\t\t// decodeHTML5 will decode to `&amp;` -- but that's a sneaky\n\t\t\t\t// semicolon-less entity!\n\t\t\t\tconst m = /^&#(?:x([A-Fa-f0-9]+)|(\\d+));$/.exec(match);\n\t\t\t\tlet c, cp;\n\t\t\t\tif (m) {\n\t\t\t\t\t// entities contains a bunch of weird legacy mappings\n\t\t\t\t\t// for numeric codepoints (T113194) which we don't want.\n\t\t\t\t\tif (m[1]) {\n\t\t\t\t\t\tcp = Number.parseInt(m[1], 16);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcp = Number.parseInt(m[2], 10);\n\t\t\t\t\t}\n\t\t\t\t\tif (cp > 0x10FFFF) {\n\t\t\t\t\t\t// Invalid entity, don't give to String.fromCodePoint\n\t\t\t\t\t\treturn match;\n\t\t\t\t\t}\n\t\t\t\t\tc = String.fromCodePoint(cp);\n\t\t\t\t} else {\n\t\t\t\t\tc = entities.decodeHTML5(match);\n\t\t\t\t\t// Length can be legit greater than one if it is astral\n\t\t\t\t\tif (c.length > 1 && c.endsWith(';')) {\n\t\t\t\t\t\t// Invalid entity!\n\t\t\t\t\t\treturn match;\n\t\t\t\t\t}\n\t\t\t\t\tcp = c.codePointAt(0);\n\t\t\t\t}\n\t\t\t\t// Check other banned codepoints (T106578)\n\t\t\t\tif (\n\t\t\t\t\t(cp < 0x09) ||\n\t\t\t\t\t(cp > 0x0A && cp < 0x20) ||\n\t\t\t\t\t(cp > 0x7E && cp < 0xA0) ||\n\t\t\t\t\t(cp > 0xD7FF && cp < 0xE000) ||\n\t\t\t\t\t(cp > 0xFFFD && cp < 0x10000) ||\n\t\t\t\t\t(cp > 0x10FFFF)\n\t\t\t\t) {\n\t\t\t\t\t// Invalid entity!\n\t\t\t\t\treturn match;\n\t\t\t\t}\n\t\t\t\treturn c;\n\t\t\t}\n\t\t);\n\t},\n\n\t/**\n\t * Entity-escape anything that would decode to a valid wikitext entity.\n\t *\n\t * Note that HTML5 allows certain \"semicolon-less\" entities, like\n\t * `&para`; these aren't allowed in wikitext and won't be escaped\n\t * by this function.\n\t *\n\t * @param {string} text\n\t * @return {string}\n\t */\n\tescapeWtEntities: function(text) {\n\t\t// [CSA] replace with entities.encode( text, 2 )?\n\t\t// but that would encode *all* ampersands, where we apparently just want\n\t\t// to encode ampersands that precede valid entities.\n\t\treturn text.replace(/&[#0-9a-zA-Z]+;/g, function(match) {\n\t\t\tvar decodedChar = Util.decodeWtEntities(match);\n\t\t\tif (decodedChar !== match) {\n\t\t\t\t// Escape the ampersand\n\t\t\t\treturn '&amp;' + match.slice(1);\n\t\t\t} else {\n\t\t\t\t// Not an entity, just return the string\n\t\t\t\treturn match;\n\t\t\t}\n\t\t});\n\t},\n\n\tescapeHtml: function(s) {\n\t\treturn s.replace(/[\"'&<>]/g, entities.encodeHTML5);\n\t},\n\n\t/**\n\t * Encode all characters as entity references.  This is done to make\n\t * characters safe for wikitext (regardless of whether they are\n\t * HTML-safe).\n\t *\n\t * @param {string} s\n\t * @return {string}\n\t */\n\tentityEncodeAll: function(s) {\n\t\t// this is surrogate-aware\n\t\treturn Array.from(s).map(function(c) {\n\t\t\tc = c.codePointAt(0).toString(16).toUpperCase();\n\t\t\tif (c.length === 1) {\n\t\t\t\tc = '0' + c;\n\t\t\t} // convention\n\t\t\tif (c === 'A0') {\n\t\t\t\treturn '&nbsp;';\n\t\t\t} // special-case common usage\n\t\t\treturn '&#x' + c + ';';\n\t\t}).join('');\n\t},\n\n\t/**\n\t * Determine whether the protocol of a link is potentially valid. Use the\n\t * environment's per-wiki config to do so.\n\t *\n\t * @param linkTarget\n\t * @param env\n\t */\n\tisProtocolValid: function(linkTarget, env) {\n\t\tvar wikiConf = env.conf.wiki;\n\t\tif (typeof linkTarget === 'string') {\n\t\t\treturn wikiConf.hasValidProtocol(linkTarget);\n\t\t} else {\n\t\t\treturn true;\n\t\t}\n\t},\n\n\tparseMediaDimensions: function(str, onlyOne) {\n\t\tvar dimensions = null;\n\t\tvar match = str.match(/^(\\d*)(?:x(\\d+))?\\s*(?:px\\s*)?$/);\n\t\tif (match) {\n\t\t\tdimensions = { x: undefined, y: undefined };\n\t\t\tif (match[1].length) {\n\t\t\t\tdimensions.x = Number(match[1]);\n\t\t\t}\n\t\t\tif (match[2] !== undefined) {\n\t\t\t\tif (onlyOne) {\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tdimensions.y = Number(match[2]);\n\t\t\t}\n\t\t}\n\t\treturn dimensions;\n\t},\n\n\t// More generally, this is defined by the media handler in core\n\tvalidateMediaParam: function(num) {\n\t\treturn num !== null && num !== undefined && num > 0;\n\t},\n\n\t// Extract content in a backwards compatible way\n\tgetStar: function(revision) {\n\t\tvar content = revision;\n\t\tif (revision && revision.slots) {\n\t\t\tcontent = revision.slots.main;\n\t\t}\n\t\treturn content;\n\t},\n\n\t/**\n\t * Magic words masquerading as templates.\n\t *\n\t * @property {Set}\n\t */\n\tmagicMasqs: new Set([\"defaultsort\", \"displaytitle\"]),\n\n\t/**\n\t * This regex was generated by running through *all unicode characters* and\n\t * testing them against *all regexes* for linktrails in a default MW install.\n\t * We had to treat it a little bit, here's what we changed:\n\t *\n\t * 1. A-Z, though allowed in Walloon, is disallowed.\n\t * 2. '\"', though allowed in Chuvash, is disallowed.\n\t * 3. '-', though allowed in Icelandic (possibly due to a bug), is disallowed.\n\t * 4. '1', though allowed in Lak (possibly due to a bug), is disallowed.\n\t *\n\t * @property {RegExp}\n\t */\n\tlinkTrailRegex: new RegExp(\n\t\t'^[^\\0-`{÷ĀĈ-ČĎĐĒĔĖĚĜĝĠ-ĪĬ-įIJĴ-ĹĻ-ĽĿŀŅņʼnŊŌŎŏŒŔŖ-ŘŜŝŠŤŦŨŪ-ŬŮŲ-ŴŶŸ' +\n\t\t'ſ-ǤǦǨǪ-Ǯǰ-ȗȜ-ȞȠ-ɘɚ-ʑʓ-ʸʽ-̂̄-΅·΋΍΢Ϗ-ЯѐѝѠѢѤѦѨѪѬѮѰѲѴѶѸѺ-ѾҀ-҃҅-ҐҒҔҕҘҚҜ-ҠҤ-ҪҬҭҰҲ' +\n\t\t'Ҵ-ҶҸҹҼ-ҿӁ-ӗӚ-ӜӞӠ-ӢӤӦӪ-ӲӴӶ-ՠֈ-׏׫-ؠً-ٳٵ-ٽٿ-څڇ-ڗڙ-ڨڪ-ڬڮڰ-ڽڿ-ۅۈ-ۊۍ-۔ۖ-਀਄਋-਎਑਒' +\n\t\t'਩਱਴਷਺਻਽੃-੆੉੊੎-੘੝੟-੯ੴ-჏ჱ-ẼẾ-\\u200b\\u200d-‒—-‗‚‛”--\\ufffd]+$'),\n\n\t/**\n\t * Check whether some text is a valid link trail.\n\t *\n\t * @param {string} text\n\t * @return {boolean}\n\t */\n\tisLinkTrail: function(text) {\n\t\tif (text && text.match && text.match(this.linkTrailRegex)) {\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t},\n\n\t/**\n\t * Convert mediawiki-format language code to a BCP47-compliant language\n\t * code suitable for including in HTML.  See\n\t * `GlobalFunctions.php::wfBCP47()` in MediaWiki sources.\n\t *\n\t * @param {string} code MediaWiki language code.\n\t * @return {string} BCP47 language code.\n\t */\n\tbcp47: function(code) {\n\t\tvar codeSegment = code.split('-');\n\t\tvar codeBCP = [];\n\t\tcodeSegment.forEach(function(seg, segNo) {\n\t\t\t// When previous segment is x, it is a private segment and should be lc\n\t\t\tif (segNo > 0 && /^x$/i.test(codeSegment[segNo - 1])) {\n\t\t\t\tcodeBCP[segNo] = seg.toLowerCase();\n\t\t\t// ISO 3166 country code\n\t\t\t} else if (seg.length === 2 && segNo > 0) {\n\t\t\t\tcodeBCP[segNo] = seg.toUpperCase();\n\t\t\t// ISO 15924 script code\n\t\t\t} else if (seg.length === 4 && segNo > 0) {\n\t\t\t\tcodeBCP[segNo] = seg[0].toUpperCase() + seg.slice(1).toLowerCase();\n\t\t\t// Use lowercase for other cases\n\t\t\t} else {\n\t\t\t\tcodeBCP[segNo] = seg.toLowerCase();\n\t\t\t}\n\t\t});\n\t\treturn codeBCP.join('-');\n\t},\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.Util = Util;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/WTUtils.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":137,"column":37,"nodeType":"Literal","endLine":137,"endColumn":65}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * These utilites pertain to extracting / modifying wikitext information from the DOM.\n *\n * @module\n */\n\n'use strict';\n\nconst Consts = require('../config/WikitextConstants.js').WikitextConstants;\nconst { DOMDataUtils } = require('./DOMDataUtils.js');\nconst { DOMUtils } = require('./DOMUtils.js');\nconst { JSUtils } = require('./jsutils.js');\nconst { TokenUtils } = require('./TokenUtils.js');\nconst { Util } = require('./Util.js');\n\nconst lastItem = JSUtils.lastItem;\n\n/**\n * Regexp for checking marker metas typeofs representing\n * transclusion markup or template param markup.\n *\n * @property {RegExp}\n */\nconst TPL_META_TYPE_REGEXP = /^mw:(?:Transclusion|Param)(?:\\/End)?$/;\n\nclass WTUtils {\n\n\t/**\n\t * Check whether a node's data-parsoid object includes\n\t * an indicator that the original wikitext was a literal\n\t * HTML element (like table or p).\n\t *\n\t * @param {Object} dp\n\t *   @param {string|undefined} [dp.stx]\n\t */\n\tstatic hasLiteralHTMLMarker(dp) {\n\t\treturn dp.stx === 'html';\n\t}\n\n\t/**\n\t * Run a node through {@link #hasLiteralHTMLMarker}.\n\t *\n\t * @param node\n\t */\n\tstatic isLiteralHTMLNode(node) {\n\t\treturn (node &&\n\t\t\tDOMUtils.isElt(node) &&\n\t\t\tthis.hasLiteralHTMLMarker(DOMDataUtils.getDataParsoid(node)));\n\t}\n\n\tstatic isZeroWidthWikitextElt(node) {\n\t\treturn Consts.ZeroWidthWikitextTags.has(node.nodeName) &&\n\t\t\t!this.isLiteralHTMLNode(node);\n\t}\n\n\t/**\n\t * Is `node` a block node that is also visible in wikitext?\n\t * An example of an invisible block node is a `<p>`-tag that\n\t * Parsoid generated, or a `<ul>`, `<ol>` tag.\n\t *\n\t * @param {Node} node\n\t */\n\tstatic isBlockNodeWithVisibleWT(node) {\n\t\treturn DOMUtils.isBlockNode(node) && !this.isZeroWidthWikitextElt(node);\n\t}\n\n\t/**\n\t * Helper functions to detect when an A-node uses [[..]]/[..]/... style\n\t * syntax (for wikilinks, ext links, url links). rel-type is not sufficient\n\t * anymore since mw:ExtLink is used for all the three link syntaxes.\n\t *\n\t * @param aNode\n\t * @param dp\n\t */\n\tstatic usesWikiLinkSyntax(aNode, dp) {\n\t\tif (dp === undefined) {\n\t\t\tdp = DOMDataUtils.getDataParsoid(aNode);\n\t\t}\n\n\t\t// SSS FIXME: This requires to be made more robust\n\t\t// for when dp.stx value is not present\n\t\treturn aNode.getAttribute(\"rel\") === \"mw:WikiLink\" ||\n\t\t\t(dp.stx && dp.stx !== \"url\" && dp.stx !== \"magiclink\");\n\t}\n\n\tstatic usesExtLinkSyntax(aNode, dp) {\n\t\tif (dp === undefined) {\n\t\t\tdp = DOMDataUtils.getDataParsoid(aNode);\n\t\t}\n\n\t\t// SSS FIXME: This requires to be made more robust\n\t\t// for when dp.stx value is not present\n\t\treturn aNode.getAttribute(\"rel\") === \"mw:ExtLink\" &&\n\t\t\t(!dp.stx || (dp.stx !== \"url\" && dp.stx !== \"magiclink\"));\n\t}\n\n\tstatic usesURLLinkSyntax(aNode, dp) {\n\t\tif (dp === undefined) {\n\t\t\tdp = DOMDataUtils.getDataParsoid(aNode);\n\t\t}\n\n\t\t// SSS FIXME: This requires to be made more robust\n\t\t// for when dp.stx value is not present\n\t\treturn aNode.getAttribute(\"rel\") === \"mw:ExtLink\" &&\n\t\t\tdp.stx && dp.stx === \"url\";\n\t}\n\n\tstatic usesMagicLinkSyntax(aNode, dp) {\n\t\tif (dp === undefined) {\n\t\t\tdp = DOMDataUtils.getDataParsoid(aNode);\n\t\t}\n\n\t\t// SSS FIXME: This requires to be made more robust\n\t\t// for when dp.stx value is not present\n\t\treturn aNode.getAttribute(\"rel\") === \"mw:ExtLink\" &&\n\t\t\tdp.stx && dp.stx === \"magiclink\";\n\t}\n\n\t/**\n\t * Check whether a node's typeof indicates that it is a template expansion.\n\t *\n\t * @param {Node} node\n\t * @return {string|null} The matched type, or null if no match.\n\t */\n\tstatic matchTplType(node) {\n\t\treturn DOMUtils.matchTypeOf(node, TPL_META_TYPE_REGEXP);\n\t}\n\n\t/**\n\t * Check whether a typeof indicates that it signifies an\n\t * expanded attribute.\n\t *\n\t * @param node\n\t * @return {bool}\n\t */\n\tstatic hasExpandedAttrsType(node) {\n\t\treturn DOMUtils.matchTypeOf(node, /^mw:ExpandedAttrs(\\/\\S+)*$/) !== null;\n\t}\n\n\t/**\n\t * Check whether a node is a meta tag that signifies a template expansion.\n\t *\n\t * @param node\n\t */\n\tstatic isTplMarkerMeta(node) {\n\t\treturn DOMUtils.matchNameAndTypeOf(node, 'META', TPL_META_TYPE_REGEXP) !== null;\n\t}\n\n\t/**\n\t * Check whether a node is a meta signifying the start of a template expansion.\n\t *\n\t * @param node\n\t */\n\tstatic isTplStartMarkerMeta(node) {\n\t\tvar t = DOMUtils.matchNameAndTypeOf(node, 'META', TPL_META_TYPE_REGEXP);\n\t\treturn t && !/\\/End$/.test(t);\n\t}\n\n\t/**\n\t * Check whether a node is a meta signifying the end of a template\n\t * expansion.\n\t *\n\t * @param {Node} n\n\t */\n\tstatic isTplEndMarkerMeta(n) {\n\t\tvar t = DOMUtils.matchNameAndTypeOf(n, 'META', TPL_META_TYPE_REGEXP);\n\t\treturn t && /\\/End$/.test(t);\n\t}\n\n\t/**\n\t * Find the first wrapper element of encapsulated content.\n\t *\n\t * @param node\n\t */\n\tstatic findFirstEncapsulationWrapperNode(node) {\n\t\tif (!this.hasParsoidAboutId(node)) {\n\t\t\treturn null;\n\t\t}\n\t\tvar about = node.getAttribute('about') || '';\n\t\tvar prev = node;\n\t\tdo {\n\t\t\tnode = prev;\n\t\t\tprev = DOMUtils.previousNonDeletedSibling(node);\n\t\t} while (prev && DOMUtils.isElt(prev) && prev.getAttribute('about') === about);\n\t\treturn this.isFirstEncapsulationWrapperNode(node) ? node : null;\n\t}\n\n\t/**\n\t * This tests whether a DOM node is a new node added during an edit session\n\t * or an existing node from parsed wikitext.\n\t *\n\t * As written, this function can only be used on non-template/extension content\n\t * or on the top-level nodes of template/extension content. This test will\n\t * return the wrong results on non-top-level nodes of template/extension content.\n\t *\n\t * @param {Node} node\n\t */\n\tstatic isNewElt(node) {\n\t\t// We cannot determine newness on text/comment nodes.\n\t\tif (!DOMUtils.isElt(node)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// For template/extension content, newness should be\n\t\t// checked on the encapsulation wrapper node.\n\t\tnode = this.findFirstEncapsulationWrapperNode(node) || node;\n\t\treturn !!DOMDataUtils.getDataParsoid(node).tmp.isNew;\n\t}\n\n\t/**\n\t * Check whether a pre is caused by indentation in the original wikitext.\n\t *\n\t * @param node\n\t */\n\tstatic isIndentPre(node) {\n\t\treturn node.nodeName === \"PRE\" && !this.isLiteralHTMLNode(node);\n\t}\n\n\t/**\n\t * Find how much offset is necessary for the DSR of an\n\t * indent-originated pre tag.\n\t *\n\t * @param {TextNode} textNode\n\t * @return {number}\n\t */\n\tstatic indentPreDSRCorrection(textNode) {\n\t\t// NOTE: This assumes a text-node and doesn't check that it is one.\n\t\t//\n\t\t// FIXME: Doesn't handle text nodes that are not direct children of the pre\n\t\tif (this.isIndentPre(textNode.parentNode)) {\n\t\t\tvar numNLs;\n\t\t\tif (textNode.parentNode.lastChild === textNode) {\n\t\t\t\t// We dont want the trailing newline of the last child of the pre\n\t\t\t\t// to contribute a pre-correction since it doesn't add new content\n\t\t\t\t// in the pre-node after the text\n\t\t\t\tnumNLs = (textNode.nodeValue.match(/\\n./g) || []).length;\n\t\t\t} else {\n\t\t\t\tnumNLs = (textNode.nodeValue.match(/\\n/g) || []).length;\n\t\t\t}\n\t\t\treturn numNLs;\n\t\t} else {\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\t/**\n\t * Check if node is an ELEMENT node belongs to a template/extension.\n\t *\n\t * NOTE: Use with caution. This technique works reliably for the\n\t * root level elements of tpl-content DOM subtrees since only they\n\t * are guaranteed to be  marked and nested content might not\n\t * necessarily be marked.\n\t *\n\t * @param {Node} node\n\t * @return {boolean}\n\t */\n\tstatic hasParsoidAboutId(node) {\n\t\tif (DOMUtils.isElt(node)) {\n\t\t\tvar about = node.getAttribute('about') || '';\n\t\t\t// SSS FIXME: Verify that our DOM spec clarifies this\n\t\t\t// expectation on about-ids and that our clients respect this.\n\t\t\treturn about && Util.isParsoidObjectId(about);\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstatic isRedirectLink(node) {\n\t\treturn DOMUtils.isElt(node) && node.nodeName === 'LINK' &&\n\t\t\t/\\bmw:PageProp\\/redirect\\b/.test(node.getAttribute('rel') || '');\n\t}\n\n\tstatic isCategoryLink(node) {\n\t\treturn DOMUtils.isElt(node) && node.nodeName === 'LINK' &&\n\t\t\t/\\bmw:PageProp\\/Category\\b/.test(node.getAttribute('rel') || '');\n\t}\n\n\tstatic isSolTransparentLink(node) {\n\t\treturn DOMUtils.isElt(node) && node.nodeName === 'LINK' &&\n\t\t\tTokenUtils.solTransparentLinkRegexp.test(node.getAttribute('rel') || '');\n\t}\n\n\t/**\n\t * Check if 'node' emits wikitext that is sol-transparent in wikitext form.\n\t * This is a test for wikitext that doesn't introduce line breaks.\n\t *\n\t * Comment, whitespace text nodes, category links, redirect links, behavior\n\t * switches, and include directives currently satisfy this definition.\n\t *\n\t * This should come close to matching TokenUtils.isSolTransparent()\n\t *\n\t * @param {Node} node\n\t */\n\tstatic emitsSolTransparentSingleLineWT(node) {\n\t\tif (DOMUtils.isText(node)) {\n\t\t\t// NB: We differ here to meet the nl condition.\n\t\t\treturn node.nodeValue.match(/^[ \\t]*$/);\n\t\t} else if (this.isRenderingTransparentNode(node)) {\n\t\t\t// NB: The only metas in a DOM should be for behavior switches and\n\t\t\t// include directives, other than explicit HTML meta tags. This\n\t\t\t// differs from our counterpart in Util where ref meta tokens\n\t\t\t// haven't been expanded to spans yet.\n\t\t\treturn true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tstatic isFallbackIdSpan(node) {\n\t\treturn DOMUtils.hasNameAndTypeOf(node, 'SPAN', 'mw:FallbackId');\n\t}\n\n\t/**\n\t * These are primarily 'metadata'-like nodes that don't show up in output rendering.\n\t * - In Parsoid output, they are represented by link/meta tags.\n\t * - In the PHP parser, they are completely stripped from the input early on.\n\t *   Because of this property, these rendering-transparent nodes are also\n\t *   SOL-transparent for the purposes of parsing behavior.\n\t *\n\t * @param node\n\t */\n\tstatic isRenderingTransparentNode(node) {\n\t\t// FIXME: Can we change this entire thing to\n\t\t// DOMUtils.isComment(node) ||\n\t\t// DOMUtils.getDataParsoid(node).stx !== 'html' &&\n\t\t//   (node.nodeName === 'META' || node.nodeName === 'LINK')\n\t\t//\n\t\treturn DOMUtils.isComment(node) ||\n\t\t\tthis.isSolTransparentLink(node) ||\n\t\t\t// Catch-all for everything else.\n\t\t\t(node.nodeName === 'META' &&\n\t\t\t\t// (Start|End)Tag metas clone data-parsoid from the tokens\n\t\t\t\t// they're shadowing, which trips up on the stx check.\n\t\t\t\t// TODO: Maybe that data should be nested in a property?\n\t\t\t\t(DOMUtils.matchTypeOf(node, /^mw:(StartTag|EndTag)$/) !== null ||\n\t\t\t\tDOMDataUtils.getDataParsoid(node).stx !== 'html')) ||\n\t\t\tthis.isFallbackIdSpan(node);\n\t}\n\n\t/**\n\t * Is node nested inside a table tag that uses HTML instead of native\n\t * wikitext?\n\t *\n\t * @param {Node} node\n\t * @return {boolean}\n\t */\n\tstatic inHTMLTableTag(node) {\n\t\tvar p = node.parentNode;\n\t\twhile (DOMUtils.isTableTag(p)) {\n\t\t\tif (this.isLiteralHTMLNode(p)) {\n\t\t\t\treturn true;\n\t\t\t} else if (p.nodeName === 'TABLE') {\n\t\t\t\t// Don't cross <table> boundaries\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tp = p.parentNode;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tstatic FIRST_ENCAP_REGEXP() {\n\t\treturn /(?:^|\\s)(mw:(?:Transclusion|Param|LanguageVariant|Extension(\\/\\S+)))(?=$|\\s)/;\n\t}\n\n\t/**\n\t * Is node the first wrapper element of encapsulated content?\n\t *\n\t * @param node\n\t */\n\tstatic isFirstEncapsulationWrapperNode(node) {\n\t\treturn DOMUtils.matchTypeOf(node, this.FIRST_ENCAP_REGEXP()) !== null;\n\t}\n\n\t/**\n\t * Is node an encapsulation wrapper elt?\n\t *\n\t * All root-level nodes of generated content are considered\n\t * encapsulation wrappers and share an about-id.\n\t *\n\t * @param node\n\t */\n\tstatic isEncapsulationWrapper(node) {\n\t\t// True if it has an encapsulation type or while walking backwards\n\t\t// over elts with identical about ids, we run into a node with an\n\t\t// encapsulation type.\n\t\tif (!DOMUtils.isElt(node)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.findFirstEncapsulationWrapperNode(node) !== null;\n\t}\n\n\tstatic isDOMFragmentWrapper(node) {\n\t\treturn DOMUtils.isElt(node) &&\n\t\t\tTokenUtils.isDOMFragmentType(node.getAttribute('typeof') || '');\n\t}\n\n\tstatic isSealedFragmentOfType(node, type) {\n\t\treturn DOMUtils.hasTypeOf(node, 'mw:DOMFragment/sealed/' + type);\n\t}\n\n\tstatic isParsoidSectionTag(node) {\n\t\treturn node.nodeName === 'SECTION' &&\n\t\t\tnode.hasAttribute('data-mw-section-id');\n\t}\n\n\tstatic isExtendedAnnotationWrapperTag(node) {\n\t\treturn node.hasAttribute('typeof') && node.getAttribute('typeof') === 'mw:ExtendedAnnRange';\n\t}\n\n\t/**\n\t * Is the node from extension content?\n\t *\n\t * @param {Node} node\n\t * @param {string} extType\n\t * @return {boolean}\n\t */\n\tstatic fromExtensionContent(node, extType) {\n\t\tvar parentNode = node.parentNode;\n\t\twhile (parentNode && !DOMUtils.atTheTop(parentNode)) {\n\t\t\tif (DOMUtils.hasTypeOf(parentNode, 'mw:Extension/' + extType)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tparentNode = parentNode.parentNode;\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Compute, when possible, the wikitext source for a node in\n\t * an frame f. Returns null if the source cannot be\n\t * extracted.\n\t *\n\t * @param {Frame} frame\n\t * @param {Node} node\n\t */\n\tstatic getWTSource(frame, node) {\n\t\tvar data = DOMDataUtils.getDataParsoid(node);\n\t\tvar dsr = (undefined !== data) ? data.dsr : null;\n\t\treturn dsr && Util.isValidDSR(dsr) ?\n\t\t\tframe.srcText.slice(dsr[0], dsr[1]) : null;\n\t}\n\n\t/**\n\t * Gets all siblings that follow 'node' that have an 'about' as\n\t * their about id.\n\t *\n\t * This is used to fetch transclusion/extension content by using\n\t * the about-id as the key.  This works because\n\t * transclusion/extension content is a forest of dom-trees formed\n\t * by adjacent dom-nodes.  This is the contract that template\n\t * encapsulation, dom-reuse, and VE code all have to abide by.\n\t *\n\t * The only exception to this adjacency rule is IEW nodes in\n\t * fosterable positions (in tables) which are not span-wrapped to\n\t * prevent them from getting fostered out.\n\t *\n\t * @param node\n\t * @param about\n\t */\n\tstatic getAboutSiblings(node, about) {\n\t\tvar nodes = [node];\n\n\t\tif (!about) {\n\t\t\treturn nodes;\n\t\t}\n\n\t\tnode = node.nextSibling;\n\t\twhile (node && (\n\t\t\tDOMUtils.isElt(node) && node.getAttribute('about') === about ||\n\t\t\t\tDOMUtils.isFosterablePosition(node) && !DOMUtils.isElt(node) && DOMUtils.isIEW(node)\n\t\t)) {\n\t\t\tnodes.push(node);\n\t\t\tnode = node.nextSibling;\n\t\t}\n\n\t\t// Remove already consumed trailing IEW, if any\n\t\twhile (nodes.length && DOMUtils.isIEW(lastItem(nodes))) {\n\t\t\tnodes.pop();\n\t\t}\n\n\t\treturn nodes;\n\t}\n\n\t/**\n\t * This function is only intended to be used on encapsulated nodes\n\t * (Template/Extension/Param content).\n\t *\n\t * Given a 'node' that has an about-id, it is assumed that it is generated\n\t * by templates or extensions.  This function skips over all\n\t * following content nodes and returns the first non-template node\n\t * that follows it.\n\t *\n\t * @param node\n\t */\n\tstatic skipOverEncapsulatedContent(node) {\n\t\tif (node.hasAttribute('about')) {\n\t\t\tvar about = node.getAttribute('about');\n\t\t\treturn lastItem(this.getAboutSiblings(node, about)).nextSibling;\n\t\t} else {\n\t\t\treturn node.nextSibling;\n\t\t}\n\t}\n\n\t// Comment encoding/decoding.\n\t//\n\t//  * Some relevant phab tickets: T94055, T70146, T60184, T95039\n\t//\n\t// The wikitext comment rule is very simple: <!-- starts a comment,\n\t// and --> ends a comment.  This means we can have almost anything as the\n\t// contents of a comment (except the string \"-->\", but see below), including\n\t// several things that are not valid in HTML5 comments:\n\t//\n\t//  * For one, the html5 comment parsing algorithm [0] leniently accepts\n\t//    --!> as a closing comment tag, which differs from the php+tidy combo.\n\t//\n\t//  * If the comment's data matches /^-?>/, html5 will end the comment.\n\t//    For example, <!-->stuff<--> breaks up as\n\t//    <!--> (the comment) followed by, stuff<--> (as text).\n\t//\n\t//  * Finally, comment data shouldn't contain two consecutive hyphen-minus\n\t//    characters (--), nor end in a hyphen-minus character (/-$/) as defined\n\t//    in the spec [1].\n\t//\n\t// We work around all these problems by using HTML entity encoding inside\n\t// the comment body.  The characters -, >, and & must be encoded in order\n\t// to prevent premature termination of the comment by one of the cases\n\t// above.  Encoding other characters is optional; all entities will be\n\t// decoded during wikitext serialization.\n\t//\n\t// In order to allow *arbitrary* content inside a wikitext comment,\n\t// including the forbidden string \"-->\" we also do some minimal entity\n\t// decoding on the wikitext.  We are also limited by our inability\n\t// to encode DSR attributes on the comment node, so our wikitext entity\n\t// decoding must be 1-to-1: that is, there must be a unique \"decoded\"\n\t// string for every wikitext sequence, and for every decoded string there\n\t// must be a unique wikitext which creates it.\n\t//\n\t// The basic idea here is to replace every string ab*c with the string with\n\t// one more b in it.  This creates a string with no instance of \"ac\",\n\t// so you can use 'ac' to encode one more code point.  In this case\n\t// a is \"--&\", \"b\" is \"amp;\", and \"c\" is \"gt;\" and we use ac to\n\t// encode \"-->\" (which is otherwise unspeakable in wikitext).\n\t//\n\t// Note that any user content which does not match the regular\n\t// expression /--(>|&(amp;)*gt;)/ is unchanged in its wikitext\n\t// representation, as shown in the first two examples below.\n\t//\n\t// User-authored comment text    Wikitext       HTML5 DOM\n\t// --------------------------    -------------  ----------------------\n\t// & - >                         & - >          &amp; &#43; &gt;\n\t// Use &gt; here                 Use &gt; here  Use &amp;gt; here\n\t// -->                           --&gt;         &#43;&#43;&gt;\n\t// --&gt;                        --&amp;gt;     &#43;&#43;&amp;gt;\n\t// --&amp;gt;                    --&amp;amp;gt; &#43;&#43;&amp;amp;gt;\n\t//\n\t// [0] http://www.w3.org/TR/html5/syntax.html#comment-start-state\n\t// [1] http://www.w3.org/TR/html5/syntax.html#comments\n\n\t/**\n\t * Map a wikitext-escaped comment to an HTML DOM-escaped comment.\n\t *\n\t * @param {string} comment Wikitext-escaped comment.\n\t * @return {string} DOM-escaped comment.\n\t */\n\tstatic encodeComment(comment) {\n\t\t// Undo wikitext escaping to obtain \"true value\" of comment.\n\t\tvar trueValue = comment\n\t\t\t.replace(/--&(amp;)*gt;/g, Util.decodeWtEntities);\n\t\t// Now encode '-', '>' and '&' in the \"true value\" as HTML entities,\n\t\t// so that they can be safely embedded in an HTML comment.\n\t\t// This part doesn't have to map strings 1-to-1.\n\t\treturn trueValue\n\t\t\t.replace(/[->&]/g, Util.entityEncodeAll);\n\t}\n\n\t/**\n\t * Map an HTML DOM-escaped comment to a wikitext-escaped comment.\n\t *\n\t * @param {string} comment DOM-escaped comment.\n\t * @return {string} Wikitext-escaped comment.\n\t */\n\tstatic decodeComment(comment) {\n\t\t// Undo HTML entity escaping to obtain \"true value\" of comment.\n\t\tvar trueValue = Util.decodeWtEntities(comment);\n\t\t// ok, now encode this \"true value\" of the comment in such a way\n\t\t// that the string \"-->\" never shows up.  (See above.)\n\t\treturn trueValue\n\t\t\t.replace(/--(&(amp;)*gt;|>)/g, function(s) {\n\t\t\t\treturn s === '-->' ? '--&gt;' : '--&amp;' + s.slice(3);\n\t\t\t});\n\t}\n\n\t/**\n\t * Utility function: we often need to know the wikitext DSR length for\n\t * an HTML DOM comment value.\n\t *\n\t * @param {Node} node A comment node containing a DOM-escaped comment.\n\t * @return {number} The wikitext length necessary to encode this comment,\n\t *   including 7 characters for the `<!--` and `-->` delimiters.\n\t */\n\tstatic decodedCommentLength(node) {\n\t\tconsole.assert(DOMUtils.isComment(node));\n\t\t// Add 7 for the \"<!--\" and \"-->\" delimiters in wikitext.\n\t\treturn this.decodeComment(node.data).length + 7;\n\t}\n\n\t/**\n\t * Escape `<nowiki>` tags.\n\t *\n\t * @param {string} text\n\t * @return {string}\n\t */\n\tstatic escapeNowikiTags(text) {\n\t\treturn text.replace(/<(\\/?nowiki\\s*\\/?\\s*)>/gi, '&lt;$1&gt;');\n\t}\n\n\t/**\n\t * Conditional encoding is because, while treebuilding, the value goes\n\t * directly from token to dom node without the comment itself being\n\t * stringified and parsed where the comment encoding would be necessary.\n\t *\n\t * @param typeOf\n\t * @param attrs\n\t * @param encode\n\t */\n\tstatic fosterCommentData(typeOf, attrs, encode) {\n\t\tlet str = JSON.stringify({\n\t\t\t'@type': typeOf,\n\t\t\tattrs,\n\t\t});\n\t\tif (encode) {\n\t\t\tstr = WTUtils.encodeComment(str);\n\t\t}\n\t\treturn str;\n\t}\n\n\tstatic reinsertFosterableContent(env, node, decode) {\n\t\tif (DOMUtils.isComment(node) && /^\\{[^]+\\}$/.test(node.data)) {\n\t\t\t// Convert serialized meta tags back from comments.\n\t\t\t// We use this trick because comments won't be fostered,\n\t\t\t// providing more accurate information about where tags are expected\n\t\t\t// to be found.\n\t\t\tvar data, type;\n\t\t\ttry {\n\t\t\t\tdata = JSON.parse(decode ? WTUtils.decodeComment(node.data) : node.data);\n\t\t\t\ttype = data[\"@type\"];\n\t\t\t} catch (e) {\n\t\t\t\t// not a valid json attribute, do nothing\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tif (/^mw:/.test(type)) {\n\t\t\t\tvar meta = node.ownerDocument.createElement(\"meta\");\n\t\t\t\tdata.attrs.forEach(function(attr) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmeta.setAttribute(...attr);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tenv.log(\"warn\", \"prepareDOM: Dropped invalid attribute\", JSON.stringify(attr));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tnode.parentNode.replaceChild(meta, node);\n\t\t\t\treturn meta;\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\tstatic getNativeExt(env, node) {\n\t\tconst prefixLen = \"mw:Extension/\".length;\n\t\tconst match = DOMUtils.matchTypeOf(node, /^mw:Extension\\/(.+?)$/);\n\t\treturn match && env.conf.wiki.extConfig.tags.get(match.slice(prefixLen));\n\t}\n}\n\nif (typeof module === \"object\") {\n\tmodule.exports.WTUtils = WTUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/jsutils.js","messages":[{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":280,"column":10,"nodeType":"NewExpression","endLine":282,"endColumn":44}],"suppressedMessages":[{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":197,"column":16,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":197,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '>>='.","line":198,"column":4,"nodeType":"AssignmentExpression","messageId":"unexpected","endLine":198,"endColumn":11,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * This file contains Parsoid-independent JS helper functions.\n * Over time, more functions can be migrated out of various other files here.\n *\n * @module\n */\n\n'use strict';\n\nrequire('../../core-upgrade.js');\n\nvar Promise = require('./promise.js');\n\nvar rejectMutation = function() {\n\tthrow new TypeError(\"Mutation attempted on read-only collection.\");\n};\n\nvar lastItem = function(array) {\n\tconsole.assert(Array.isArray(array));\n\treturn array[array.length - 1];\n};\n\n/** @namespace */\nvar JSUtils = {\n\n\t/**\n\t * Return the last item in an array.\n\t *\n\t * @method\n\t * @param {Array} array\n\t * @return {any} The last item in `array`\n\t */\n\tlastItem: lastItem,\n\n\t/**\n\t * Return a {@link Map} with the same initial keys and values as the\n\t * given {@link Object}.\n\t *\n\t * @param {Object} obj\n\t * @return {Map}\n\t */\n\tmapObject: function(obj) {\n\t\treturn new Map(Object.entries(obj));\n\t},\n\n\t/**\n\t * Return a two-way Map that maps each element to its index\n\t * (and vice-versa).\n\t *\n\t * @param {Array} arr\n\t * @return {Map}\n\t */\n\tarrayMap: function(arr) {\n\t\tvar m = new Map(arr.map(function(e, i) {\n\t\t\treturn [e, i];\n\t\t}));\n\t\tm.item = function(i) {\n\t\t\treturn arr[i];\n\t\t};\n\t\treturn m;\n\t},\n\n\t/**\n\t * ES6 maps/sets are still writable even when frozen, because they\n\t * store data inside the object linked from an internal slot.\n\t * This freezes a map by disabling the mutation methods, although\n\t * it's not bulletproof: you could use `Map.prototype.set.call(m, ...)`\n\t * to still mutate the backing store.\n\t *\n\t * @param it\n\t * @param freezeEntries\n\t */\n\tfreezeMap: function(it, freezeEntries) {\n\t\t// Allow `it` to be an iterable, as well as a map.\n\t\tif (!(it instanceof Map)) {\n\t\t\tit = new Map(it);\n\t\t}\n\t\tit.set = it.clear = it.delete = rejectMutation;\n\t\tObject.freeze(it);\n\t\tif (freezeEntries) {\n\t\t\tit.forEach(function(v, k) {\n\t\t\t\tJSUtils.deepFreeze(v);\n\t\t\t\tJSUtils.deepFreeze(k);\n\t\t\t});\n\t\t}\n\t\treturn it;\n\t},\n\n\t/**\n\t * This makes a set read-only.\n\t *\n\t * @param it\n\t * @param freezeEntries\n\t * @see {@link .freezeMap}\n\t */\n\tfreezeSet: function(it, freezeEntries) {\n\t\t// Allow `it` to be an iterable, as well as a set.\n\t\tif (!(it instanceof Set)) {\n\t\t\tit = new Set(it);\n\t\t}\n\t\tit.add = it.clear = it.delete = rejectMutation;\n\t\tObject.freeze(it);\n\t\tif (freezeEntries) {\n\t\t\tit.forEach(function(v) {\n\t\t\t\tJSUtils.deepFreeze(v);\n\t\t\t});\n\t\t}\n\t\treturn it;\n\t},\n\n\t/**\n\t * Deep-freeze an object.\n\t * {@link Map}s and {@link Set}s are handled with {@link .freezeMap} and\n\t * {@link .freezeSet}.\n\t *\n\t * @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/freeze\n\t * @param {any} o\n\t * @return {any} Frozen object\n\t */\n\tdeepFreeze: function(o) {\n\t\tif (!(o instanceof Object)) {\n\t\t\treturn o;\n\t\t} else if (Object.isFrozen(o)) {\n\t\t\t// Note that this might leave an unfrozen reference somewhere in\n\t\t\t// the object if there is an already frozen object containing an\n\t\t\t// unfrozen object.\n\t\t\treturn o;\n\t\t} else if (o instanceof Map) {\n\t\t\treturn JSUtils.freezeMap(o, true);\n\t\t} else if (o instanceof Set) {\n\t\t\treturn JSUtils.freezeSet(o, true);\n\t\t}\n\n\t\tObject.freeze(o);\n\t\tfor (var propKey in o) {\n\t\t\tvar desc = Object.getOwnPropertyDescriptor(o, propKey);\n\t\t\tif ((!desc) || desc.get || desc.set) {\n\t\t\t\t// If the object is on the prototype or is a getter, skip it.\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// Recursively call deepFreeze.\n\t\t\tJSUtils.deepFreeze(desc.value);\n\t\t}\n\t\treturn o;\n\t},\n\n\t/**\n\t * Deep freeze an object, except for the specified fields.\n\t *\n\t * @param {Object} o\n\t * @param {Object} ignoreFields\n\t * @return {Object} Frozen object.\n\t */\n\tdeepFreezeButIgnore: function(o, ignoreFields) {\n\t\tfor (var prop in o) {\n\t\t\tvar desc = Object.getOwnPropertyDescriptor(o, prop);\n\t\t\tif (ignoreFields[prop] === true || (!desc) || desc.get || desc.set) {\n\t\t\t\t// Ignore getters, primitives, and explicitly ignored fields.\n\t\t\t\treturn;\n\t\t\t}\n\t\t\to[prop] = JSUtils.deepFreeze(desc.value);\n\t\t}\n\t\tObject.freeze(o);\n\t},\n\n\t/**\n\t * Sort keys in an object, recursively, for better reproducibility.\n\t * (This is especially useful before serializing as JSON.)\n\t *\n\t * @param obj\n\t */\n\tsortObject: function(obj) {\n\t\tvar sortObject = JSUtils.sortObject;\n\t\tvar sortValue = function(v) {\n\t\t\tif (v instanceof Object) {\n\t\t\t\treturn Array.isArray(v) ? v.map(sortValue) : sortObject(v);\n\t\t\t}\n\t\t\treturn v;\n\t\t};\n\t\treturn Object.keys(obj).sort().reduce(function(sorted, k) {\n\t\t\tsorted[k] = sortValue(obj[k]);\n\t\t\treturn sorted;\n\t\t}, {});\n\t},\n\n\t/**\n\t * Convert a counter to a Base64 encoded string.\n\t * Padding is stripped. \\,+ are replaced with _,- respectively.\n\t * Warning: Max integer is 2^31 - 1 for bitwise operations.\n\t *\n\t * @param n\n\t */\n\tcounterToBase64: function(n) {\n\t\t/* eslint-disable no-bitwise */\n\t\tvar arr = [];\n\t\tdo {\n\t\t\tarr.unshift(n & 0xff);\n\t\t\tn >>= 8;\n\t\t} while (n > 0);\n\t\treturn (Buffer.from(arr))\n\t\t\t.toString(\"base64\")\n\t\t\t.replace(/=/g, \"\")\n\t\t\t.replace(/\\//g, \"_\")\n\t\t\t.replace(/\\+/g, \"-\");\n\t\t/* eslint-enable no-bitwise */\n\t},\n\n\t/**\n\t * Escape special regexp characters in a string.\n\t *\n\t * @param {string} s\n\t * @return {string} A regular expression string that matches the\n\t *  literal characters in s.\n\t */\n\tescapeRegExp: function(s) {\n\t\treturn s.replace(/[\\^\\\\$*+?.()|{}\\[\\]\\/]/g, '\\\\$&');\n\t},\n\n\t/**\n\t * Escape special regexp characters in a string, returning a\n\t * case-insensitive regular expression.  This is usually denoted\n\t * by something like `(?i:....)` in most programming languages,\n\t * but JavaScript doesn't support embedded regexp flags.\n\t *\n\t * @param {string} s\n\t * @return {string} A regular expression string that matches the\n\t *  literal characters in s.\n\t */\n\tescapeRegExpIgnoreCase: function(s) {\n\t\t// Using Array.from() here ensures we split on unicode codepoints,\n\t\t// which may be longer than a single JavaScript character.\n\t\treturn Array.from(s).map((c) => {\n\t\t\tif (/[\\^\\\\$*+?.()|{}\\[\\]\\/]/.test(c)) {\n\t\t\t\treturn '\\\\' + c;\n\t\t\t}\n\t\t\tconst uc = c.toUpperCase();\n\t\t\tconst lc = c.toLowerCase();\n\t\t\tif (c === lc && c === uc) {\n\t\t\t\treturn c;\n\t\t\t}\n\t\t\tif (uc.length === 1 && lc.length === 1) {\n\t\t\t\treturn `[${ uc }${ lc }]`;\n\t\t\t}\n\t\t\treturn `(?:${ uc }|${ lc })`;\n\t\t}).join('');\n\t},\n\n\t/**\n\t * Join pieces of regular expressions together.  This helps avoid\n\t * having to switch between string and regexp quoting rules, and\n\t * can also give you a poor-man's version of the \"x\" flag, ie:\n\t * ```\n\t *  var re = rejoin( \"(\",\n\t *      /foo|bar/, \"|\",\n\t *      someRegExpFromAVariable\n\t *      \")\", { flags: \"i\" } );\n\t * ```\n\t * Note that this is basically string concatenation, except that\n\t * regular expressions are converted to strings using their `.source`\n\t * property, and then the final resulting string is converted to a\n\t * regular expression.\n\t *\n\t * If the final argument is a regular expression, its flags will be\n\t * used for the result.  Alternatively, you can make the final argument\n\t * an object, with a `flags` property (as shown in the example above).\n\t *\n\t * @return {RegExp}\n\t */\n\trejoin: function() {\n\t\tvar regexps = Array.from(arguments);\n\t\tvar last = lastItem(regexps);\n\t\tvar flags;\n\t\tif (typeof (last) === 'object') {\n\t\t\tif (last instanceof RegExp) {\n\t\t\t\tflags = /\\/([gimy]*)$/.exec(last.toString())[1];\n\t\t\t} else {\n\t\t\t\tflags = regexps.pop().flags;\n\t\t\t}\n\t\t}\n\t\treturn new RegExp(regexps.reduce(function(acc, r) {\n\t\t\treturn acc + (r instanceof RegExp ? r.source : r);\n\t\t}, ''), flags === undefined ? '' : flags);\n\t},\n\n\t/**\n\t * Append an array to an accumulator using the most efficient method\n\t * available. Makes sure that accumulation is O(n).\n\t *\n\t * @param accum\n\t * @param arr\n\t */\n\tpushArray: function push(accum, arr) {\n\t\tif (accum.length < arr.length) {\n\t\t\treturn accum.concat(arr);\n\t\t} else {\n\t\t\t// big accum & arr\n\t\t\tfor (var i = 0, l = arr.length; i < l; i++) {\n\t\t\t\taccum.push(arr[i]);\n\t\t\t}\n\t\t\treturn accum;\n\t\t}\n\t},\n\n\t/**\n\t * Helper function to ease migration to Promise-based control flow\n\t * (aka, \"after years of wandering, arrive in the Promise land\").\n\t * This function allows retrofitting an existing callback-based\n\t * method to return an equivalent Promise, allowing enlightened\n\t * new code to omit the callback parameter and treat it as if\n\t * it had an API which simply returned a Promise for the result.\n\t *\n\t * Sample use:\n\t * ```\n\t *   // callback is node-style: callback(err, value)\n\t *   function legacyApi(param1, param2, callback) {\n\t *     callback = JSUtils.mkPromised(callback); // THIS LINE IS NEW\n\t *     ... some implementation here...\n\t *     return callback.promise; // THIS LINE IS NEW\n\t *   }\n\t *   // old-style caller, still works:\n\t *   legacyApi(x, y, function(err, value) { ... });\n\t *   // new-style caller, such hotness:\n\t *   return legacyApi(x, y).then(function(value) { ... });\n\t * ```\n\t * The optional `names` parameter to `mkPromised` is the same\n\t * as the optional second argument to `Promise.promisify` in\n\t * {@link https://github/cscott/prfun}.\n\t * It allows the use of `mkPromised` for legacy functions which\n\t * promise multiple results to their callbacks, eg:\n\t * ```\n\t *   callback(err, body, response);  // from npm \"request\" module\n\t * ```\n\t * For this callback signature, you have two options:\n\t * 1. Pass `true` as the names parameter:\n\t *    ```\n\t *      function legacyRequest(options, callback) {\n\t *        callback = JSUtils.mkPromised(callback, true);\n\t *        ... existing implementation...\n\t *        return callback.promise;\n\t *      }\n\t *    ```\n\t *    This resolves the promise with the array `[body, response]`, so\n\t *    a Promise-using caller looks like:\n\t *    ```\n\t *      return legacyRequest(options).then(function(r) {\n\t *        var body = r[0], response = r[1];\n\t *        ...\n\t *      }\n\t *    ```\n\t *    If you are using `prfun` then `Promise#spread` is convenient:\n\t *    ```\n\t *      return legacyRequest(options).spread(function(body, response) {\n\t *        ...\n\t *      });\n\t *    ```\n\t * 2. Alternatively (and probably preferably), provide an array of strings\n\t *    as the `names` parameter:\n\t *    ```\n\t *      function legacyRequest(options, callback) {\n\t *        callback = JSUtils.mkPromised(callback, ['body','response']);\n\t *        ... existing implementation...\n\t *        return callback.promise;\n\t *      }\n\t *    ```\n\t *    The resolved value will be an object with those fields:\n\t *    ```\n\t *      return legacyRequest(options).then(function(r) {\n\t *        var body = r.body, response = r.response;\n\t *        ...\n\t *      }\n\t *    ```\n\t * Note that in both cases the legacy callback behavior is unchanged:\n\t * ```\n\t *   legacyRequest(options, function(err, body, response) { ... });\n\t * ```\n\t *\n\t * @param {Function|undefined} callback\n\t * @param {true|Array<string>} [names]\n\t * @return {Function}\n\t * @return {Promise} [return.promise] A promise that will be fulfilled\n\t *  when the returned callback function is invoked.\n\t */\n\tmkPromised: function(callback, names) {\n\t\tvar res, rej;\n\t\tvar p = new Promise(function(_res, _rej) {\n\t\t\tres = _res; rej = _rej;\n\t\t});\n\t\tvar f = function(e, v) {\n\t\t\tif (e) {\n\t\t\t\trej(e);\n\t\t\t} else if (names === true) {\n\t\t\t\tres(Array.prototype.slice.call(arguments, 1));\n\t\t\t} else if (names) {\n\t\t\t\tvar value = {};\n\t\t\t\tfor (var index in names) {\n\t\t\t\t\tvalue[names[index]] = arguments[(+index) + 1];\n\t\t\t\t}\n\t\t\t\tres(value);\n\t\t\t} else {\n\t\t\t\tres(v);\n\t\t\t}\n\t\t\treturn callback && callback.apply(this, arguments);\n\t\t};\n\t\tf.promise = p;\n\t\treturn f;\n\t},\n\n\t/**\n\t * Determine whether two objects are identical, recursively.\n\t *\n\t * @param {any} a\n\t * @param {any} b\n\t * @return {boolean}\n\t */\n\tdeepEquals: function(a, b) {\n\t\tvar i;\n\t\tif (a === b) {\n\t\t\t// If only it were that simple.\n\t\t\treturn true;\n\t\t}\n\n\t\tif (a === undefined || b === undefined ||\n\t\t\t\ta === null || b === null) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (a.constructor !== b.constructor) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (a instanceof Object) {\n\t\t\tfor (i in a) {\n\t\t\t\tif (!this.deepEquals(a[i], b[i])) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (i in b) {\n\t\t\t\tif (a[i] === undefined) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t/**\n\t * Return accurate system time\n\t *\n\t * @return {number}\n\t */\n\tstartTime: function() {\n\t\tvar startHrTime = process.hrtime();\n\t\tvar milliseconds = (startHrTime[0] * 1e9 + startHrTime[1]) / 1000000;\t// convert seconds and nanoseconds to a scalar milliseconds value\n\t\treturn milliseconds;\n\t},\n\n\t/**\n\t * Return millisecond accurate system time differential\n\t *\n\t * @param {number} previousTime\n\t * @return {number}\n\t */\n\telapsedTime: function(previousTime) {\n\t\tvar endHrTime = process.hrtime();\n\t\tvar milliseconds = (endHrTime[0] * 1e9 + endHrTime[1]) / 1000000;\t// convert seconds and nanoseconds to a scalar milliseconds value\n\t\treturn milliseconds - previousTime;\n\t},\n\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.JSUtils = JSUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/utils/promise.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/wt2html/XMLSerializer.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/lib/wt2html/tokenizer.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/quibble.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/src/Config/variants.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/MockEnv.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/TestUtils.js","messages":[{"ruleId":"security/detect-unsafe-regex","severity":1,"message":"Unsafe Regular Expression","line":150,"column":12,"nodeType":"Literal","endLine":150,"endColumn":35}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * @module\n */\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\nvar entities = require('entities');\n\nvar ContentUtils = require('../lib/utils/ContentUtils.js').ContentUtils;\nvar DOMUtils = require('../lib/utils/DOMUtils.js').DOMUtils;\nvar DOMDataUtils = require('../lib/utils/DOMDataUtils.js').DOMDataUtils;\nvar Util = require('../lib/utils/Util.js').Util;\nvar WTUtils = require('../lib/utils/WTUtils.js').WTUtils;\nvar DOMNormalizer = require('../lib/html2wt/DOMNormalizer.js').DOMNormalizer;\nvar MockEnv = require('./MockEnv.js').MockEnv;\n\nvar TestUtils = {};\n\n/**\n * Little helper function for encoding XML entities.\n *\n * @param {string} string\n * @return {string}\n */\nTestUtils.encodeXml = function(string) {\n\treturn entities.encodeXML(string);\n};\n\n/**\n * Specialized normalization of the PHP parser & Parsoid output, to ignore\n * a few known-ok differences in parser test runs.\n *\n * This code is also used by the Parsoid round-trip testing code.\n *\n * If parsoidOnly is true-ish, we allow more markup through (like property\n * and typeof attributes), for better checking of parsoid-only test cases.\n *\n * @param {string} domBody\n * @param {Object} options\n * @param {boolean} [options.parsoidOnly=false]\n * @param {boolean} [options.preserveIEW=false]\n * @param {boolean} [options.hackNormalize=false]\n * @return {string}\n */\nTestUtils.normalizeOut = function(domBody, options) {\n\tif (!options) {\n\t\toptions = {};\n\t}\n\tconst parsoidOnly = options.parsoidOnly;\n\tconst preserveIEW = options.preserveIEW;\n\n\tif (options.hackyNormalize) {\n\t\t// Mock env obj\n\t\t//\n\t\t// FIXME: This is ugly.\n\t\t// (a) The normalizer shouldn't need the full env.\n\t\t//     Pass options and a logger instead?\n\t\t// (b) DOM diff code is using page-id for some reason.\n\t\t//     That feels like a carryover of 2013 era code.\n\t\t//     If possible, get rid of it and diff-mark dependency\n\t\t//     on the env object.\n\t\tconst env = new MockEnv({}, null);\n\t\tif (typeof (domBody) === 'string') {\n\t\t\tdomBody = env.createDocument(domBody).body;\n\t\t}\n\t\tvar mockState = {\n\t\t\tenv,\n\t\t\tselserMode: false,\n\t\t};\n\t\tDOMDataUtils.visitAndLoadDataAttribs(domBody, { markNew: true });\n\t\tdomBody = (new DOMNormalizer(mockState).normalize(domBody));\n\t\tDOMDataUtils.visitAndStoreDataAttribs(domBody);\n\t} else if (typeof (domBody) === 'string') {\n\t\tdomBody = DOMUtils.parseHTML(domBody).body;\n\t}\n\n\tvar stripTypeof = parsoidOnly ?\n\t\t/^mw:Placeholder$/ :\n\t\t/^mw:(?:DisplaySpace|Placeholder|Nowiki|Transclusion|Entity)$/;\n\tdomBody = this.unwrapSpansAndNormalizeIEW(domBody, stripTypeof, parsoidOnly, preserveIEW);\n\tvar out = ContentUtils.toXML(domBody, { innerXML: true });\n\t// NOTE that we use a slightly restricted regexp for \"attribute\"\n\t//  which works for the output of DOM serialization.  For example,\n\t//  we know that attribute values will be surrounded with double quotes,\n\t//  not unquoted or quoted with single quotes.  The serialization\n\t//  algorithm is given by:\n\t//  http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#serializing-html-fragments\n\tif (!/[^<]*(<\\w+(\\s+[^\\0-\\cZ\\s\"'>\\/=]+(=\"[^\"]*\")?)*\\/?>[^<]*)*/.test(out)) {\n\t\tthrow new Error(\"normalizeOut input is not in standard serialized form\");\n\t}\n\n\t// Eliminate a source of indeterminacy from leaked strip markers\n\tout = out.replace(/UNIQ-.*?-QINU/g, '');\n\n\t// Normalize COINS ids -- they aren't stable\n\tout = out.replace(/\\s?id=['\"]coins_\\d+['\"]/ig, '');\n\n\t// maplink extension\n\tout = out.replace(/\\s?data-overlays='[^']*'/ig, '');\n\n\tif (parsoidOnly) {\n\t\t// unnecessary attributes, we don't need to check these\n\t\t// style is in there because we should only check classes.\n\t\tout = out.replace(/ (data-parsoid|prefix|about|rev|datatype|inlist|usemap|vocab)=\\\\?\"[^\\\"]*\\\\?\"/g, '');\n\t\t// single-quoted variant\n\t\tout = out.replace(/ (data-parsoid|prefix|about|rev|datatype|inlist|usemap|vocab)=\\\\?'[^\\']*\\\\?'/g, '');\n\t\t// apos variant\n\t\tout = out.replace(/ (data-parsoid|prefix|about|rev|datatype|inlist|usemap|vocab)=&apos;.*?&apos;/g, '');\n\n\t\t// strip self-closed <nowiki /> because we frequently test WTS\n\t\t// <nowiki> insertion by providing an html/parsoid section with the\n\t\t// <meta> tags stripped out, allowing the html2wt test to verify that\n\t\t// the <nowiki> is correctly added during WTS, while still allowing\n\t\t// the html2html and wt2html versions of the test to pass as a\n\t\t// sanity check.  If <meta>s were not stripped, these tests would all\n\t\t// have to be modified and split up.  Not worth it at this time.\n\t\t// (see commit 689b22431ad690302420d049b10e689de6b7d426)\n\t\tout = out\n\t\t\t.replace(/<span typeof=\"mw:Nowiki\"><\\/span>/g, '');\n\n\t\treturn out;\n\t}\n\n\t// Normalize headings by stripping out Parsoid-added ids so that we don't\n\t// have to add these ids to every parser test that uses headings.\n\t// We will test the id generation scheme separately via mocha tests.\n\tout = out.replace(/(<h[1-6].*?) id=\"[^\"]*\"([^>]*>)/g, '$1$2');\n\n\t// strip meta/link elements\n\tout = out\n\t\t.replace(/<\\/?(?:meta|link)(?: [^\\0-\\cZ\\s\"'>\\/=]+(?:=(?:\"[^\"]*\"|'[^']*'))?)*\\/?>/g, '');\n\t// Ignore troublesome attributes.\n\t// Strip JSON attributes like data-mw and data-parsoid early so that\n\t// comment stripping in normalizeNewlines does not match unbalanced\n\t// comments in wikitext source.\n\tout = out.replace(/ (data-parsoid|prefix|about|rev|datatype|inlist|usemap|vocab|data-mw|resource|rel|property|class)=\\\\?\"[^\\\"]*\\\\?\"/g, '');\n\t// single-quoted variant\n\tout = out.replace(/ (data-parsoid|prefix|about|rev|datatype|inlist|usemap|vocab|data-mw|resource|rel|property|class)=\\\\?'[^\\']*\\\\?'/g, '');\n\t// strip typeof last\n\tout = out.replace(/ typeof=\"[^\\\"]*\"/g, '');\n\n\treturn out\n\t\t// replace mwt ids\n\t\t.replace(/ id=\"mw((t\\d+)|([\\w-]{2,}))\"/g, '')\n\t\t.replace(/<span[^>]+about=\"[^\"]*\"[^>]*>/g, '')\n\t\t.replace(/(\\s)<span>\\s*<\\/span>\\s*/g, '$1')\n\t\t.replace(/<span>\\s*<\\/span>/g, '')\n\t\t.replace(/(href=\")(?:\\.?\\.\\/)+/g, '$1')\n\t\t// replace unnecessary URL escaping\n\t\t.replace(/ href=\"[^\"]*\"/g, Util.decodeURI)\n\t\t// strip thumbnail size prefixes\n\t\t.replace(/(src=\"[^\"]*?)\\/thumb(\\/[0-9a-f]\\/[0-9a-f]{2}\\/[^\\/]+)\\/[0-9]+px-[^\"\\/]+(?=\")/g, '$1$2');\n};\n\n/**\n * Normalize newlines in IEW to spaces instead.\n *\n * @param {Node} body\n *   The document `<body>` node to normalize.\n * @param {RegExp} [stripSpanTypeof]\n * @param {boolean} [parsoidOnly=false]\n * @param {boolean} [preserveIEW=false]\n * @return {Node}\n */\nTestUtils.unwrapSpansAndNormalizeIEW = function(body, stripSpanTypeof, parsoidOnly, preserveIEW) {\n\tvar newlineAround = function(node) {\n\t\treturn node && /^(BODY|CAPTION|DIV|DD|DT|LI|P|TABLE|TR|TD|TH|TBODY|DL|OL|UL|H[1-6])$/.test(node.nodeName);\n\t};\n\tvar unwrapSpan;  // forward declare\n\tvar cleanSpans = function(node) {\n\t\tvar child, next;\n\t\tif (!stripSpanTypeof) {\n\t\t\treturn;\n\t\t}\n\t\tfor (child = node.firstChild; child; child = next) {\n\t\t\tnext = child.nextSibling;\n\t\t\tif (child.nodeName === 'SPAN' &&\n\t\t\t\tstripSpanTypeof.test(child.getAttribute('typeof') || '')) {\n\t\t\t\tunwrapSpan(node, child);\n\t\t\t}\n\t\t}\n\t};\n\tunwrapSpan = function(parent, node) {\n\t\t// first recurse to unwrap any spans in the immediate children.\n\t\tcleanSpans(node);\n\t\t// now unwrap this span.\n\t\tDOMUtils.migrateChildren(node, parent, node);\n\t\tparent.removeChild(node);\n\t};\n\tvar visit = function(node, stripLeadingWS, stripTrailingWS, inPRE) {\n\t\tvar child, next, prev;\n\t\tif (node.nodeName === 'PRE') {\n\t\t\t// Preserve newlines in <pre> tags\n\t\t\tinPRE = true;\n\t\t}\n\t\tif (!preserveIEW && DOMUtils.isText(node)) {\n\t\t\tif (!inPRE) {\n\t\t\t\tnode.data = node.data.replace(/\\s+/g, ' ');\n\t\t\t}\n\t\t\tif (stripLeadingWS) {\n\t\t\t\tnode.data = node.data.replace(/^\\s+/, '');\n\t\t\t}\n\t\t\tif (stripTrailingWS) {\n\t\t\t\tnode.data = node.data.replace(/\\s+$/, '');\n\t\t\t}\n\t\t}\n\t\t// unwrap certain SPAN nodes\n\t\tcleanSpans(node);\n\t\t// now remove comment nodes\n\t\tif (!parsoidOnly) {\n\t\t\tfor (child = node.firstChild; child; child = next) {\n\t\t\t\tnext = child.nextSibling;\n\t\t\t\tif (DOMUtils.isComment(child)) {\n\t\t\t\t\tnode.removeChild(child);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// reassemble text nodes split by a comment or span, if necessary\n\t\tnode.normalize();\n\t\t// now recurse.\n\t\tif (node.nodeName === 'PRE') {\n\t\t\t// hack, since PHP adds a newline before </pre>\n\t\t\tstripLeadingWS = false;\n\t\t\tstripTrailingWS = true;\n\t\t} else if (node.nodeName === 'SPAN' &&\n\t\t\t\t/^mw[:]/.test(node.getAttribute('typeof') || '')) {\n\t\t\t// SPAN is transparent; pass the strip parameters down to kids\n\t\t} else {\n\t\t\tstripLeadingWS = stripTrailingWS = newlineAround(node);\n\t\t}\n\t\tchild = node.firstChild;\n\t\t// Skip over the empty mw:FallbackId <span> and strip leading WS\n\t\t// on the other side of it.\n\t\tif (/^H[1-6]$/.test(node.nodeName) &&\n\t\t\tchild && WTUtils.isFallbackIdSpan(child)) {\n\t\t\tchild = child.nextSibling;\n\t\t}\n\t\tfor (; child; child = next) {\n\t\t\tnext = child.nextSibling;\n\t\t\tvisit(child,\n\t\t\t\tstripLeadingWS,\n\t\t\t\tstripTrailingWS && !child.nextSibling,\n\t\t\t\tinPRE);\n\t\t\tstripLeadingWS = false;\n\t\t}\n\t\tif (inPRE || preserveIEW) {\n\t\t\treturn node;\n\t\t}\n\t\t// now add newlines around appropriate nodes.\n\t\tfor (child = node.firstChild; child; child = next) {\n\t\t\tprev = child.previousSibling;\n\t\t\tnext = child.nextSibling;\n\t\t\tif (newlineAround(child)) {\n\t\t\t\tif (prev && DOMUtils.isText(prev)) {\n\t\t\t\t\tprev.data = prev.data.replace(/\\s*$/, '\\n');\n\t\t\t\t} else {\n\t\t\t\t\tprev = node.ownerDocument.createTextNode('\\n');\n\t\t\t\t\tnode.insertBefore(prev, child);\n\t\t\t\t}\n\t\t\t\tif (next && DOMUtils.isText(next)) {\n\t\t\t\t\tnext.data = next.data.replace(/^\\s*/, '\\n');\n\t\t\t\t} else {\n\t\t\t\t\tnext = node.ownerDocument.createTextNode('\\n');\n\t\t\t\t\tnode.insertBefore(next, child.nextSibling);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn node;\n\t};\n\t// clone body first, since we're going to destructively mutate it.\n\treturn visit(body.cloneNode(true), true, true, false);\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.TestUtils = TestUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/api-testing/Parsoid.js","messages":[{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":668,"column":26,"nodeType":"NewExpression","endLine":668,"endColumn":58},{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":680,"column":26,"nodeType":"NewExpression","endLine":680,"endColumn":58}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/** Cases for testing the Parsoid API through HTTP */\n/* global describe, it, before */\n\n'use strict';\n\nconst { action, assert, REST, utils } = require( 'api-testing' );\n\nvar domino = require('domino');\nvar should = require('chai').should();\nvar semver = require('semver');\nvar url = require('url');\nconst { DOMUtils } = require( \"../../lib/utils/DOMUtils\" );\n\nvar Util = require('../../lib/utils/Util.js').Util;\nvar JSUtils = require('../../lib/utils/jsutils').JSUtils;\n\nconst parsoidOptions = {\n\tlimits: {\n\t\twt2html: { maxWikitextSize: 20000 },\n\t\thtml2wt: { maxHTMLSize: 10000 },\n\t},\n};\n\n// FIXME(T283875): These should all be re-enabled\nvar skipForNow = true;\n\nvar defaultContentVersion = '2.8.0';\n\n// section wrappers are a distraction from the main business of\n// this file which is to verify functionality of API end points\n// independent of what they are returning and computing.\n//\n// Verifying the correctness of content is actually the job of\n// parser tests and other tests.\n//\n// So, hide most of that that distraction in a helper.\n//\n// Right now, all uses of this helper have empty lead sections.\n// But, maybe in the future, this may change. So, retain the option.\nfunction validateDoc(doc, nodeName, emptyLead) {\n\tvar leadSection = doc.body.firstChild;\n\tleadSection.nodeName.should.equal('SECTION');\n\tif (emptyLead) {\n\t\t// Could have whitespace and comments\n\t\tleadSection.childElementCount.should.equal(0);\n\t}\n\tvar nonEmptySection = emptyLead ? leadSection.nextSibling : leadSection;\n\tnonEmptySection.firstChild.nodeName.should.equal(nodeName);\n}\n\nfunction status200( res ) {\n\tassert.strictEqual( res.status, 200, res.text );\n}\n\n// Return a matcher function that checks whether a content type matches the given parameters.\nfunction contentTypeMatcher( expectedMime, expectedSpec, expectedVersion ) {\n\tconst pattern = /^([-\\w]+\\/[-\\w]+);(?: charset=utf-8;)? profile=\"https:\\/\\/www.mediawiki.org\\/wiki\\/Specs\\/([-\\w]+)\\/(\\d+\\.\\d+\\.\\d+)\"$/;\n\n\treturn ( actual ) => {\n\t\tconst parts = pattern.exec( actual );\n\t\tif ( !parts ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst [ , mime, spec, version ] = parts;\n\n\t\t// match version using caret semantics\n\t\tif ( !semver.satisfies( version, `^${ expectedVersion || defaultContentVersion }` ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( mime !== expectedMime || spec !== expectedSpec ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t};\n}\n\ndescribe('Parsoid API', function() {\n\tconst client = new REST();\n\tconst parsedUrl = new url.URL(client.req.app);\n\tconst hostname = parsedUrl.hostname;\n\tconst mockDomain = client.pathPrefix = `rest.php/${ hostname }`;\n\tconst page = utils.title( 'Lint Page ' );\n\tconst pageEncoded = encodeURIComponent( page );\n\tlet revid, oldrevid;\n\n\tbefore(async function() {\n\t\tthis.timeout(30000);\n\n\t\tconst alice = await action.alice();\n\n\t\t// Create pages\n\t\tconst oldEdit = await alice.edit(page, { text: 'First Revision Content' });\n\t\toldEdit.result.should.equal('Success');\n\t\toldrevid = oldEdit.newrevid;\n\n\t\tlet edit = await alice.edit(page, { text: '{|\\n|hi\\n|ho\\n|}' });\n\t\tedit.result.should.equal('Success');\n\t\trevid = edit.newrevid;\n\n\t\tedit = await alice.edit('JSON Page', {\n\t\t\ttext: '[1]', contentmodel: 'json'\n\t\t});\n\t\tedit.result.should.equal('Success');\n\t});\n\n\tconst acceptableHtmlResponse = function(contentVersion, expectFunc) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.headers.should.have.property('content-type');\n\t\t\tres.headers['content-type'].should.equal(\n\t\t\t\t'text/html; charset=utf-8; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + contentVersion + '\"'\n\t\t\t);\n\t\t\tres.text.should.not.equal('');\n\t\t\tif (expectFunc) {\n\t\t\t\treturn expectFunc(res.text);\n\t\t\t}\n\t\t};\n\t};\n\n\tconst acceptablePageBundleResponse = function(contentVersion, expectFunc) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.headers.should.have.property('content-type');\n\t\t\tres.headers['content-type'].should.satisfy( contentTypeMatcher( 'application/json', 'pagebundle', contentVersion ) );\n\t\t\tres.body.should.have.property('html');\n\t\t\tres.body.html.should.have.property('headers');\n\t\t\tres.body.html.headers.should.have.property('content-type');\n\t\t\tres.body.html.headers['content-type'].should.satisfy( contentTypeMatcher( 'text/html', 'HTML', contentVersion ) );\n\t\t\tres.body.html.should.have.property('body');\n\t\t\tres.body.should.have.property('data-parsoid');\n\t\t\tres.body['data-parsoid'].should.have.property('headers');\n\t\t\tres.body['data-parsoid'].headers.should.have.property('content-type');\n\t\t\tres.body['data-parsoid'].headers['content-type'].should.satisfy( contentTypeMatcher( 'application/json', 'data-parsoid', contentVersion ) );\n\t\t\tres.body['data-parsoid'].should.have.property('body');\n\t\t\tif (semver.gte(contentVersion, '999.0.0')) {\n\t\t\t\tres.body.should.have.property('data-mw');\n\t\t\t\tres.body['data-mw'].should.have.property('headers');\n\t\t\t\tres.body['data-mw'].headers.should.have.property('content-type');\n\t\t\t\tres.body['data-mw'].headers['content-type'].should.satisfy( contentTypeMatcher( 'application/json', 'data-mw', contentVersion ) );\n\t\t\t\tres.body['data-mw'].should.have.property('body');\n\t\t\t}\n\t\t\tif (expectFunc) {\n\t\t\t\treturn expectFunc(res.body.html.body);\n\t\t\t}\n\t\t};\n\t};\n\n\tdescribe('domain check', function() {\n\t\tit('should apply to /v3/transform endpoint', function(done) {\n\t\t\tclient.req\n\t\t\t\t.get('rest.php/the.wrong.domain/v3/page/wikitext/' + pageEncoded )\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tres.status.should.equal(400, res.text);\n\t\t\t\t\tres.body.error.should.equal('parameter-validation-failed');\n\t\t\t\t\tres.body.failureCode.should.equal('invalid-domain');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\tit('should apply to /v3/transform endpoint', function(done) {\n\t\t\tclient.req\n\t\t\t\t.post('rest.php/the.wrong.domain/v3/transform/wikitext/to/html/')\n\t\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tres.status.should.equal(400, res.text);\n\t\t\t\t\tres.body.error.should.equal('parameter-validation-failed');\n\t\t\t\t\tres.body.failureCode.should.equal('invalid-domain');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\t});\n\n\tdescribe('formats', function() {\n\n\t\tit('should accept application/x-www-form-urlencoded', function(done) {\n\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.type('form')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: '== h2 ==',\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tvalidateDoc(domino.createDocument(res.text), 'H2', true);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept application/json', function(done) {\n\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.type('json')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: '== h2 ==',\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tvalidateDoc(domino.createDocument(res.text), 'H2', true);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept multipart/form-data', function(done) {\n\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.field('wikitext', '== h2 ==')\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tvalidateDoc(domino.createDocument(res.text), 'H2', true);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\t// Skipped because all errors are returned as JSON in Parsoid/PHP\n\t\tit.skip('should return a plaintext error', function(done) {\n\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/wikitext/Doesnotexist')\n\t\t\t\t.expect(404)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tres.headers['content-type'].should.equal(\n\t\t\t\t\t\t'text/plain; charset=utf-8'\n\t\t\t\t\t);\n\t\t\t\t\tres.text.should.equal('Did not find page revisions for Doesnotexist');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a json error', function(done) {\n\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/Doesnotexist')\n\t\t\t\t.expect(404)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tres.headers['content-type'].should.equal(\n\t\t\t\t\t\t'application/json'\n\t\t\t\t\t);\n\t\t\t\t\tres.body.errorKey.should.equal('rest-specified-revision-unavailable');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t\t// Skipped because all errors are returned as JSON in Parsoid/PHP\n\t\tit.skip('should return an html error', function(done) {\n\t\t\tclient.req\n\t\t\t\t.get('<img src=x onerror=\"javascript:alert(\\'hi\\')\">/v3/page/html/XSS')\n\t\t\t\t.expect(404)\n\t\t\t\t.expect(function(res) {\n\t\t\t\t\tres.headers['content-type'].should.equal(\n\t\t\t\t\t\t'text/html; charset=utf-8'\n\t\t\t\t\t);\n\t\t\t\t\tres.text.should.equal('Invalid domain: &lt;img src=x onerror=&quot;javascript:alert(&apos;hi&apos;)&quot;&gt;');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t});\n\n\t});  // formats\n\n\tdescribe('accepts', function() {\n\n\t\tit('should not accept requests for older content versions (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', 'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/0.0.0\"')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(406)\n\t\t\t.expect(function(res) {\n\t\t\t\t// FIXME: See skipped html error test above\n\t\t\t\tJSON.parse(res.error.text).errorKey.should.equal(\n\t\t\t\t\t'rest-unsupported-target-format'\n\t\t\t\t);\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not accept requests for older content versions (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/0.0.0\"')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(406)\n\t\t\t.expect(function(res) {\n\t\t\t\tJSON.parse(res.error.text).errorKey.should.equal(\n\t\t\t\t\t'rest-unsupported-target-format'\n\t\t\t\t);\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not accept requests for other profiles (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', 'text/html; profile=\"something different\"')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(406)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not accept requests for other profiles (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"something different\"')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(406)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept wildcards (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', '*/*')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(defaultContentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept wildcards (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', '*/*')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(defaultContentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// T347426: Support for non-default major HTML versions has been disabled\n\t\tit.skip('should prefer higher quality (html)', function(done) {\n\t\t\tvar contentVersion = '999.0.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept',\n\t\t\t\t'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/2.8.0\"; q=0.5,' +\n\t\t\t\t'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"; q=0.8')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(contentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// T347426: Support for non-default major HTML versions has been disabled\n\t\tit.skip('should prefer higher quality (pagebundle)', function(done) {\n\t\t\tvar contentVersion = '999.0.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept',\n\t\t\t\t'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/2.8.0\"; q=0.5,' +\n\t\t\t\t'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/999.0.0\"; q=0.8')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept requests for the latest content version (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(defaultContentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept requests for the latest content version (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({ wikitext: '== h2 ==' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(defaultContentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept requests for content version 2.x (html)', function(done) {\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', 'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + contentVersion + '\"')\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(contentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept requests for content version 2.x (pagebundle)', function(done) {\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/' + contentVersion + '\"')\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\t// In < 999.x, data-mw is still inline.\n\t\t\t\thtml.should.match(/\\s+data-mw\\s*=\\s*['\"]/);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// Note that these tests aren't that useful directly after a major version bump\n\n\t\tit('should accept requests for older content version 2.x (html)', function(done) {\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', 'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/2.0.0\"')  // Keep this on the older version\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(contentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept requests for older content version 2.x (pagebundle)', function(done) {\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/2.0.0\"')  // Keep this on the older version\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\t// In < 999.x, data-mw is still inline.\n\t\t\t\thtml.should.match(/\\s+data-mw\\s*=\\s*['\"]/);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should sanity check 2.x content (pagebundle)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Missing files in wiki\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/' + contentVersion + '\"')\n\t\t\t.send({ wikitext: '[[File:Audio.oga]]' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\tvar doc = domino.createDocument(html);\n\t\t\t\tdoc.querySelectorAll('audio').length.should.equal(1);\n\t\t\t\tdoc.querySelectorAll('video').length.should.equal(0);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// T347426: Support for non-default major HTML versions has been disabled\n\t\tit.skip('should accept requests for content version 999.x (html)', function(done) {\n\t\t\tvar contentVersion = '999.0.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.set('Accept', 'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + contentVersion + '\"')\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptableHtmlResponse(contentVersion))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// T347426: Support for non-default major HTML versions has been disabled\n\t\tit.skip('should accept requests for content version 999.x (pagebundle)', function(done) {\n\t\t\tvar contentVersion = '999.0.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/' + contentVersion + '\"')\n\t\t\t.send({ wikitext: '{{1x|hi}}' })\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\t// In 999.x, data-mw is in the pagebundle.\n\t\t\t\thtml.should.not.match(/\\s+data-mw\\s*=\\s*['\"]/);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t});  // accepts\n\n\tconst validWikitextResponse = function(expected) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.headers.should.have.property('content-type');\n\t\t\tres.headers['content-type'].should.satisfy( contentTypeMatcher( 'text/plain', 'wikitext', '1.0.0' ) );\n\t\t\tif (expected !== undefined) {\n\t\t\t\tres.text.should.equal(expected);\n\t\t\t} else {\n\t\t\t\tres.text.should.not.equal('');\n\t\t\t}\n\t\t};\n\t};\n\n\tconst validHtmlResponse = function(expectFunc) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.headers.should.have.property('content-type');\n\t\t\tres.headers['content-type'].should.satisfy( contentTypeMatcher( 'text/html', 'HTML' ) );\n\t\t\tvar doc = domino.createDocument(res.text);\n\t\t\tif (expectFunc) {\n\t\t\t\treturn expectFunc(doc);\n\t\t\t} else {\n\t\t\t\tres.text.should.not.equal('');\n\t\t\t}\n\t\t};\n\t};\n\n\tconst validJsonResponse = function(expected) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.headers.should.have.property('content-type');\n\t\t\tres.headers['content-type'].should.equal( 'application/json' );\n\t\t\tif (expected !== undefined) {\n\t\t\t\tres.text.should.equal(expected);\n\t\t\t} else {\n\t\t\t\tres.text.should.not.equal('');\n\t\t\t}\n\t\t};\n\t};\n\n\tconst validPageBundleResponse = function(expectFunc) {\n\t\treturn function(res) {\n\t\t\tres.statusCode.should.equal(200, res.text);\n\t\t\tres.body.should.have.property('html');\n\t\t\tres.body.html.should.have.property('headers');\n\t\t\tres.body.html.headers.should.have.property('content-type');\n\t\t\tres.body.html.headers['content-type'].should.satisfy( contentTypeMatcher( 'text/html', 'HTML' ) );\n\t\t\tres.body.html.should.have.property('body');\n\t\t\tres.body.should.have.property('data-parsoid');\n\t\t\tres.body['data-parsoid'].should.have.property('headers');\n\t\t\tres.body['data-parsoid'].headers.should.have.property('content-type');\n\t\t\tres.body['data-parsoid'].headers['content-type'].should.satisfy( contentTypeMatcher( 'application/json', 'data-parsoid' ) );\n\t\t\tres.body['data-parsoid'].should.have.property('body');\n\t\t\t// TODO: Check data-mw when 999.x is the default.\n\t\t\tconsole.assert(!semver.gte(defaultContentVersion, '999.0.0'));\n\t\t\tvar doc = domino.createDocument(res.body.html.body);\n\t\t\tif (expectFunc) {\n\t\t\t\treturn expectFunc(doc, res.body['data-parsoid'].body);\n\t\t\t}\n\t\t};\n\t};\n\n\tdescribe('wt2lint', function() {\n\n\t\tit('should lint the given page', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Enable linting config\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/lint/' + page + '/' + revid )\n\t\t\t.expect(status200)\n\t\t\t.expect(function(res) {\n\t\t\t\tres.body.should.be.instanceof(Array);\n\t\t\t\tres.body.length.should.equal(1);\n\t\t\t\tres.body[0].type.should.equal('fostered');\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should lint the given wikitext', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Enable linting config\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/lint/')\n\t\t\t.send({\n\t\t\t\twikitext: {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t},\n\t\t\t\t\tbody: \"{|\\nhi\\n|ho\\n|}\",\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(status200)\n\t\t\t.expect(function(res) {\n\t\t\t\tres.body.should.be.instanceof(Array);\n\t\t\t\tres.body.length.should.equal(1);\n\t\t\t\tres.body[0].type.should.equal('fostered');\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should lint the given page, transform', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Enable linting config\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/lint/' + page + '/' + revid)\n\t\t\t.send({})\n\t\t\t.expect(status200)\n\t\t\t.expect(function(res) {\n\t\t\t\tres.body.should.be.instanceof(Array);\n\t\t\t\tres.body.length.should.equal(1);\n\t\t\t\tres.body[0].type.should.equal('fostered');\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should redirect title to latest revision (lint)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/lint/')\n\t\t\t.send({\n\t\t\t\t// no revid or wikitext source provided\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: page,\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect( validJsonResponse() )\n\t\t\t.end(done);\n\t\t});\n\n\t});\n\n\tdescribe(\"wt2html\", function() {\n\n\t\tit('should get from a title and old revision (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/html/${ page }/${ oldrevid }`)\n\t\t\t.redirects( 0 )\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.firstChild.textContent.should.contain('First Revision Content');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should get from a title and current revision (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/html/${ page }`)\n\t\t\t.redirects( 1 )\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.firstChild.textContent.should.contain('hi');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// Disabled for T349098\n\t\tit.skip('should get from a title and current revision (html, json content)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/html/JSON_Page')\n\t\t\t.redirects(1)\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.nodeName.should.equal('TABLE');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should get from a title and revision (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/pagebundle/${ page }/${ revid }`)\n\t\t\t.redirects( 0 )\n\t\t\t.expect(validPageBundleResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\t// Disabled for T349098\n\t\tit.skip('should get from a title and revision (pagebundle, json content)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/pagebundle/JSON_Page')\n\t\t\t.redirects(1)\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.nodeName.should.equal('TABLE');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should get from a title and revision (wikitext)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/wikitext/${ page }/${ oldrevid }`)\n\t\t\t.redirects( 0 )\n\t\t\t.expect(validWikitextResponse('First Revision Content'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit.skip('should set a custom etag for get requests (html)', function(done) {\n\t\t\tconst etagPrefixExp = new RegExp( '^\"' + revid + '/' );\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/html/${ page }/${ revid }`)\n\t\t\t.expect(validHtmlResponse())\n\t\t\t.expect((res) => {\n\t\t\t\tres.headers.should.have.property('etag');\n\t\t\t\tres.headers.etag.should.match( etagPrefixExp );\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit.skip('should set a custom etag for get requests (pagebundle)', function(done) {\n\t\t\tconst etagPrefixExp = new RegExp( '^\"' + revid + '/' );\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/pagebundle/${ page }/${ revid }`)\n\t\t\t.expect(validPageBundleResponse())\n\t\t\t.expect((res) => {\n\t\t\t\tres.headers.should.have.property('etag');\n\t\t\t\tres.headers.etag.should.match( etagPrefixExp );\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept wikitext as a string for html', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: \"== h2 ==\",\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept json contentmodel as a string for html', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: '{\"1\":2}',\n\t\t\t\tcontentmodel: 'json',\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.nodeName.should.equal('TABLE');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept wikitext as a string for pagebundle', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\twikitext: \"== h2 ==\",\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept json contentmodel as a string for pagebundle', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\twikitext: '{\"1\":2}',\n\t\t\t\tcontentmodel: 'json',\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.nodeName.should.equal('TABLE');\n\t\t\t\tshould.not.exist(doc.querySelector('*[typeof=\"mw:Error\"]'));\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept wikitext with headers', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t},\n\t\t\t\t\tbody: \"== h2 ==\",\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should require a title when no wikitext is provided (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should require a title when no wikitext is provided (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when revision not found (page, html)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/html/Doesnotexist')\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when revision not found (page, pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/pagebundle/Doesnotexist')\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when revision not found (transform, wt2html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/Doesnotexist')\n\t\t\t.send({})\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when revision not found (transform, wt2pb)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/Doesnotexist')\n\t\t\t.send({})\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept an original title (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\t// no revid or wikitext source provided\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: page,\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept an original title (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\t// no revid or wikitext source provided\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: page,\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept an original title, other than main', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\t// no revid or wikitext source provided\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: page,\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not require a title when empty wikitext is provided (html)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: '',\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.children.length.should.equal(1); // empty lead section\n\t\t\t\tdoc.body.firstChild.nodeName.should.equal('SECTION');\n\t\t\t\tdoc.body.firstChild.children.length.should.equal(0);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not require a title when empty wikitext is provided (pagebundle)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\twikitext: '',\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not require a title when wikitext is provided', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: \"== h2 ==\",\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not require a rev id when wikitext and a title is provided', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/Main_Page')\n\t\t\t.send({\n\t\t\t\twikitext: \"== h2 ==\",\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept the wikitext source as original data', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + `/v3/transform/wikitext/to/html/${ page }/${ revid }`)\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"== h2 ==\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should use the proper source text', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Missing template 1x\n\t\t\tclient.req\n\t\t\t.post(mockDomain + `/v3/transform/wikitext/to/html/${ page }/${ revid }`)\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"{{1x|foo|bar=bat}}\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'P', false);\n\t\t\t\tvar span = doc.querySelector('span[typeof=\"mw:Transclusion\"]');\n\t\t\t\tvar dmw = JSON.parse(span.getAttribute('data-mw'));\n\t\t\t\tvar template = dmw.parts[0].template;\n\t\t\t\ttemplate.target.wt.should.equal('1x');\n\t\t\t\ttemplate.params[1].wt.should.equal('foo');\n\t\t\t\ttemplate.params.bar.wt.should.equal('bat');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept the wikitext source as original without a title or revision', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"== h2 ==\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tvalidateDoc(doc, 'H2', true);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit(\"should respect body parameter in wikitext->html (body_only)\", function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: \"''foo''\",\n\t\t\t\tbody_only: 1,\n\t\t\t})\n\t\t\t.expect(validHtmlResponse())\n\t\t\t.expect(function(res) {\n\t\t\t\t// v3 only returns children of <body>\n\t\t\t\tres.text.should.not.match(/<body/);\n\t\t\t\tres.text.should.match(/<p/);\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit(\"should respect body parameter in wikitext->pagebundle requests (body_only)\", function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\twikitext: \"''foo''\",\n\t\t\t\tbody_only: 1,\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse())\n\t\t\t.expect(function(res) {\n\t\t\t\t// v3 only returns children of <body>\n\t\t\t\tres.body.html.body.should.not.match(/<body/);\n\t\t\t\tres.body.html.body.should.match(/<p/);\n\t\t\t\t// No section wrapping in body-only mode\n\t\t\t\tres.body.html.body.should.not.match(/<section/);\n\t\t\t})\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not include captured offsets', function(done) {\n\t\t\tclient.req\n\t\t\t.get(mockDomain + `/v3/page/pagebundle/${ page }/${ revid }`)\n\t\t\t.expect(validPageBundleResponse(function(doc, dp) {\n\t\t\t\tdp.should.not.have.property('sectionOffsets');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a request too large error (post wt)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Set limits in config\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Large_Page',\n\t\t\t\t},\n\t\t\t\twikitext: \"a\".repeat(parsoidOptions.limits.wt2html.maxWikitextSize + 1),\n\t\t\t})\n\t\t\t.expect(413)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a request too large error (get page)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Set limits in config\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/html/Large_Page/3')\n\t\t\t.expect(413)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should add redlinks for get (html)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Create Relinks_Page\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/html/Redlinks_Page/103')\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.querySelectorAll('a').length.should.equal(3);\n\t\t\t\tvar redLinks = doc.body.querySelectorAll('.new');\n\t\t\t\tredLinks.length.should.equal(1);\n\t\t\t\tredLinks[0].getAttribute('title').should.equal('Doesnotexist');\n\t\t\t\tvar redirects = doc.body.querySelectorAll('.mw-redirect');\n\t\t\t\tredirects.length.should.equal(1);\n\t\t\t\tredirects[0].getAttribute('title').should.equal('Redirected');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should add redlinks for get (pagebundle)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Create Redlinks_Page\n\t\t\tclient.req\n\t\t\t.get(mockDomain + '/v3/page/pagebundle/Redlinks_Page/103')\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.querySelectorAll('a').length.should.equal(3);\n\t\t\t\tvar redLinks = doc.body.querySelectorAll('.new');\n\t\t\t\tredLinks.length.should.equal(1);\n\t\t\t\tredLinks[0].getAttribute('title').should.equal('Doesnotexist');\n\t\t\t\tvar redirects = doc.body.querySelectorAll('.mw-redirect');\n\t\t\t\tredirects.length.should.equal(1);\n\t\t\t\tredirects[0].getAttribute('title').should.equal('Redirected');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should add redlinks for transform (html)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Fix redlinks count, by creating pages\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t.send({\n\t\t\t\twikitext: \"[[Special:Version]] [[Doesnotexist]] [[Redirected]]\",\n\t\t\t})\n\t\t\t.expect(validHtmlResponse(function(doc) {\n\t\t\t\tdoc.body.querySelectorAll('a').length.should.equal(3);\n\t\t\t\tvar redLinks = doc.body.querySelectorAll('.new');\n\t\t\t\tredLinks.length.should.equal(1);\n\t\t\t\tredLinks[0].getAttribute('title').should.equal('Doesnotexist');\n\t\t\t\tvar redirects = doc.body.querySelectorAll('.mw-redirect');\n\t\t\t\tredirects.length.should.equal(1);\n\t\t\t\tredirects[0].getAttribute('title').should.equal('Redirected');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should add redlinks for transform (pagebundle)', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Fix redlinks count, by creating pages\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\twikitext: \"[[Special:Version]] [[Doesnotexist]] [[Redirected]]\",\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.querySelectorAll('a').length.should.equal(3);\n\t\t\t\tvar redLinks = doc.body.querySelectorAll('.new');\n\t\t\t\tredLinks.length.should.equal(1);\n\t\t\t\tredLinks[0].getAttribute('title').should.equal('Doesnotexist');\n\t\t\t\tvar redirects = doc.body.querySelectorAll('.mw-redirect');\n\t\t\t\tredirects.length.should.equal(1);\n\t\t\t\tredirects[0].getAttribute('title').should.equal('Redirected');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\t(skipForNow ? describe.skip : describe)('Variant conversion', function() {\n\n\t\t\tit('should not perform unnecessary variant conversion for get of en page (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + `/v3/page/html/${ page }/${ revid }`)\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect(validHtmlResponse())\n\t\t\t\t.expect('Content-Language', 'en')\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst vary = res.headers.vary || '';\n\t\t\t\t\tvary.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform unnecessary variant conversion for get of en page (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + `/v3/page/pagebundle/${ page }/${ revid }`)\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect(validPageBundleResponse())\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// HTTP headers should not be set.\n\t\t\t\t\tconst vary1 = res.headers.vary || '';\n\t\t\t\t\tvary1.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang1 = res.headers['content-language'] || '';\n\t\t\t\t\tlang1.should.equal('');\n\t\t\t\t\t// But equivalent headers should be present in the JSON body.\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\tconst vary2 = headers.vary || '';\n\t\t\t\t\tvary2.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang2 = headers['content-language'];\n\t\t\t\t\tlang2.should.equal('en');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform unnecessary variant conversion for get on page w/ magic word (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/html/No_Variant_Page/105')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\t// No conversion done since __NOCONTENTCONVERT__ is set\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd\\n');\n\t\t\t\t}))\n\t\t\t\t// But the vary/language headers are still set.\n\t\t\t\t.expect('Content-Language', 'sr-el')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform unnecessary variant conversion for get on page w/ magic word (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/No_Variant_Page/105')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\t// No conversion done since __NOCONTENTCONVERT__ is set\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd\\n');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// HTTP headers should not be set.\n\t\t\t\t\tconst vary1 = res.headers.vary || '';\n\t\t\t\t\tvary1.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang1 = res.headers['content-language'] || '';\n\t\t\t\t\tlang1.should.equal('');\n\t\t\t\t\t// But vary/language headers should be set in JSON body.\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\tconst vary2 = headers.vary || '';\n\t\t\t\t\tvary2.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang2 = headers['content-language'];\n\t\t\t\t\tlang2.should.equal('sr-el');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform unrequested variant conversion for get w/ no accept-language header (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/html/Variant_Page/104')\n\t\t\t\t// no accept-language header sent\n\t\t\t\t.expect('Content-Language', 'sr')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform unrequested variant conversion for get w/ no accept-language header (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/Variant_Page/104')\n\t\t\t\t// no accept-language header sent\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// HTTP headers should not be set.\n\t\t\t\t\tconst vary1 = res.headers.vary || '';\n\t\t\t\t\tvary1.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang1 = res.headers['content-language'] || '';\n\t\t\t\t\tlang1.should.equal('');\n\t\t\t\t\t// But vary/language headers should be set in JSON body.\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\tconst vary2 = headers.vary || '';\n\t\t\t\t\tvary2.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang2 = headers['content-language'];\n\t\t\t\t\tlang2.should.equal('sr');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform variant conversion for get w/ base variant specified (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/html/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr') // this is base variant\n\t\t\t\t.expect('Content-Language', 'sr')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform variant conversion for get w/ base variant specified (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr') // this is base variant\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// HTTP headers should not be set.\n\t\t\t\t\tconst vary1 = res.headers.vary || '';\n\t\t\t\t\tvary1.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang1 = res.headers['content-language'] || '';\n\t\t\t\t\tlang1.should.equal('');\n\t\t\t\t\t// But vary/language headers should be set in JSON body.\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\tconst vary2 = headers.vary || '';\n\t\t\t\t\tvary2.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang2 = headers['content-language'];\n\t\t\t\t\tlang2.should.equal('sr');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform variant conversion for get w/ invalid variant specified (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/html/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr-BOGUS') // this doesn't exist\n\t\t\t\t.expect('Content-Language', 'sr')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform variant conversion for get w/ invalid variant specified (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr-BOGUS') // this doesn't exist\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// HTTP headers should not be set.\n\t\t\t\t\tconst vary1 = res.headers.vary || '';\n\t\t\t\t\tvary1.should.not.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang1 = res.headers['content-language'] || '';\n\t\t\t\t\tlang1.should.equal('');\n\t\t\t\t\t// But vary/language headers should be set in JSON body.\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\tconst vary2 = headers.vary || '';\n\t\t\t\t\tvary2.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t\tconst lang2 = headers['content-language'];\n\t\t\t\t\tlang2.should.equal('sr');\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for get (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/html/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect('Content-Language', 'sr-el')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for get (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.get(mockDomain + '/v3/page/pagebundle/Variant_Page/104')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage in HTTP header (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.set('Content-Language', 'sr')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: \"абвг abcd x\",\n\t\t\t\t})\n\t\t\t\t.expect('Content-Language', 'sr-el')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd x');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage in HTTP header (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.set('Content-Language', 'sr')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: \"абвг abcd x\",\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd x');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage in JSON header (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-language': 'sr',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"абвг abcd x\",\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect('Content-Language', 'sr-el')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd x');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage in JSON header (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.send({\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-language': 'sr',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"абвг abcd\",\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage from oldid (html)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/html/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.send({\n\t\t\t\t\toriginal: { revid: 104 },\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\tbody: \"абвг abcd x\",\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect('Content-Language', 'sr-el')\n\t\t\t\t.expect('Vary', /\\bAccept-Language\\b/i)\n\t\t\t\t.expect(validHtmlResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd x');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should perform variant conversion for transform given pagelanguage from oldid (pagebundle)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/wikitext/to/pagebundle/')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.send({\n\t\t\t\t\toriginal: { revid: 104 },\n\t\t\t\t\twikitext: \"абвг abcd\",\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse((doc) => {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t});\n\n\t}); // end wt2html\n\n\tdescribe(\"html2wt\", function() {\n\n\t\tit('should require html when serializing', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when revision not found (transform, html2wt)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/Doesnotexist/1122334455')\n\t\t\t.send({\n\t\t\t\thtml: '<pre>hi ho</pre>'\n\t\t\t})\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should error when if-match doesn\\'t match', function(done) {\n\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t\t.set( 'if-match', '\"1234/deadbeef\"' )\n\t\t\t\t.send({\n\t\t\t\t\thtml: '<pre>hi ho</pre>'\n\t\t\t\t})\n\t\t\t\t.expect(412)\n\t\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not error when oldid not supplied (transform, html2wt)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/Doesnotexist')\n\t\t\t.send({\n\t\t\t\thtml: '<pre>hi ho</pre>'\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(' hi ho\\n'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept html as a string', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept html for json contentmodel as a string', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\"><head prefix=\"mwr: http://en.wikipedia.org/wiki/Special:Redirect/\"><meta charset=\"utf-8\"/><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:isVersionOf\" href=\"//en.wikipedia.org/wiki/Main_Page\"/><title></title><base href=\"//en.wikipedia.org/wiki/\"/><link rel=\"stylesheet\" href=\"//en.wikipedia.org/w/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid|ext.cite.style&amp;only=styles&amp;skin=vector\"/></head><body lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><table class=\"mw-json mw-json-object\"><tbody><tr><th>a</th><td class=\"value mw-json-number\">4</td></tr><tr><th>b</th><td class=\"value mw-json-number\">3</td></tr></tbody></table></body></html>',\n\t\t\t\tcontentmodel: 'json',\n\t\t\t})\n\t\t\t.expect(validJsonResponse('{\"a\":4,\"b\":3}'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept html with headers', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: {\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t},\n\t\t\t\t\tbody: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should allow a title in the url', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/Main_Page')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should allow a title in the original data', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: \"Main_Page\",\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should allow a revision id in the url', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + `/v3/transform/html/to/wikitext/${ page }/${ revid }`)\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should allow a revision id in the original data', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\trevid,\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept original wikitext as src', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/plain;profile=\"https://www.mediawiki.org/wiki/Specs/wikitext/1.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<strong>MediaWiki has been successfully installed.</strong>\\n\\nConsult the [//meta.wikimedia.org/wiki/Help:Contents User\\'s Guide] for information on using the wiki software.\\n\\n== Getting started ==\\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\\n* [//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\\n',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept original html for selser (default)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"<!DOCTYPE html>\\n<html prefix=\\\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\\\" about=\\\"http://localhost/index.php/Special:Redirect/revision/1\\\"><head prefix=\\\"mwr: http://localhost/index.php/Special:Redirect/\\\"><meta property=\\\"mw:articleNamespace\\\" content=\\\"0\\\"/><link rel=\\\"dc:replaces\\\" resource=\\\"mwr:revision/0\\\"/><meta property=\\\"dc:modified\\\" content=\\\"2014-09-12T22:46:59.000Z\\\"/><meta about=\\\"mwr:user/0\\\" property=\\\"dc:title\\\" content=\\\"MediaWiki default\\\"/><link rel=\\\"dc:contributor\\\" resource=\\\"mwr:user/0\\\"/><meta property=\\\"mw:revisionSHA1\\\" content=\\\"8e0aa2f2a7829587801db67d0424d9b447e09867\\\"/><meta property=\\\"dc:description\\\" content=\\\"\\\"/><link rel=\\\"dc:isVersionOf\\\" href=\\\"http://localhost/index.php/Main_Page\\\"/><title>Main_Page</title><base href=\\\"http://localhost/index.php/\\\"/><link rel=\\\"stylesheet\\\" href=\\\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\\\"/></head><body id=\\\"mwAA\\\" lang=\\\"en\\\" class=\\\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\\\" dir=\\\"ltr\\\"><p id=\\\"mwAQ\\\"><strong id=\\\"mwAg\\\">MediaWiki has been successfully installed.</strong></p>\\n\\n<p id=\\\"mwAw\\\">Consult the <a rel=\\\"mw:ExtLink\\\" href=\\\"//meta.wikimedia.org/wiki/Help:Contents\\\" id=\\\"mwBA\\\">User's Guide</a> for information on using the wiki software.</p>\\n\\n<h2 id=\\\"mwBQ\\\"> Getting started </h2>\\n<ul id=\\\"mwBg\\\"><li id=\\\"mwBw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\\\" id=\\\"mwCA\\\">Configuration settings list</a></li>\\n<li id=\\\"mwCQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\\\" id=\\\"mwCg\\\">MediaWiki FAQ</a></li>\\n<li id=\\\"mwCw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\\\" id=\\\"mwDA\\\">MediaWiki release mailing list</a></li>\\n<li id=\\\"mwDQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\\\" id=\\\"mwDg\\\">Localise MediaWiki for your language</a></li></ul></body></html>\",\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"counter\": 14,\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\t\"mwAA\": { \"dsr\": [0, 592, 0, 0] }, \"mwAQ\": { \"dsr\": [0, 59, 0, 0] }, \"mwAg\": { \"stx\": \"html\", \"dsr\": [0, 59, 8, 9] }, \"mwAw\": { \"dsr\": [61, 171, 0, 0] }, \"mwBA\": { \"dsr\": [73, 127, 41, 1] }, \"mwBQ\": { \"dsr\": [173, 194, 2, 2] }, \"mwBg\": { \"dsr\": [195, 592, 0, 0] }, \"mwBw\": { \"dsr\": [195, 300, 1, 0] }, \"mwCA\": { \"dsr\": [197, 300, 75, 1] }, \"mwCQ\": { \"dsr\": [301, 373, 1, 0] }, \"mwCg\": { \"dsr\": [303, 373, 56, 1] }, \"mwCw\": { \"dsr\": [374, 472, 1, 0] }, \"mwDA\": { \"dsr\": [376, 472, 65, 1] }, \"mwDQ\": { \"dsr\": [473, 592, 1, 0] }, \"mwDg\": { \"dsr\": [475, 592, 80, 1] },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept original html for selser (1.1.1, meta)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><meta property=\"mw:html:version\" content=\"1.1.1\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html; profile=\"mediawiki.org/specs/html/1.1.1\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"<!DOCTYPE html>\\n<html prefix=\\\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\\\" about=\\\"http://localhost/index.php/Special:Redirect/revision/1\\\"><head prefix=\\\"mwr: http://localhost/index.php/Special:Redirect/\\\"><meta property=\\\"mw:articleNamespace\\\" content=\\\"0\\\"/><link rel=\\\"dc:replaces\\\" resource=\\\"mwr:revision/0\\\"/><meta property=\\\"dc:modified\\\" content=\\\"2014-09-12T22:46:59.000Z\\\"/><meta about=\\\"mwr:user/0\\\" property=\\\"dc:title\\\" content=\\\"MediaWiki default\\\"/><link rel=\\\"dc:contributor\\\" resource=\\\"mwr:user/0\\\"/><meta property=\\\"mw:revisionSHA1\\\" content=\\\"8e0aa2f2a7829587801db67d0424d9b447e09867\\\"/><meta property=\\\"dc:description\\\" content=\\\"\\\"/><link rel=\\\"dc:isVersionOf\\\" href=\\\"http://localhost/index.php/Main_Page\\\"/><title>Main_Page</title><base href=\\\"http://localhost/index.php/\\\"/><link rel=\\\"stylesheet\\\" href=\\\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\\\"/></head><body id=\\\"mwAA\\\" lang=\\\"en\\\" class=\\\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\\\" dir=\\\"ltr\\\"><p id=\\\"mwAQ\\\"><strong id=\\\"mwAg\\\">MediaWiki has been successfully installed.</strong></p>\\n\\n<p id=\\\"mwAw\\\">Consult the <a rel=\\\"mw:ExtLink\\\" href=\\\"//meta.wikimedia.org/wiki/Help:Contents\\\" id=\\\"mwBA\\\">User's Guide</a> for information on using the wiki software.</p>\\n\\n<h2 id=\\\"mwBQ\\\"> Getting started </h2>\\n<ul id=\\\"mwBg\\\"><li id=\\\"mwBw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\\\" id=\\\"mwCA\\\">Configuration settings list</a></li>\\n<li id=\\\"mwCQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\\\" id=\\\"mwCg\\\">MediaWiki FAQ</a></li>\\n<li id=\\\"mwCw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\\\" id=\\\"mwDA\\\">MediaWiki release mailing list</a></li>\\n<li id=\\\"mwDQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\\\" id=\\\"mwDg\\\">Localise MediaWiki for your language</a></li></ul></body></html>\",\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/0.0.1\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"counter\": 14,\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\t\"mwAA\": { \"dsr\": [0, 592, 0, 0] }, \"mwAQ\": { \"dsr\": [0, 59, 0, 0] }, \"mwAg\": { \"stx\": \"html\", \"dsr\": [0, 59, 8, 9] }, \"mwAw\": { \"dsr\": [61, 171, 0, 0] }, \"mwBA\": { \"dsr\": [73, 127, 41, 1] }, \"mwBQ\": { \"dsr\": [173, 194, 2, 2] }, \"mwBg\": { \"dsr\": [195, 592, 0, 0] }, \"mwBw\": { \"dsr\": [195, 300, 1, 0] }, \"mwCA\": { \"dsr\": [197, 300, 75, 1] }, \"mwCQ\": { \"dsr\": [301, 373, 1, 0] }, \"mwCg\": { \"dsr\": [303, 373, 56, 1] }, \"mwCw\": { \"dsr\": [374, 472, 1, 0] }, \"mwDA\": { \"dsr\": [376, 472, 65, 1] }, \"mwDQ\": { \"dsr\": [473, 592, 1, 0] }, \"mwDg\": { \"dsr\": [475, 592, 80, 1] },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept original html for selser (1.1.1, headers)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\t// Don't set the mw:html:version so that we get it from the original/headers\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html prefix=\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\" about=\"http://localhost/index.php/Special:Redirect/revision/1\"><head prefix=\"mwr: http://localhost/index.php/Special:Redirect/\"><meta property=\"mw:articleNamespace\" content=\"0\"/><link rel=\"dc:replaces\" resource=\"mwr:revision/0\"/><meta property=\"dc:modified\" content=\"2014-09-12T22:46:59.000Z\"/><meta about=\"mwr:user/0\" property=\"dc:title\" content=\"MediaWiki default\"/><link rel=\"dc:contributor\" resource=\"mwr:user/0\"/><meta property=\"mw:revisionSHA1\" content=\"8e0aa2f2a7829587801db67d0424d9b447e09867\"/><meta property=\"dc:description\" content=\"\"/><link rel=\"dc:isVersionOf\" href=\"http://localhost/index.php/Main_Page\"/><title>Main_Page</title><base href=\"http://localhost/index.php/\"/><link rel=\"stylesheet\" href=\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\"/></head><body data-parsoid=\\'{\"dsr\":[0,592,0,0]}\\' lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\" dir=\"ltr\"><p data-parsoid=\\'{\"dsr\":[0,59,0,0]}\\'><strong data-parsoid=\\'{\"stx\":\"html\",\"dsr\":[0,59,8,9]}\\'>MediaWiki has been successfully installed.</strong></p>\\n\\n<p data-parsoid=\\'{\"dsr\":[61,171,0,0]}\\'>Consult the <a rel=\"mw:ExtLink\" href=\"//meta.wikimedia.org/wiki/Help:Contents\" data-parsoid=\\'{\"dsr\":[73,127,41,1]}\\'>User\\'s Guide</a> for information on using the wiki software.</p>\\n\\n<h2 data-parsoid=\\'{\"dsr\":[173,194,2,2]}\\'> Getting started </h2>\\n<ul data-parsoid=\\'{\"dsr\":[195,592,0,0]}\\'><li data-parsoid=\\'{\"dsr\":[195,300,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\" data-parsoid=\\'{\"dsr\":[197,300,75,1]}\\'>Configuration settings list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[301,373,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\" data-parsoid=\\'{\"dsr\":[303,373,56,1]}\\'>MediaWiki FAQ</a></li>\\n<li data-parsoid=\\'{\"dsr\":[374,472,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\" data-parsoid=\\'{\"dsr\":[376,472,65,1]}\\'>MediaWiki release mailing list</a></li>\\n<li data-parsoid=\\'{\"dsr\":[473,592,1,0]}\\'> <a rel=\"mw:ExtLink\" href=\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\" data-parsoid=\\'{\"dsr\":[475,592,80,1]}\\'>Localise MediaWiki for your language</a></li></ul></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html; profile=\"mediawiki.org/specs/html/1.1.1\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: \"<!DOCTYPE html>\\n<html prefix=\\\"dc: http://purl.org/dc/terms/ mw: http://mediawiki.org/rdf/\\\" about=\\\"http://localhost/index.php/Special:Redirect/revision/1\\\"><head prefix=\\\"mwr: http://localhost/index.php/Special:Redirect/\\\"><meta property=\\\"mw:articleNamespace\\\" content=\\\"0\\\"/><link rel=\\\"dc:replaces\\\" resource=\\\"mwr:revision/0\\\"/><meta property=\\\"dc:modified\\\" content=\\\"2014-09-12T22:46:59.000Z\\\"/><meta about=\\\"mwr:user/0\\\" property=\\\"dc:title\\\" content=\\\"MediaWiki default\\\"/><link rel=\\\"dc:contributor\\\" resource=\\\"mwr:user/0\\\"/><meta property=\\\"mw:revisionSHA1\\\" content=\\\"8e0aa2f2a7829587801db67d0424d9b447e09867\\\"/><meta property=\\\"dc:description\\\" content=\\\"\\\"/><link rel=\\\"dc:isVersionOf\\\" href=\\\"http://localhost/index.php/Main_Page\\\"/><title>Main_Page</title><base href=\\\"http://localhost/index.php/\\\"/><link rel=\\\"stylesheet\\\" href=\\\"//localhost/load.php?modules=mediawiki.legacy.commonPrint,shared|mediawiki.skinning.elements|mediawiki.skinning.content|mediawiki.skinning.interface|skins.vector.styles|site|mediawiki.skinning.content.parsoid&amp;only=styles&amp;debug=true&amp;skin=vector\\\"/></head><body id=\\\"mwAA\\\" lang=\\\"en\\\" class=\\\"mw-content-ltr sitedir-ltr ltr mw-body mw-body-content mediawiki\\\" dir=\\\"ltr\\\"><p id=\\\"mwAQ\\\"><strong id=\\\"mwAg\\\">MediaWiki has been successfully installed.</strong></p>\\n\\n<p id=\\\"mwAw\\\">Consult the <a rel=\\\"mw:ExtLink\\\" href=\\\"//meta.wikimedia.org/wiki/Help:Contents\\\" id=\\\"mwBA\\\">User's Guide</a> for information on using the wiki software.</p>\\n\\n<h2 id=\\\"mwBQ\\\"> Getting started </h2>\\n<ul id=\\\"mwBg\\\"><li id=\\\"mwBw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings\\\" id=\\\"mwCA\\\">Configuration settings list</a></li>\\n<li id=\\\"mwCQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ\\\" id=\\\"mwCg\\\">MediaWiki FAQ</a></li>\\n<li id=\\\"mwCw\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce\\\" id=\\\"mwDA\\\">MediaWiki release mailing list</a></li>\\n<li id=\\\"mwDQ\\\"> <a rel=\\\"mw:ExtLink\\\" href=\\\"//www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources\\\" id=\\\"mwDg\\\">Localise MediaWiki for your language</a></li></ul></body></html>\",\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/0.0.1\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"counter\": 14,\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\t\"mwAA\": { \"dsr\": [0, 592, 0, 0] }, \"mwAQ\": { \"dsr\": [0, 59, 0, 0] }, \"mwAg\": { \"stx\": \"html\", \"dsr\": [0, 59, 8, 9] }, \"mwAw\": { \"dsr\": [61, 171, 0, 0] }, \"mwBA\": { \"dsr\": [73, 127, 41, 1] }, \"mwBQ\": { \"dsr\": [173, 194, 2, 2] }, \"mwBg\": { \"dsr\": [195, 592, 0, 0] }, \"mwBw\": { \"dsr\": [195, 300, 1, 0] }, \"mwCA\": { \"dsr\": [197, 300, 75, 1] }, \"mwCQ\": { \"dsr\": [301, 373, 1, 0] }, \"mwCg\": { \"dsr\": [303, 373, 56, 1] }, \"mwCw\": { \"dsr\": [374, 472, 1, 0] }, \"mwDA\": { \"dsr\": [376, 472, 65, 1] }, \"mwDQ\": { \"dsr\": [473, 592, 1, 0] }, \"mwDg\": { \"dsr\": [475, 592, 80, 1] },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse())\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return http 400 if supplied data-parsoid is empty', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<html><head></head><body><p>hi</p></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<html><head></head><body><p>ho</p></body></html>',\n\t\t\t\t\t},\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: {},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\t// FIXME: Pagebundle validation in general is needed\n\t\tit.skip('should return http 400 if supplied data-parsoid is a string', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<html><head></head><body><p>hi</p></body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<html><head></head><body><p>ho</p></body></html>',\n\t\t\t\t\t},\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: 'Garbled text from RESTBase.',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\t// The following three tests should all serialize as:\n\t\t//   \"<div>Selser test\"\n\t\t// However, we're deliberately setting the original wikitext in\n\t\t// the first two to garbage so that when selser doesn't detect any\n\t\t// difference between the new and old html, it'll just reuse that\n\t\t// string and we have a reliable way of determining that selser\n\t\t// was used.\n\n\t\tit('should use selser with supplied wikitext', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Create Junk Page\n\t\t\t// New and old html are identical, which should produce no diffs\n\t\t\t// and reuse the original wikitext.\n\t\t\tclient.req\n\t\t\t// Need to provide an oldid so that selser mode is enabled\n\t\t\t// Without an oldid, serialization falls back to non-selser wts.\n\t\t\t// The oldid is used to fetch wikitext, but if wikitext is provided\n\t\t\t// (as in this test), it is not used. So, for testing purposes,\n\t\t\t// we can use any old random id, as long as something is present.\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: \"Junk Page\",\n\t\t\t\t\trevid: 1234,\n\t\t\t\t\twikitext: {\n\t\t\t\t\t\tbody: \"1. This is just some junk. See the comment above.\",\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\tbody: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\tmwAA: {},\n\t\t\t\t\t\t\t\tmwBB: { \"autoInsertedEnd\": true, \"stx\": \"html\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(\n\t\t\t\t\"1. This is just some junk. See the comment above.\"\n\t\t\t))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should use selser with wikitext fetched from the mw api', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Creat Junk Page\n\t\t\t// New and old html are identical, which should produce no diffs\n\t\t\t// and reuse the original wikitext.\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\toriginal: {\n\t\t\t\t\trevid: 2,\n\t\t\t\t\ttitle: \"Junk Page\",\n\t\t\t\t\thtml: {\n\t\t\t\t\t\tbody: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\tmwAA: {},\n\t\t\t\t\t\t\t\tmwBB: { \"autoInsertedEnd\": true, \"stx\": \"html\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(\n\t\t\t\t\"2. This is just some junk. See the comment above.\"\n\t\t\t))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should fallback to non-selective serialization', function(done) {\n\t\t\t// Without the original wikitext and an unavailable\n\t\t\t// TemplateFetch for the source (no revision id provided),\n\t\t\t// it should fallback to non-selective serialization.\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: \"Junk Page\",\n\t\t\t\t\thtml: {\n\t\t\t\t\t\tbody: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">Selser test</div></body></html>\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\tmwAA: {},\n\t\t\t\t\t\t\t\tmwBB: { \"autoInsertedEnd\": true, \"stx\": \"html\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(\n\t\t\t\t\"<div>Selser test\"\n\t\t\t))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should apply data-parsoid to duplicated ids', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">data-parsoid test</div><div id=\\\"mwBB\\\">data-parsoid test</div></body></html>\",\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: \"Doesnotexist\",\n\t\t\t\t\thtml: {\n\t\t\t\t\t\tbody: \"<html><body id=\\\"mwAA\\\"><div id=\\\"mwBB\\\">data-parsoid test</div></body></html>\",\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t\"data-parsoid\": {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\t\"ids\": {\n\t\t\t\t\t\t\t\tmwAA: {},\n\t\t\t\t\t\t\t\tmwBB: { \"autoInsertedEnd\": true, \"stx\": \"html\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(\n\t\t\t\t\"<div>data-parsoid test<div>data-parsoid test\"\n\t\t\t))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a 400 for missing inline data-mw (2.x)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">hi</p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/2.8.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a 400 for not supplying data-mw', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">hi</p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should apply original data-mw', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">hi</p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"parts\": [{ \"template\": { \"target\": { \"wt\": \"1x\", \"href\": \"./Template:1x\" }, \"params\": { \"1\": { \"wt\": \"hi\" } }, \"i\": 0 } }] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('{{1x|hi}}'))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// Sanity check data-mw was applied in the previous test\n\t\tit('should return a 400 for missing modified data-mw', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">hi</p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { } },  // Missing data-mw.parts!\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should give precedence to inline data-mw over original', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" data-mw=\\'{\"parts\":[{\"template\":{\"target\":{\"wt\":\"1x\",\"href\":\"./Template:1x\"},\"params\":{\"1\":{\"wt\":\"hi\"}},\"i\":0}}]}\\' id=\"mwAQ\">hi</p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { } },  // Missing data-mw.parts!\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('{{1x|hi}}'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should not apply original data-mw if modified is supplied', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">hi</p>',\n\t\t\t\t'data-mw': {\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tids: { \"mwAQ\": { \"parts\": [{ \"template\": { \"target\": { \"wt\": \"1x\", \"href\": \"./Template:1x\" }, \"params\": { \"1\": { \"wt\": \"hi\" } }, \"i\": 0 } }] } },\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { } },  // Missing data-mw.parts!\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('{{1x|hi}}'))\n\t\t\t.end(done);\n\t\t});\n\n\t\t// The next three tests, although redundant with the above precedence\n\t\t// tests, are an attempt to show clients the semantics of separate\n\t\t// data-mw in the API.  The main idea is,\n\t\t//\n\t\t//   non-inline-data-mw = modified || original\n\t\t//   inline-data-mw > non-inline-data-mw\n\n\t\tit('should apply original data-mw when modified is absent (captions 1)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"optList\": [{ \"ck\": \"caption\", \"ak\": \"Testing 123\" }] },\n\t\t\t\t\t\t\t\t\"mwAw\": { \"a\": { \"href\": \"./File:Foobar.jpg\" }, \"sa\": {} },\n\t\t\t\t\t\t\t\t\"mwBA\": { \"a\": { \"resource\": \"./File:Foobar.jpg\", \"height\": \"28\", \"width\": \"240\" },\"sa\": { \"resource\": \"File:Foobar.jpg\" } },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"caption\": \"Testing 123\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('[[File:Foobar.jpg|Testing 123]]'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should give precedence to inline data-mw over modified (captions 2)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" data-mw=\"{}\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\t'data-mw': {\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\"mwAg\": { \"caption\": \"Testing 123\" },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"optList\": [{ \"ck\": \"caption\", \"ak\": \"Testing 123\" }] },\n\t\t\t\t\t\t\t\t\"mwAw\": { \"a\": { \"href\": \"./File:Foobar.jpg\" }, \"sa\": {} },\n\t\t\t\t\t\t\t\t\"mwBA\": { \"a\": { \"resource\": \"./File:Foobar.jpg\", \"height\": \"28\", \"width\": \"240\" },\"sa\": { \"resource\": \"File:Foobar.jpg\" } },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"caption\": \"Testing 123\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('[[File:Foobar.jpg]]'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should give precedence to modified data-mw over original (captions 3)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\t'data-mw': {\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\"mwAg\": {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"optList\": [{ \"ck\": \"caption\", \"ak\": \"Testing 123\" }] },\n\t\t\t\t\t\t\t\t\"mwAw\": { \"a\": { \"href\": \"./File:Foobar.jpg\" }, \"sa\": {} },\n\t\t\t\t\t\t\t\t\"mwBA\": { \"a\": { \"resource\": \"./File:Foobar.jpg\", \"height\": \"28\", \"width\": \"240\" },\"sa\": { \"resource\": \"File:Foobar.jpg\" } },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {\n\t\t\t\t\t\t\t\t\"mwAg\": { \"caption\": \"Testing 123\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p><span class=\"mw-default-size\" typeof=\"mw:File\" id=\"mwAg\"><a href=\"./File:Foobar.jpg\" id=\"mwAw\"><img resource=\"./File:Foobar.jpg\" src=\"//upload.wikimedia.org/wikipedia/commons/3/3a/Foobar.jpg\" data-file-width=\"240\" data-file-height=\"28\" data-file-type=\"bitmap\" height=\"28\" width=\"240\" id=\"mwBA\"/></a></span></p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(validWikitextResponse('[[File:Foobar.jpg]]'))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should apply extra normalizations', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<h2></h2>',\n\t\t\t\toriginal: { title: 'Doesnotexist' },\n\t\t\t})\n\t\t\t.expect(validWikitextResponse(\n\t\t\t\t''\n\t\t\t))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should return a request too large error', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Set limits in config\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/html/to/wikitext/')\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Large_Page',\n\t\t\t\t},\n\t\t\t\thtml: \"a\".repeat(parsoidOptions.limits.html2wt.maxHTMLSize + 1),\n\t\t\t})\n\t\t\t.expect(413)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should fail to downgrade the original version for an unknown transition', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/wikitext/')\n\t\t\t.send({\n\t\t\t\thtml: '<!DOCTYPE html>\\n<html><head><meta charset=\"utf-8\"/><meta property=\"mw:htmlVersion\" content=\"2.8.0\"/></head><body id=\"mwAA\" lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body-content parsoid-body mediawiki mw-parser-output\" dir=\"ltr\">123</body></html>',\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': { body: { \"ids\": {} } },\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/2222.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<!DOCTYPE html>\\n<html><head><meta charset=\"utf-8\"/><meta property=\"mw:html:version\" content=\"2222.0.0\"/></head><body id=\"mwAA\" lang=\"en\" class=\"mw-content-ltr sitedir-ltr ltr mw-body-content parsoid-body mediawiki mw-parser-output\" dir=\"ltr\">123</body></html>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t}); // end html2wt\n\n\tdescribe('pb2pb', function() {\n\n\t\tit('should require an original or previous version', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/Reuse_Page/100')\n\t\t\t.send({})\n\t\t\t.expect(400)\n\t\t\t.end(done);\n\t\t});\n\n\t\tvar previousRevHTML = {\n\t\t\trevid: 99,\n\t\t\thtml: {\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t},\n\t\t\t\tbody: '<p about=\"#mwt1\" typeof=\"mw:Transclusion\" data-mw=\\'{\"parts\":[{\"template\":{\"target\":{\"wt\":\"colours of the rainbow\",\"href\":\"./Template:Colours_of_the_rainbow\"},\"params\":{},\"i\":0}}]}\\' id=\"mwAg\">pink</p>',\n\t\t\t},\n\t\t\t\"data-parsoid\": {\n\t\t\t\theaders: {\n\t\t\t\t\t'content-type': 'application/json;profile=\"https://www.mediawiki.org/wiki/Specs/data-parsoid/' + defaultContentVersion + '\"',\n\t\t\t\t},\n\t\t\t\tbody: {\n\t\t\t\t\t'counter': 2,\n\t\t\t\t\t'ids': {\n\t\t\t\t\t\t'mwAg': { 'pi': [[]], 'src': '{{colours of the rainbow}}' },  // artificially added src\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\n\t\tit('should error when revision not found (transform, pb2pb)', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/Doesnotexist')\n\t\t\t.send({\n\t\t\t\tprevious: previousRevHTML,\n\t\t\t})\n\t\t\t.expect(404)\n\t\t\t.end(done);\n\t\t});\n\n\t\t// FIXME: Expansion reuse wasn't ported, see T98995\n\t\tit.skip('should accept the previous revision to reuse expansions', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/Reuse_Page/100')\n\t\t\t.send({\n\t\t\t\tprevious: previousRevHTML,\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.textContent.should.match(/pink/);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tvar origHTML = Util.clone(previousRevHTML);\n\t\torigHTML.revid = 100;\n\n\t\t// FIXME: Expansion reuse wasn't ported, see T98995\n\t\tit.skip('should accept the original and reuse certain expansions', function(done) {\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/Reuse_Page/100')\n\t\t\t.send({\n\t\t\t\tupdates: {\n\t\t\t\t\ttransclusions: true,\n\t\t\t\t},\n\t\t\t\toriginal: origHTML,\n\t\t\t})\n\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\tdoc.body.firstChild.textContent.should.match(/purple/);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should refuse an unknown conversion (2.x -> 999.x)', function(done) {\n\t\t\tpreviousRevHTML.html.headers['content-type'].should.equal('text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/2.8.0\"');\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/Reuse_Page/100')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/999.0.0\"')\n\t\t\t.send({\n\t\t\t\tprevious: previousRevHTML,\n\t\t\t})\n\t\t\t.expect(415)\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should downgrade 999.x content to 2.x', function(done) {\n\t\t\tvar contentVersion = '2.8.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/')\n\t\t\t.set('Accept', 'application/json; profile=\"https://www.mediawiki.org/wiki/Specs/pagebundle/' + contentVersion + '\"')\n\t\t\t.send({\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"pi\": [[{ \"k\": \"1\" }]] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t'data-mw': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: { \"mwAQ\": { \"parts\": [{ \"template\": { \"target\": { \"wt\": \"1x\", \"href\": \"./Template:1x\" }, \"params\": { \"1\": { \"wt\": \"hi\" } }, \"i\": 0 } }] } },\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/999.0.0\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<!DOCTYPE html>\\n<html><head><meta charset=\"utf-8\"/><meta property=\"mw:htmlVersion\" content=\"999.0.0\"/></head><body><p about=\"#mwt1\" typeof=\"mw:Transclusion\" id=\"mwAQ\">ho</p></body></html>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(status200)\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\t// In < 999.x, data-mw is still inline.\n\t\t\t\thtml.should.match(/\\s+data-mw\\s*=\\s*['\"]/);\n\t\t\t\thtml.should.not.match(/\\s+data-parsoid\\s*=\\s*['\"]/);\n\t\t\t\tvar doc = domino.createDocument(html);\n\t\t\t\tvar meta = doc.querySelector('meta[property=\"mw:html:version\"], meta[property=\"mw:htmlVersion\"]');\n\t\t\t\tmeta.getAttribute('content').should.equal(contentVersion);\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tit('should accept the original and update the redlinks', function(done) {\n\t\t\tif (skipForNow) {\n\t\t\t\treturn this.skip();\n\t\t\t}  // Create pages to fix redlinks count\n\t\t\t// NOTE: Keep this on an older version to show that it's preserved\n\t\t\t// through the transformation.\n\t\t\tvar contentVersion = '2.0.0';\n\t\t\tclient.req\n\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/')\n\t\t\t.send({\n\t\t\t\tupdates: {\n\t\t\t\t\tredlinks: true,\n\t\t\t\t},\n\t\t\t\toriginal: {\n\t\t\t\t\ttitle: 'Doesnotexist',\n\t\t\t\t\t'data-parsoid': {\n\t\t\t\t\t\tbody: {\n\t\t\t\t\t\t\tids: {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\thtml: {\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + contentVersion + '\"',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody: '<p><a rel=\"mw:WikiLink\" href=\"./Special:Version\" title=\"Special:Version\">Special:Version</a> <a rel=\"mw:WikiLink\" href=\"./Doesnotexist\" title=\"Doesnotexist\">Doesnotexist</a> <a rel=\"mw:WikiLink\" href=\"./Redirected\" title=\"Redirected\">Redirected</a></p>',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t})\n\t\t\t.expect(acceptablePageBundleResponse(contentVersion, function(html) {\n\t\t\t\tvar doc = domino.createDocument(html);\n\t\t\t\tdoc.body.querySelectorAll('a').length.should.equal(3);\n\t\t\t\tvar redLinks = doc.body.querySelectorAll('.new');\n\t\t\t\tredLinks.length.should.equal(1);\n\t\t\t\tredLinks[0].getAttribute('title').should.equal('Doesnotexist');\n\t\t\t\tvar redirects = doc.body.querySelectorAll('.mw-redirect');\n\t\t\t\tredirects.length.should.equal(1);\n\t\t\t\tredirects[0].getAttribute('title').should.equal('Redirected');\n\t\t\t}))\n\t\t\t.end(done);\n\t\t});\n\n\t\tdescribe('Variant conversion', function() {\n\n\t\t\tit('should return unconverted nl page if sr variant conversion is requested on nl page', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/MediaWiki:ok%2Fnl')\n\t\t\t\t.send({\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\tvariant: { target: 'sr-el' },\n\t\t\t\t\t},\n\t\t\t\t\toriginal: {\n\t\t\t\t\t\trevid,\n\t\t\t\t\t\thtml: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t'content-type': 'text/html; profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: '<p>абвг abcd</p>',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should accept the original and do variant conversion (given oldid)', function(done) {\n\t\t\t\tif (skipForNow) {\n\t\t\t\t\treturn this.skip();\n\t\t\t\t} // Enabled language conversion\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/')\n\t\t\t\t.send({\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\tvariant: { target: 'sr-el' },\n\t\t\t\t\t},\n\t\t\t\t\toriginal: {\n\t\t\t\t\t\trevid: 104, /* sets the pagelanguage */\n\t\t\t\t\t\thtml: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: '<p>абвг abcd x</p>',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// We don't actually require the result to have data-parsoid\n\t\t\t\t\t// if the input didn't have data-parsoid; hack the result\n\t\t\t\t\t// in order to make validPageBundleResponse() pass.\n\t\t\t\t\tres.body['data-parsoid'].body = {};\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd x');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should accept the original and do variant conversion (given pagelanguage)', function(done) {\n\t\t\t\tif (skipForNow) {\n\t\t\t\t\treturn this.skip();\n\t\t\t\t} // Enabled language conversion\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/')\n\t\t\t\t.set('Content-Language', 'sr')\n\t\t\t\t.set('Accept-Language', 'sr-el')\n\t\t\t\t.send({\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\tvariant: { /* target implicit from accept-language */ },\n\t\t\t\t\t},\n\t\t\t\t\toriginal: {\n\t\t\t\t\t\thtml: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: '<p>абвг abcd</p>',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// We don't actually require the result to have data-parsoid\n\t\t\t\t\t// if the input didn't have data-parsoid; hack the result\n\t\t\t\t\t// in order to make validPageBundleResponse() pass.\n\t\t\t\t\tres.body['data-parsoid'].body = {};\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\t\tdoc.body.textContent.should.equal('abvg abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders['content-language'].should.equal('sr-el');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t\tit('should not perform variant conversion w/ invalid variant (given pagelanguage)', function(done) {\n\t\t\t\tclient.req\n\t\t\t\t.post(mockDomain + '/v3/transform/pagebundle/to/pagebundle/')\n\t\t\t\t.set('Content-Language', 'sr')\n\t\t\t\t.set('Accept-Language', 'sr-BOGUS')\n\t\t\t\t.send({\n\t\t\t\t\tupdates: {\n\t\t\t\t\t\tvariant: { /* target implicit from accept-language */ },\n\t\t\t\t\t},\n\t\t\t\t\toriginal: {\n\t\t\t\t\t\thtml: {\n\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t'content-type': 'text/html;profile=\"https://www.mediawiki.org/wiki/Specs/HTML/' + defaultContentVersion + '\"',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbody: '<p>абвг abcd</p>',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.expect(status200)\n\t\t\t\t.expect((res) => {\n\t\t\t\t\t// We don't actually require the result to have data-parsoid\n\t\t\t\t\t// if the input didn't have data-parsoid; hack the result\n\t\t\t\t\t// in order to make validPageBundleResponse() pass.\n\t\t\t\t\tres.body['data-parsoid'].body = {};\n\t\t\t\t})\n\t\t\t\t.expect(validPageBundleResponse(function(doc) {\n\t\t\t\t\tdoc.body.textContent.should.equal('абвг abcd');\n\t\t\t\t}))\n\t\t\t\t.expect((res) => {\n\t\t\t\t\tconst headers = res.body.html.headers;\n\t\t\t\t\theaders.should.have.property('content-language');\n\t\t\t\t\theaders['content-language'].should.equal('sr');\n\t\t\t\t\theaders.should.have.property('vary');\n\t\t\t\t\theaders.vary.should.match(/\\bAccept-Language\\b/i);\n\t\t\t\t})\n\t\t\t\t.end(done);\n\t\t\t});\n\n\t\t});\n\n\t});  // end pb2pb\n\n\tdescribe( 'roundtrip-test', function () {\n\t\t// This mirrors the behavior of the runTests function in roundtrip-test.js\n\n\t\tconst transformOptions = {\n\t\t\t// For compatibility with Parsoid/PHP service, per roundtrip-test.js\n\t\t\toffsetType: 'ucs2'\n\t\t};\n\n\t\tit( 'Roundtrip should convert wikitext to HTML and back, with selser working', async () => {\n\t\t\t// get the wikitext of the page\n\t\t\tconst { text: oldWt } = await client.req\n\t\t\t\t.get( mockDomain + `/v3/page/wikitext/${ pageEncoded }` );\n\n\t\t\t// First, fetch the HTML for the requested page's wikitext\n\t\t\tconst wt2html = await client.req\n\t\t\t\t.post( mockDomain + `/v3/transform/wikitext/to/pagebundle/${ pageEncoded }` )\n\t\t\t\t.send( Object.assign( {\n\t\t\t\t\twikitext: oldWt,\n\t\t\t\t}, transformOptions ) );\n\n\t\t\tassert.equal( 200, wt2html.statusCode, wt2html.text );\n\n\t\t\t// Now, request the wikitext for the obtained HTML, without providing the original HTML\n\t\t\tconst pb2wt1 = await client.req\n\t\t\t\t.post( mockDomain + `/v3/transform/pagebundle/to/wikitext/${ pageEncoded }` )\n\t\t\t\t.send( Object.assign( {\n\t\t\t\t\thtml: wt2html.body.html,\n\t\t\t\t}, transformOptions ) );\n\n\t\t\tassert.equal( 200, pb2wt1.statusCode, pb2wt1.text );\n\n\t\t\t// This might fail, since we are not applying selser.\n\t\t\t// We are trusting that the conversion back to wikitext will be clean.\n\t\t\tassert.equal( pb2wt1.text, oldWt );\n\n\t\t\t// Now, request the wikitext for the obtained HTML, with Selser\n\t\t\tvar newDocument = DOMUtils.parseHTML( wt2html.body.html.body );\n\t\t\tvar newNode = newDocument.createComment('rtSelserEditTestComment');\n\t\t\tnewDocument.body.appendChild(newNode);\n\n\t\t\tconst pb2wt2 = await client.req\n\t\t\t\t.post( mockDomain + `/v3/transform/pagebundle/to/wikitext/${ pageEncoded }` )\n\t\t\t\t.send( {\n\t\t\t\t\thtml: newDocument.outerHTML,\n\t\t\t\t\toldid: revid,\n\t\t\t\t\toriginal: {\n\t\t\t\t\t\t'data-parsoid': wt2html.body['data-parsoid'],\n\t\t\t\t\t\t'data-mw': wt2html.body['data-mw'],\n\t\t\t\t\t\twikitext: { body: oldWt },\n\t\t\t\t\t\thtml: wt2html.body.html,\n\t\t\t\t\t},\n\t\t\t\t\t...transformOptions\n\t\t\t\t} );\n\n\t\t\tassert.equal( 200, pb2wt2.statusCode, pb2wt2.text );\n\n\t\t\t// Check that the comment was included in the wikitext\n\t\t\tassert.include( pb2wt2.text, 'rtSelserEditTestComment' );\n\n\t\t\t// Remove the selser trigger comment\n\t\t\tconst actual2 = pb2wt2.text.replace(/<!--rtSelserEditTestComment-->\\n*$/, '');\n\n\t\t\tassert.equal( actual2, oldWt );\n\t\t} );\n\n\t} );\n\n\tdescribe( 'ETags', function () {\n\n\t\tit( '/transform/html must accept the ETag in the If-Match header, if one is returned by /page/html (T238849)', async () => {\n\t\t\tconst { statusCode: status1, headers: headers1, text: text1 } = await client.req\n\t\t\t\t.get( mockDomain + `/v3/page/html/${ page }/${ revid }` )\n\t\t\t\t.query( { stash: 'yes' } );\n\n\t\t\tassert.deepEqual( status1, 200, text1 );\n\n\t\t\tif ( !headers1.etag ) {\n\t\t\t\t// This is a structure test that is asserting one of two acceptable situations:\n\t\t\t\t// Either, the page endpoint doesn't return an ETag. This implies that the\n\t\t\t\t// HTML wasn't stored anywhere, so it can't be re-used.\n\t\t\t\t// Or the transform endpoint accepts the ETag returned by the page endpoint.\n\t\t\t\t// This ensures that clients can loop through any ETags they receive.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// The request above should have stashed a rendering associated with the ETag it\n\t\t\t// returned. Pass the ETag in the If-Match header.\n\t\t\tconst { statusCode: status2, text: text2 } = await client.req\n\t\t\t\t.post( mockDomain + `/v3/transform/html/to/wikitext/${ page }/${ revid }` )\n\t\t\t\t.set( 'If-Match', headers1.etag )\n\t\t\t\t.send( {\n\t\t\t\t\thtml: text1\n\t\t\t\t} );\n\n\t\t\t// Just check we got a 200. The returned wikitext may or may not be \"dirty\".\n\t\t\tassert.deepEqual( status2, 200, text2 );\n\t\t} );\n\n\t} );\n});\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/api-testing/RoundTrip.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/phpunit/Parsoid/ParserTests/data/testsWithKnownFailures-knownFailures.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/test.config.yaml","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/testreduce/config.example.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/testreduce/rtTestWrapper.js","messages":[{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":13,"column":9,"nodeType":"CallExpression","endLine":13,"endColumn":48}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nconst fs = require('fs');\nconst path = require('path');\n\n/* This is a wrapper around the roundtrip testing script */\nconst rtTest = require('../../bin/roundtrip-test.js');\nconst yaml = require('js-yaml');\n\n// Read ids from a file and return the first line of the file\nfunction getTestRunId(opts) {\n\tconst testRunIdFile = opts.testRunIdFile || path.resolve(__dirname, 'parsoid.rt-test.ids');\n\treturn fs.readFileSync(testRunIdFile, 'utf-8').split('\\n')[0];\n}\n\nfunction runRoundTripTest(config, test) {\n\treturn rtTest.runTests(test.title, {\n\t\tprefix: test.prefix,\n\t\tparsoidURLOpts: config.parsoidPHP,\n\t}, rtTest.xmlFormat).then(function(result) {\n\t\treturn result.output;\n\t});\n}\n\nif (typeof module === 'object') {\n\tmodule.exports.runRoundTripTest = runRoundTripTest;\n\tmodule.exports.getTestRunId = getTestRunId;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/RCUtils.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/ScriptUtils.js","messages":[{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":57,"column":4,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":57,"endColumn":19}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * This file contains general utilities for scripts in\n * the bin/, tools/, tests/ directories. This file should\n * not contain any helpers that are needed by code in the\n * lib/ directory.\n *\n * @module\n */\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\nvar Promise = require('../lib/utils/promise.js');\nvar request = Promise.promisify(require('request'), true);\nvar Util = require('../lib/utils/Util.js').Util;\n\nvar ScriptUtils = {\n\t/**\n\t * Split a tracing / debugging flag string into individual flags\n\t * and return them.\n\t *\n\t * @param {Object} origFlag The original flag string.\n\t * @return {Array}\n\t */\n\tsplitFlags: function(origFlag) {\n\t\tvar objFlags = origFlag.split(\",\");\n\t\tif (objFlags.includes(\"selser\") && !objFlags.includes(\"wts\")) {\n\t\t\tobjFlags.push(\"wts\");\n\t\t}\n\t\treturn objFlags;\n\t},\n\n\t/**\n\t * Set debugging flags on an object, based on an options object.\n\t *\n\t * @param {Object} parsoidOptions Object to be assigned to the ParsoidConfig.\n\t * @param {Object} cliOpts The options object to use for setting the debug flags.\n\t * @return {Object} The modified object.\n\t */\n\tsetDebuggingFlags: function(parsoidOptions, cliOpts) {\n\t\t// Handle the --help options\n\t\tvar exit = false;\n\t\tif (cliOpts.trace === 'help') {\n\t\t\tconsole.error(ScriptUtils.traceUsageHelp());\n\t\t\texit = true;\n\t\t}\n\t\tif (cliOpts.dump === 'help') {\n\t\t\tconsole.error(ScriptUtils.dumpUsageHelp());\n\t\t\texit = true;\n\t\t}\n\t\tif (cliOpts.debug === 'help') {\n\t\t\tconsole.error(ScriptUtils.debugUsageHelp());\n\t\t\texit = true;\n\t\t}\n\t\tif (exit) {\n\t\t\tprocess.exit(1);\n\t\t}\n\n\t\t// Ok, no help requested: process the options.\n\t\tif (cliOpts.debug !== undefined) {\n\t\t\t// Continue to support generic debugging.\n\t\t\tif (cliOpts.debug === true) {\n\t\t\t\tconsole.warn(\"Warning: Generic debugging, not handler-specific.\");\n\t\t\t\tparsoidOptions.debug = ScriptUtils.booleanOption(cliOpts.debug);\n\t\t\t} else {\n\t\t\t\t// Setting --debug automatically enables --trace\n\t\t\t\tparsoidOptions.debugFlags = ScriptUtils.splitFlags(cliOpts.debug);\n\t\t\t\tparsoidOptions.traceFlags = parsoidOptions.debugFlags;\n\t\t\t}\n\t\t}\n\n\t\tif (cliOpts.trace !== undefined) {\n\t\t\tif (cliOpts.trace === true) {\n\t\t\t\tconsole.warn(\"Warning: Generic tracing is no longer supported. Ignoring --trace flag. Please provide handler-specific tracing flags, e.g. '--trace pre,html5', to turn it on.\");\n\t\t\t} else {\n\t\t\t\t// Add any new trace flags to the list of existing trace flags (if\n\t\t\t\t// any were inherited from debug); otherwise, create a new list.\n\t\t\t\tparsoidOptions.traceFlags = (parsoidOptions.traceFlags || []).concat(ScriptUtils.splitFlags(cliOpts.trace));\n\t\t\t}\n\t\t}\n\n\t\tif (cliOpts.dump !== undefined) {\n\t\t\tif (cliOpts.dump === true) {\n\t\t\t\tconsole.warn(\"Warning: Generic dumping not enabled. Please set a flag.\");\n\t\t\t} else {\n\t\t\t\tparsoidOptions.dumpFlags = ScriptUtils.splitFlags(cliOpts.dump);\n\t\t\t}\n\t\t}\n\n\t\treturn parsoidOptions;\n\t},\n\n\t/**\n\t * Returns a help message for the tracing flags.\n\t */\n\ttraceUsageHelp: function() {\n\t\treturn [\n\t\t\t\"Tracing\",\n\t\t\t\"-------\",\n\t\t\t\"- With one or more comma-separated flags, traces those specific phases\",\n\t\t\t\"- Supported flags:\",\n\t\t\t\"  * pre-peg   : shows input to tokenizer\",\n\t\t\t\"  * peg       : shows tokens emitted by tokenizer\",\n\t\t\t\"  * sync:1    : shows tokens flowing through the post-tokenizer Sync Token Transform Manager\",\n\t\t\t\"  * async:2   : shows tokens flowing through the Async Token Transform Manager\",\n\t\t\t\"  * sync:3    : shows tokens flowing through the post-expansion Sync Token Transform Manager\",\n\t\t\t\"  * tsp       : shows tokens flowing through the TokenStreamPatcher (useful to see in-order token stream)\",\n\t\t\t\"  * list      : shows actions of the list handler\",\n\t\t\t\"  * sanitizer : shows actions of the sanitizer\",\n\t\t\t\"  * pre       : shows actions of the pre handler\",\n\t\t\t\"  * p-wrap    : shows actions of the paragraph wrapper\",\n\t\t\t\"  * html      : shows tokens that are sent to the HTML tree builder\",\n\t\t\t\"  * dsr       : shows dsr computation on the DOM\",\n\t\t\t\"  * tplwrap   : traces template wrapping code (currently only range overlap/nest/merge code)\",\n\t\t\t\"  * wts       : trace actions of the regular wikitext serializer\",\n\t\t\t\"  * selser    : trace actions of the selective serializer\",\n\t\t\t\"  * domdiff   : trace actions of the DOM diffing code\",\n\t\t\t\"  * wt-escape : debug wikitext-escaping\",\n\t\t\t\"  * batcher   : trace API batch aggregation and dispatch\",\n\t\t\t\"  * apirequest: trace all API requests\",\n\t\t\t\"  * time      : trace times for various phases (right now, limited to DOMPP passes)\",\n\t\t\t\"  * time/dompp: trace times for DOM Post processing passes\",\n\t\t\t\"\",\n\t\t\t\"--debug enables tracing of all the above phases except Token Transform Managers\",\n\t\t\t\"\",\n\t\t\t\"Examples:\",\n\t\t\t\"$ node parse --trace pre,p-wrap,html < foo\",\n\t\t\t\"$ node parse --trace sync:3,dsr < foo\",\n\t\t].join('\\n');\n\t},\n\n\t/**\n\t * Returns a help message for the dump flags.\n\t */\n\tdumpUsageHelp: function() {\n\t\treturn [\n\t\t\t\"Dumping state\",\n\t\t\t\"-------------\",\n\t\t\t\"- Dumps state at different points of execution\",\n\t\t\t\"- DOM dumps are always doc.outerHTML\",\n\t\t\t\"- Supported flags:\",\n\t\t\t\"\",\n\t\t\t\"  * tplsrc            : dumps preprocessed template source that will be tokenized (via ?action=expandtemplates)\",\n\t\t\t\"  * extoutput         : dumps HTML output form extensions (via ?action=parse)\",\n\t\t\t\"\",\n\t\t\t\"  --- Dump flags for wt2html DOM passes ---\",\n\t\t\t\"  * dom:pre-XXX       : dumps DOM before pass XXX runs\",\n\t\t\t\"  * dom:post-XXX      : dumps DOM after pass XXX runs\",\n\t\t\t\"\",\n\t\t\t\"    Available passes (in the order they run):\",\n\t\t\t\"\",\n\t\t\t\"      fostered, tb-fixups, normalize, pwrap, \",\n\t\t\t\"      migrate-metas, pres, migrate-nls, dsr, tplwrap, \",\n\t\t\t\"      dom-unpack, tag:EXT (replace EXT with extension: cite, poem, etc)\",\n\t\t\t\"      sections, heading-ids, lang-converter, linter, \",\n\t\t\t\"      strip-metas, linkclasses, redlinks, downgrade\",\n\t\t\t\"\",\n\t\t\t\"  --- Dump flags for html2wt ---\",\n\t\t\t\"  * dom:post-dom-diff : in selective serialization, dumps DOM after running dom diff\",\n\t\t\t\"  * dom:post-normal   : in serialization, dumps DOM after normalization\",\n\t\t\t\"  * wt2html:limits    : dumps used resources (along with configured limits)\\n\",\n\t\t\t\"--debug dumps state at these different stages\\n\",\n\t\t\t\"Examples:\",\n\t\t\t\"$ node parse --dump dom:pre-dsr,dom:pre-tplwrap < foo\",\n\t\t\t\"$ node parse --trace html --dump dom:pre-tplwrap < foo\",\n\t\t\t\"\\n\",\n\t\t].join('\\n');\n\t},\n\n\t/**\n\t * Returns a help message for the debug flags.\n\t */\n\tdebugUsageHelp: function() {\n\t\treturn [\n\t\t\t\"Debugging\",\n\t\t\t\"---------\",\n\t\t\t\"- With one or more comma-separated flags, provides more verbose tracing than the equivalent trace flag\",\n\t\t\t\"- Supported flags:\",\n\t\t\t\"  * pre       : shows actions of the pre handler\",\n\t\t\t\"  * wts       : trace actions of the regular wikitext serializer\",\n\t\t\t\"  * selser    : trace actions of the selective serializer\",\n\t\t].join('\\n');\n\t},\n\n\t/**\n\t * Sets templating and processing flags on an object,\n\t * based on an options object.\n\t *\n\t * @param {Object} parsoidOptions Object to be assigned to the ParsoidConfig.\n\t * @param {Object} cliOpts The options object to use for setting the debug flags.\n\t * @return {Object} The modified object.\n\t */\n\tsetTemplatingAndProcessingFlags: function(parsoidOptions, cliOpts) {\n\t\t[\n\t\t\t'fetchConfig',\n\t\t\t'fetchTemplates',\n\t\t\t'fetchImageInfo',\n\t\t\t'expandExtensions',\n\t\t\t'addHTMLTemplateParameters',\n\t\t].forEach(function(c) {\n\t\t\tif (cliOpts[c] !== undefined) {\n\t\t\t\tparsoidOptions[c] = ScriptUtils.booleanOption(cliOpts[c]);\n\t\t\t}\n\t\t});\n\t\tif (cliOpts.usePHPPreProcessor !== undefined) {\n\t\t\tparsoidOptions.usePHPPreProcessor = parsoidOptions.fetchTemplates &&\n\t\t\t\tScriptUtils.booleanOption(cliOpts.usePHPPreProcessor);\n\t\t}\n\t\tif (cliOpts.maxDepth !== undefined) {\n\t\t\tparsoidOptions.maxDepth = typeof (cliOpts.maxdepth) === 'number' ?\n\t\t\t\tcliOpts.maxdepth : parsoidOptions.maxDepth;\n\t\t}\n\t\tif (cliOpts.apiURL) {\n\t\t\tif (!Array.isArray(parsoidOptions.mwApis)) {\n\t\t\t\tparsoidOptions.mwApis = [];\n\t\t\t}\n\t\t\tparsoidOptions.mwApis.push({ prefix: 'customwiki', uri: cliOpts.apiURL });\n\t\t}\n\t\tif (cliOpts.addHTMLTemplateParameters !== undefined) {\n\t\t\tparsoidOptions.addHTMLTemplateParameters =\n\t\t\t\tScriptUtils.booleanOption(cliOpts.addHTMLTemplateParameters);\n\t\t}\n\t\tif (cliOpts.lint) {\n\t\t\tparsoidOptions.linting = true;\n\t\t\tif (!parsoidOptions.linter) {\n\t\t\t\tparsoidOptions.linter = {};\n\t\t\t}\n\t\t\tparsoidOptions.linter.sendAPI = false;\n\t\t}\n\t\tif (cliOpts.useBatchAPI !== undefined) {\n\t\t\tparsoidOptions.useBatchAPI = cliOpts.useBatchAPI;\n\t\t}\n\n\t\treturn parsoidOptions;\n\t},\n\n\t/**\n\t * Parse a boolean option returned by the yargs package.\n\t * The strings 'false' and 'no' are also treated as false values.\n\t * This allows `--debug=no` and `--debug=false` to mean the same as\n\t * `--no-debug`.\n\t *\n\t * @param {boolean|string} val\n\t *   a boolean, or a string naming a boolean value.\n\t * @return {boolean}\n\t */\n\tbooleanOption: function(val) {\n\t\tif (!val) {\n\t\t\treturn false;\n\t\t}\n\t\tif ((typeof val) === 'string' && /^(no|false)$/i.test(val)) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\t/**\n\t * Set the color flags, based on an options object.\n\t *\n\t * @param {Object} options\n\t *   The options object to use for setting the mode of the 'color' package.\n\t * @param {string|boolean} options.color\n\t *   Whether to use color.  Passing 'auto' will enable color only if\n\t *   stdout is a TTY device.\n\t */\n\tsetColorFlags: function(options) {\n\t\tvar colors = require('colors');\n\t\tif (options.color === 'auto') {\n\t\t\tif (!process.stdout.isTTY) {\n\t\t\t\tcolors.mode = 'none';\n\t\t\t}\n\t\t} else if (!ScriptUtils.booleanOption(options.color)) {\n\t\t\tcolors.mode = 'none';\n\t\t}\n\t},\n\n\t/**\n\t * Add standard options to an yargs options hash.\n\t * This handles options parsed by `setDebuggingFlags`,\n\t * `setTemplatingAndProcessingFlags`, `setColorFlags`,\n\t * and standard --help options.\n\t *\n\t * The `defaults` option is optional, and lets you override\n\t * the defaults for the standard options.\n\t *\n\t * @param opts\n\t * @param defaults\n\t */\n\taddStandardOptions: function(opts, defaults) {\n\t\tvar standardOpts = {\n\t\t\t// standard CLI options\n\t\t\t'help': {\n\t\t\t\tdescription: 'Show this help message',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': false,\n\t\t\t\talias: 'h',\n\t\t\t},\n\t\t\t// handled by `setDebuggingFlags`\n\t\t\t'debug': {\n\t\t\t\tdescription: 'Provide optional flags. Use --debug=help for supported options',\n\t\t\t},\n\t\t\t'trace': {\n\t\t\t\tdescription: 'Use --trace=help for supported options',\n\t\t\t},\n\t\t\t'dump': {\n\t\t\t\tdescription: 'Dump state. Use --dump=help for supported options',\n\t\t\t},\n\t\t\t// handled by `setTemplatingAndProcessingFlags`\n\t\t\t'fetchConfig': {\n\t\t\t\tdescription: 'Whether to fetch the wiki config from the server or use our local copy',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': true,\n\t\t\t},\n\t\t\t'fetchTemplates': {\n\t\t\t\tdescription: 'Whether to fetch included templates recursively',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': true,\n\t\t\t},\n\t\t\t'fetchImageInfo': {\n\t\t\t\tdescription: 'Whether to fetch image info via the API',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': true,\n\t\t\t},\n\t\t\t'expandExtensions': {\n\t\t\t\tdescription: 'Whether we should request extension tag expansions from a wiki',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': true,\n\t\t\t},\n\t\t\t'usePHPPreProcessor': {\n\t\t\t\tdescription: 'Whether to use the PHP preprocessor to expand templates',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': true,\n\t\t\t},\n\t\t\t'addHTMLTemplateParameters': {\n\t\t\t\tdescription: 'Parse template parameters to HTML and add them to template data',\n\t\t\t\t'boolean': true,\n\t\t\t\t'default': false,\n\t\t\t},\n\t\t\t'maxdepth': {\n\t\t\t\tdescription: 'Maximum expansion depth',\n\t\t\t\t'default': 40,\n\t\t\t},\n\t\t\t'apiURL': {\n\t\t\t\tdescription: 'http path to remote API, e.g. http://en.wikipedia.org/w/api.php',\n\t\t\t\t'default': null,\n\t\t\t},\n\t\t\t'useBatchAPI': {\n\t\t\t\tdescription: 'Turn on/off the API batching system',\n\t\t\t\t'boolean': false,\n\t\t\t},\n\t\t\t// handled by `setColorFlags`\n\t\t\t'color': {\n\t\t\t\tdescription: 'Enable color output Ex: --no-color',\n\t\t\t\t'default': 'auto',\n\t\t\t},\n\t\t};\n\t\t// allow overriding defaults\n\t\tObject.keys(defaults || {}).forEach(function(name) {\n\t\t\tif (standardOpts[name]) {\n\t\t\t\tstandardOpts[name].default = defaults[name];\n\t\t\t}\n\t\t});\n\t\treturn Util.extendProps(opts, standardOpts);\n\t},\n};\n\n/**\n * Perform a HTTP request using the 'request' package, and retry on failures.\n * Only use on idempotent HTTP end points.\n *\n * @param {number} retries The number of retries to attempt.\n * @param {Object} requestOptions Request options.\n * @param {number} [delay] Exponential back-off.\n * @return {Promise}\n */\nScriptUtils.retryingHTTPRequest = function(retries, requestOptions, delay) {\n\tdelay = delay || 100;  // start with 100ms\n\treturn request(requestOptions)\n\t.catch(function(error) {\n\t\tif (retries--) {\n\t\t\tconsole.error('HTTP ' + requestOptions.method + ' to \\n' +\n\t\t\t\t\t(requestOptions.uri || requestOptions.url) + ' failed: ' + error +\n\t\t\t\t\t'\\nRetrying in ' + (delay / 1000) + ' seconds.');\n\t\t\treturn Promise.delay(delay).then(function() {\n\t\t\t\treturn ScriptUtils.retryingHTTPRequest(retries, requestOptions, delay * 2);\n\t\t\t});\n\t\t} else {\n\t\t\treturn Promise.reject(error);\n\t\t}\n\t})\n\t.spread(function(res, body) {\n\t\tif (res.statusCode !== 200) {\n\t\t\tthrow new Error('Got status code: ' + res.statusCode +\n\t\t\t\t'; body: ' + JSON.stringify(body || '').slice(0, 500));\n\t\t}\n\t\treturn Array.from(arguments);\n\t});\n};\n\nif (typeof module === \"object\") {\n\tmodule.exports.ScriptUtils = ScriptUtils;\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/build-langconv-fst.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/language/FST.js' in '/src/repo/tools'","line":84,"column":21,"nodeType":"Literal","messageId":"notFound","endLine":84,"endColumn":45},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found openSync from package \"fs\" with non literal argument at index 0","line":109,"column":13,"nodeType":"CallExpression","endLine":109,"endColumn":37},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found openSync from package \"fs\" with non literal argument at index 0","line":302,"column":14,"nodeType":"CallExpression","endLine":302,"endColumn":39}],"suppressedMessages":[{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":204,"column":10,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":204,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '>>>='.","line":205,"column":8,"nodeType":"AssignmentExpression","messageId":"unexpected","endLine":205,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '>>>='.","line":205,"column":25,"nodeType":"AssignmentExpression","messageId":"unexpected","endLine":205,"endColumn":35,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '|'.","line":206,"column":11,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":206,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":206,"column":18,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":206,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":280,"column":13,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":280,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":281,"column":10,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":281,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '<<'.","line":284,"column":11,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":284,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":284,"column":24,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":284,"endColumn":31,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '&'.","line":293,"column":7,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":293,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '>>>'.","line":294,"column":13,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":294,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-bitwise","severity":2,"message":"Unexpected use of '>>>'.","line":296,"column":12,"nodeType":"BinaryExpression","messageId":"unexpected","endLine":296,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\n/**\n * Compile an .att-format finite state transducer (as output by foma)\n * into a compact byte-array representation which is directly executable.\n * The input is expected to be a \"byte machine\", that is, unicode code units\n * have already been decomposed into code points corresponding to UTF-8\n * bytes.  Symbols used in the ATT file:\n *\n *  @0@      Epsilon (\"no character\").  Used in both input and output edges;\n *           as an input edge this introduced nondeterminism.\n *  <hh>    The input byte with hexadecimal value <hh>\n *             (\"00\" should never appear in the ATT file; see below.)\n *  @_IDENTITY_SYMBOL_@   Any character not named in the (implicit) alphabet\n *  [[       Bracket characters, used to delimit \"unsafe\" strings in\n *  ]]       \"bracket machines'.\n *\n * The output is a byte array.  We use a variable-length integer encoding:\n *   0xxx xxxy -> the directly-encoded value (xxx xxxx)\n *   1xxx xxxx -> (xxx xxxx) + 128 * ( 1 + <the value encoded by subsequent bytes>)\n * For signed quantities, the least significant digit is used for a sign\n * bit.  That is, to encode first:\n *   from_signed(x) = (x >= 0) ? (2*x) : (-2*(x + 1) + 1);\n * and when decoding:\n *   to_signed(x) = (x & 1) ? (((x-1)/-2)-1) : (x/2);\n * See [en:Variable-length_quantity#Zigzag_encoding] for details.\n *\n * Byte value 0x00 is used for \"epsilon\" edges.  Null characters are\n *  disallowed in wikitext, and foma would have trouble handling them\n *  natively since it is written in C with null-terminated strings.\n *  As an input character this represents a non-deterministic transition;\n *  as an output character it represents \"no output\".\n *  If you wanted (for some reason) to allow null characters in the\n *  input (which are not included in the \"anything else\" case), then\n *  encode them as 0xC0 0x80 (aka \"Modified UTF-8\").  [Similarly, if\n *  you wanted to emit a null character, you could emit 0xC0 0x80,\n *  although emitting 0x00 directly ought to work fine as well.]\n *\n * Byte values 0xF8 - 0xFF are disallowed in UTF-8.  We use them for\n * special cases, as follows:\n *  0xFF: EOF (the end of the input string).  Final states in the machine\n *   are represented with an inchar=0xFF outchar=0x00 transition to a\n *   unique \"stop state\" (aka state #0).  Non-final states have no outgoing\n *   edge for input 0xFF.\n *  0xFE: IDENTITY.  As an output character it copies the input character.\n *  0xFD: ]]\n *  0xFC: [[  These bracketing characters should only appear as output\n *   characters; they will never appear in the input.\n *\n * The byte array begins with eight \"magic bytes\" to help identify the\n * file format.\n *\n * Following this, we have an array of states.  State #0 is the unique\n * \"final state\"; state #1 is the unique start state.  Each state is:\n *   <# of bytes in each edge: variable unsigned int>\n *   <# edges: variable unsigned int>\n *   <edge #0>\n *   <edge #1>\n *   etc\n * Each edge is:\n *   <in byte: 1 byte>\n *   <out byte: 1 byte>\n *   <target state: variable signed int>\n *   <padding, if necessary to reach proper # of bytes in each edge>\n *\n * Edges are sorted by <in byte> to allow binary search. All target\n * states are relative, refer to the start position of that state in\n * the byte array, and are padded to the same size within each state.\n * If the first edge(s) have <in byte> = 0x00 then these edges\n * represent possible epsilon transitions from this state (aka, these\n * edge should be tried if subsequent execution from this state\n * fails).\n */\n\nconst fs = require('fs');\nconst path = require('path');\nconst yargs = require('yargs');\nconst { StringDecoder } = require('string_decoder');\n\nconst FST = require('../lib/language/FST.js');\n\nconst BYTE_IDENTITY = FST.constants.BYTE_IDENTITY;\nconst BYTE_RBRACKET = FST.constants.BYTE_RBRACKET;\nconst BYTE_LBRACKET = FST.constants.BYTE_LBRACKET;\nconst BYTE_FAIL     = FST.constants.BYTE_FAIL;\nconst BYTE_EOF      = FST.constants.BYTE_EOF;\nconst BYTE_EPSILON  = FST.constants.BYTE_EPSILON;\n\nclass DefaultMap extends Map {\n\tconstructor(makeDefaultValue) {\n\t\tsuper();\n\t\tthis.makeDefaultValue = makeDefaultValue;\n\t}\n\n\tgetDefault(key) {\n\t\tif (!this.has(key)) {\n\t\t\tthis.set(key, this.makeDefaultValue());\n\t\t}\n\t\treturn this.get(key);\n\t}\n}\n\n// Splits input on `\\r\\n?|\\n` without holding entire file in memory at once.\nfunction *readLines(inFile) {\n\tconst fd = fs.openSync(inFile, 'r');\n\ttry {\n\t\tconst buf = Buffer.alloc(1024);\n\t\tconst decoder = new StringDecoder('utf8');\n\t\tlet line = '';\n\t\tlet sawCR = false;\n\t\twhile (true) {\n\t\t\tconst bytesRead = fs.readSync(fd, buf, 0, buf.length);\n\t\t\tif (bytesRead === 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlet lineStart = 0;\n\t\t\tfor (let i = 0; i < bytesRead; i++) {\n\t\t\t\tif (buf[i] === 13 || buf[i] === 10) {\n\t\t\t\t\tline += decoder.write(buf.slice(lineStart, i));\n\t\t\t\t\tif (!(buf[i] === 10 && sawCR)) {\n\t\t\t\t\t\t// skip over the zero-length \"lines\" caused by \\r\\n\n\t\t\t\t\t\tyield line;\n\t\t\t\t\t}\n\t\t\t\t\tline = '';\n\t\t\t\t\tlineStart = i + 1;\n\t\t\t\t\tsawCR = (buf[i] === 13);\n\t\t\t\t} else {\n\t\t\t\t\tsawCR = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tline += decoder.write(buf.slice(lineStart, bytesRead));\n\t\t}\n\t\tline += decoder.end();\n\t\tyield line;\n\t} finally {\n\t\tfs.closeSync(fd);\n\t}\n}\n\nfunction readAttFile(inFile, handleState, handleFinal) {\n\tlet lastState = 0;\n\tlet edges = [];\n\tconst finalStates = [];\n\tfor (const line of readLines(inFile)) {\n\t\tif (line.length === 0) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst fields = line.split(/\\t/g);\n\t\tconst state = +fields[0];\n\t\tif (fields.length === 1 || state !== lastState) {\n\t\t\tif (lastState >= 0) {\n\t\t\t\thandleState(lastState, edges);\n\t\t\t\tedges = [];\n\t\t\t\tlastState = -1;\n\t\t\t}\n\t\t}\n\t\tif (fields.length === 1) {\n\t\t\tfinalStates.push(state);\n\t\t} else {\n\t\t\tconsole.assert(fields.length === 4);\n\t\t\tconst to = +fields[1];\n\t\t\tconst inChar = fields[2];\n\t\t\tconst outChar = fields[3];\n\t\t\tedges.push({ to, inChar, outChar });\n\t\t\tlastState = state;\n\t\t}\n\t}\n\tif (lastState >= 0) {\n\t\thandleState(lastState, edges);\n\t}\n\tif (handleFinal) {\n\t\thandleFinal(finalStates);\n\t}\n}\n\nclass DynamicBuffer {\n\tconstructor(chunkLength) {\n\t\tthis.chunkLength = chunkLength || 16384;\n\t\tthis.currBuff = Buffer.alloc(this.chunkLength);\n\t\tthis.buffNum = 0;\n\t\tthis.offset = 0;\n\t\tthis.buffers = [ this.currBuff ];\n\t\tthis.lastLength = 0;\n\t}\n\n\temit(b) {\n\t\tconsole.assert(b !== undefined);\n\t\tif (this.offset >= this.currBuff.length) {\n\t\t\tthis.buffNum++; this.offset = 0;\n\t\t\tthis._maybeCreateBuffers();\n\t\t\tthis.currBuff = this.buffers[this.buffNum];\n\t\t}\n\t\tthis.currBuff[this.offset++] = b;\n\t\tthis._maybeUpdateLength();\n\t}\n\n\temitUnsignedV(val, pad) {\n\t\tconst o = [];\n\t\t/* eslint-disable no-bitwise */\n\t\to.push(val & 127);\n\t\tfor (val >>>= 7; val; val >>>= 7) {\n\t\t\to.push(128 | (--val & 127));\n\t\t}\n\t\t/* eslint-enable no-bitwise */\n\t\tfor (let j = o.length - 1; j >= 0; j--) {\n\t\t\tthis.emit(o[j]);\n\t\t}\n\t\tif (pad !== undefined) {\n\t\t\tfor (let j = o.length; j < pad; j++) {\n\t\t\t\tthis.emit(0 /* padding */);\n\t\t\t}\n\t\t}\n\t}\n\n\temitSignedV(val, pad) {\n\t\tif (val >= 0) {\n\t\t\tval *= 2;\n\t\t} else {\n\t\t\tval = (-val) * 2 - 1;\n\t\t}\n\t\tthis.emitUnsignedV(val, pad);\n\t}\n\n\tposition() {\n\t\treturn this.offset + this.buffNum * this.chunkLength;\n\t}\n\n\tlength() {\n\t\treturn this.lastLength + (this.buffers.length - 1) * this.chunkLength;\n\t}\n\n\ttruncate() {\n\t\tthis.lastLength = this.offset;\n\t\tthis.buffers.length = this.buffNum + 1;\n\t}\n\n\t_maybeCreateBuffers() {\n\t\twhile (this.buffNum >= this.buffers.length) {\n\t\t\tthis.buffers.push(Buffer.alloc(this.chunkLength));\n\t\t\tthis.lastLength = 0;\n\t\t}\n\t}\n\n\t_maybeUpdateLength() {\n\t\tif (\n\t\t\tthis.offset > this.lastLength &&\n\t\t\tthis.buffNum === this.buffers.length - 1\n\t\t) {\n\t\t\tthis.lastLength = this.offset;\n\t\t}\n\t}\n\n\tseek(pos) {\n\t\tconsole.assert(pos !== undefined);\n\t\tthis.buffNum = Math.floor(pos / this.chunkLength);\n\t\tthis.offset = pos - (this.buffNum * this.chunkLength);\n\t\tthis._maybeCreateBuffers();\n\t\tthis.currBuff = this.buffers[this.buffNum];\n\t\tthis._maybeUpdateLength();\n\t}\n\n\tread() {\n\t\tif (this.offset >= this.currBuff.length) {\n\t\t\tthis.buffNum++; this.offset = 0;\n\t\t\tthis._maybeCreateBuffers();\n\t\t\tthis.currBuff = this.buffers[this.buffNum];\n\t\t}\n\t\tconst b = this.currBuff[this.offset++];\n\t\tthis._maybeUpdateLength();\n\t\treturn b;\n\t}\n\n\treadUnsignedV() {\n\t\tlet b = this.read();\n\t\t/* eslint-disable no-bitwise */\n\t\tlet val = b & 127;\n\t\twhile (b & 128) {\n\t\t\tval += 1;\n\t\t\tb = this.read();\n\t\t\tval = (val << 7) + (b & 127);\n\t\t}\n\t\t/* eslint-enable no-bitwise */\n\t\treturn val;\n\t}\n\n\treadSignedV() {\n\t\tconst v = this.readUnsignedV();\n\t\t/* eslint-disable no-bitwise */\n\t\tif (v & 1) {\n\t\t\treturn -(v >>> 1) - 1;\n\t\t} else {\n\t\t\treturn (v >>> 1);\n\t\t}\n\t\t/* eslint-enable no-bitwise */\n\t}\n\n\twriteFile(outFile) {\n\t\tconst fd = fs.openSync(outFile, 'w');\n\t\ttry {\n\t\t\tlet i;\n\t\t\tfor (i = 0; i < this.buffers.length - 1; i++) {\n\t\t\t\tfs.writeSync(fd, this.buffers[i]);\n\t\t\t}\n\t\t\tfs.writeSync(fd, this.buffers[i], 0, this.lastLength);\n\t\t} finally {\n\t\t\tfs.closeSync(fd);\n\t\t}\n\t}\n}\n\nfunction processOne(inFile, outFile, verbose, justBrackets, maxEdgeBytes) {\n\tif (justBrackets === undefined) {\n\t\tjustBrackets = /\\bbrack-/.test(inFile);\n\t}\n\tif (maxEdgeBytes === undefined) {\n\t\tmaxEdgeBytes = 10;\n\t}\n\n\tlet finalStates;\n\tconst alphabet = new Set();\n\tconst sym2byte = function(sym) {\n\t\tif (sym === '@_IDENTITY_SYMBOL_@') {\n\t\t\treturn BYTE_IDENTITY;\n\t\t}\n\t\tif (sym === '@0@') {\n\t\t\treturn BYTE_EPSILON;\n\t\t}\n\t\tif (sym === '[[') {\n\t\t\treturn BYTE_LBRACKET;\n\t\t}\n\t\tif (sym === ']]') {\n\t\t\treturn BYTE_RBRACKET;\n\t\t}\n\t\tif (/^[0-9A-F][0-9A-F]$/i.test(sym)) {\n\t\t\tconst b = Number.parseInt(sym, 16);\n\t\t\tconsole.assert(b !== 0 && b < 0xF8);\n\t\t\treturn b;\n\t\t}\n\t\tconsole.assert(false, `Bad symbol: ${ sym }`);\n\t};\n\t// Quickly read through once in order to pull out the set of final states\n\t// and the alphabet\n\treadAttFile(inFile, (state, edges) => {\n\t\tfor (const e of edges) {\n\t\t\talphabet.add(sym2byte(e.inChar));\n\t\t\talphabet.add(sym2byte(e.outChar));\n\t\t}\n\t}, (fs2) => {\n\t\tfinalStates = new Set(fs2);\n\t});\n\t// Anything not in `alphabet` is going to be treated as 'anything else'\n\t// but we want to force 0x00 and 0xF8-0xFF to be treated as 'anything else'\n\talphabet.delete(0);\n\tfor (let i = 0xF8; i <= 0xFF; i++) {\n\t\talphabet.delete(i);\n\t}\n\t// Emit a magic number.\n\tconst out = new DynamicBuffer();\n\tout.emit(0x70); out.emit(0x46); out.emit(0x53); out.emit(0x54);\n\tout.emit(0x00); out.emit(0x57); out.emit(0x4D); out.emit(0x00);\n\t// Ok, now read through and build the output array\n\tlet synState = -1;\n\tconst stateMap = new Map();\n\t// Reserve the EOF state (0 in output)\n\tstateMap.set(synState--, out.position());\n\tout.emitUnsignedV(0);\n\tout.emitUnsignedV(0);\n\tconst processState = (state, edges) => {\n\t\tconsole.assert(!stateMap.has(state));\n\t\tstateMap.set(state, out.position());\n\t\tout.emitUnsignedV(maxEdgeBytes);\n\t\t// First emit epsilon edges\n\t\tconst r = edges.filter(e => e.inByte === BYTE_EPSILON);\n\t\t// Then emit a sorted table of inByte transitions, omitting repeated\n\t\t// entries (so it's a range map)\n\t\t// Note that BYTE_EOF is always either FAIL or a transition to a unique\n\t\t// state, so we can always treat values lower than the first entry\n\t\t// or higher than the last entry as FAIL.\n\t\tconst edgeMap = new Map(edges.map(e => [e.inByte, e]));\n\t\tlet lastEdge = { outByte: BYTE_FAIL, to: state };\n\t\tfor (let i = 1; i <= BYTE_EOF; i++) {\n\t\t\tlet e = (alphabet.has(i) || i === BYTE_EOF) ?\n\t\t\t\tedgeMap.get(i) : edgeMap.get(BYTE_IDENTITY);\n\t\t\tif (!e) {\n\t\t\t\te = { outByte: BYTE_FAIL, to: state };\n\t\t\t}\n\t\t\t// where possible remap outByte to IDENTITY to maximize chances\n\t\t\t// of adjacent states matching\n\t\t\tconst out2 = (i === e.outByte) ? BYTE_IDENTITY : e.outByte;\n\t\t\tif (out2 !== lastEdge.outByte || e.to !== lastEdge.to) {\n\t\t\t\tlastEdge = { inByte: i, outByte: out2, to: e.to };\n\t\t\t\tr.push(lastEdge);\n\t\t\t}\n\t\t}\n\t\tout.emitUnsignedV(r.length);\n\t\tr.forEach((e) => {\n\t\t\tout.emit(e.inByte);\n\t\t\tout.emit(e.outByte);\n\t\t\tout.emitSignedV(e.to, maxEdgeBytes - 2 /* for inByte/outByte */);\n\t\t});\n\t};\n\treadAttFile(inFile, (state, edges) => {\n\t\t// Map characters to bytes\n\t\tedges = edges.map((e) => ({\n\t\t\tto: e.to,\n\t\t\tinByte: sym2byte(e.inChar),\n\t\t\toutByte: sym2byte(e.outChar),\n\t\t}));\n\t\t// If this is a final state, add a synthetic EOF edge\n\t\tif (finalStates.has(state)) {\n\t\t\tedges.push({ to: -1, inByte: BYTE_EOF, outByte: BYTE_EPSILON });\n\t\t}\n\t\t// Collect edges and figure out if we need to split the state\n\t\t// (if there are multiple edges with the same non-epsilon inByte).\n\t\tconst edgeMap = new DefaultMap(() => []);\n\t\tfor (const e of edges) {\n\t\t\tedgeMap.getDefault(e.inByte).push(e);\n\t\t}\n\t\t// For each inByte with multiple outgoing edges, replace those\n\t\t// edges with a single edge:\n\t\t//  { to: newState, inChar: e.inByte, outChar: BYTE_EPSILON }\n\t\t// ...and then create a new state with edges:\n\t\t//  [{ to: e[n].to, inChar: BYTE_EPSILON, outChar: e[n].outChar},...]\n\t\tconst extraStates = [];\n\t\tfor (const [inByte, e] of edgeMap.entries()) {\n\t\t\tif (inByte !== BYTE_EPSILON && e.length > 1) {\n\t\t\t\tconst nstate = synState--;\n\t\t\t\textraStates.push({\n\t\t\t\t\tstate: nstate,\n\t\t\t\t\tedges: e.map((ee) => ({\n\t\t\t\t\t\tto: ee.to,\n\t\t\t\t\t\tinByte: BYTE_EPSILON,\n\t\t\t\t\t\toutByte: ee.outByte,\n\t\t\t\t\t})),\n\t\t\t\t});\n\t\t\t\tedgeMap.set(inByte, [{\n\t\t\t\t\tto: nstate,\n\t\t\t\t\tinByte: inByte,\n\t\t\t\t\toutByte: BYTE_EPSILON\n\t\t\t\t}]);\n\t\t\t}\n\t\t}\n\t\tprocessState(state, [].concat.apply([], Array.from(edgeMap.values())));\n\t\textraStates.forEach((extra) => {\n\t\t\tprocessState(extra.state, extra.edges);\n\t\t});\n\t});\n\t// Rarely a state will not be mentioned in the .att file except\n\t// in the list of final states; check this & process at the end.\n\tfinalStates.forEach((state) => {\n\t\tif (!stateMap.has(state)) {\n\t\t\tprocessState(state, [\n\t\t\t\t{ to: -1, inByte: BYTE_EOF, outByte: BYTE_EPSILON }\n\t\t\t]);\n\t\t}\n\t});\n\t// Fixup buffer to include relative offsets to states\n\tconst state0pos = stateMap.get(-1);\n\tout.seek(state0pos);\n\twhile (out.position() < out.length()) {\n\t\tconst edgeWidth = out.readUnsignedV();\n\t\tconst nEdges = out.readUnsignedV();\n\t\tconst edge0 = out.position();\n\t\tfor (let i = 0; i < nEdges; i++) {\n\t\t\tconst p = edge0 + i * edgeWidth + /* inByte/outByte: */ 2;\n\t\t\tout.seek(p);\n\t\t\tconst state = out.readSignedV();\n\t\t\tout.seek(p);\n\t\t\tconsole.assert(stateMap.has(state), `${ state } not found`);\n\t\t\tout.emitSignedV(stateMap.get(state) - p, edgeWidth - 2);\n\t\t}\n\t\tout.seek(edge0 + nEdges * edgeWidth);\n\t}\n\t// Now iteratively narrow the field widths until the file is as small\n\t// as it can be.\n\twhile (true) {\n\t\tlet trimmed = 0;\n\t\tstateMap.clear();\n\t\tconst widthMap = new Map();\n\t\tout.seek(state0pos);\n\t\twhile (out.position() < out.length()) {\n\t\t\tconst statePos = out.position();\n\t\t\tstateMap.set(statePos, statePos - trimmed);\n\t\t\tconst edgeWidth = out.readUnsignedV();\n\t\t\tconst widthPos = out.position();\n\t\t\tconst nEdges = out.readUnsignedV();\n\t\t\tlet maxWidth = 0;\n\t\t\tconst edge0 = out.position();\n\t\t\tfor (let i = 0; i < nEdges; i++) {\n\t\t\t\tconst p = edge0 + i * edgeWidth;\n\t\t\t\tout.seek(p);\n\t\t\t\tout.read(); out.read(); out.readSignedV();\n\t\t\t\tconst thisWidth = out.position() - p;\n\t\t\t\tmaxWidth = Math.max(maxWidth, thisWidth);\n\t\t\t}\n\t\t\twidthMap.set(statePos, maxWidth);\n\t\t\ttrimmed += (edgeWidth - maxWidth) * nEdges;\n\t\t\tif (maxWidth !== edgeWidth) {\n\t\t\t\tout.seek(statePos);\n\t\t\t\tout.emitUnsignedV(maxWidth);\n\t\t\t\ttrimmed += (out.position() - widthPos);\n\t\t\t\tout.seek(statePos);\n\t\t\t\tout.emitUnsignedV(edgeWidth);\n\t\t\t}\n\t\t\tout.seek(edge0 + nEdges * edgeWidth);\n\t\t}\n\t\tstateMap.set(out.position(), out.position() - trimmed);\n\n\t\tif (trimmed === 0) {\n\t\t\tbreak; /* nothing left to do */\n\t\t}\n\t\tif (verbose) {\n\t\t\tconsole.log('.');\n\t\t}\n\n\t\tout.seek(state0pos);\n\t\twhile (out.position() < out.length()) {\n\t\t\tconst statePos = out.position();\n\t\t\tconsole.assert(stateMap.has(statePos) && widthMap.has(statePos));\n\t\t\tconst nWidth = widthMap.get(statePos);\n\n\t\t\tconst oldWidth = out.readUnsignedV();\n\t\t\tconst nEdges = out.readUnsignedV();\n\t\t\tconst edge0 = out.position();\n\n\t\t\tlet nPos = stateMap.get(statePos);\n\t\t\tout.seek(nPos);\n\t\t\tout.emitUnsignedV(nWidth);\n\t\t\tout.emitUnsignedV(nEdges);\n\t\t\tnPos = out.position();\n\n\t\t\tfor (let i = 0; i < nEdges; i++) {\n\t\t\t\tout.seek(edge0 + i * oldWidth);\n\t\t\t\tconst inByte = out.read();\n\t\t\t\tconst outByte = out.read();\n\t\t\t\tlet toPos = out.position();\n\t\t\t\ttoPos += out.readSignedV();\n\t\t\t\tconsole.assert(stateMap.has(toPos), toPos);\n\t\t\t\ttoPos = stateMap.get(toPos);\n\n\t\t\t\tout.seek(nPos);\n\t\t\t\tout.emit(inByte);\n\t\t\t\tout.emit(outByte);\n\t\t\t\ttoPos -= out.position();\n\t\t\t\tout.emitSignedV(toPos, nWidth - 2);\n\t\t\t\tnPos = out.position();\n\t\t\t}\n\t\t\tout.seek(edge0 + nEdges * oldWidth);\n\t\t}\n\t\tout.seek(stateMap.get(out.position()));\n\t\tout.truncate();\n\t}\n\n\t// Done!\n\tout.writeFile(outFile);\n}\n\nfunction main() {\n\tconst yopts = yargs\n\t.usage(\n\t\t'Usage: $0 [options] <conversion> <inverse>\\n' +\n\t\t'Converts a finite-state transducer in .att format.'\n\t)\n\t.options({\n\t\t'output': {\n\t\t\tdescription: 'Output filename (or base name)',\n\t\t\talias: 'o',\n\t\t\tnargs: 1,\n\t\t\tnormalize: true,\n\t\t},\n\t\t'file': {\n\t\t\tdescription: 'Input .att filename',\n\t\t\talias: 'f',\n\t\t\tconflicts: 'language',\n\t\t\timplies: 'output',\n\t\t\tnargs: 1,\n\t\t\tnormalize: true,\n\t\t},\n\t\t'language': {\n\t\t\tdescription: 'Converts trans-{conversion}, brack-{conversion}-noop, and brack-{conversion}-{inverse} in default locations',\n\t\t\talias: 'l',\n\t\t\tconflicts: 'file',\n\t\t\tarray: true,\n\t\t},\n\t\t'brackets': {\n\t\t\tdescription: 'Emit a bracket-location machine',\n\t\t\talias: 'b',\n\t\t\tboolean: true,\n\t\t\tdefault: undefined,\n\t\t},\n\t\t'verbose': {\n\t\t\tdescription: 'Show progress',\n\t\t\talias: 'v',\n\t\t\tboolean: true,\n\t\t},\n\t})\n\t.example('$0 -l sr-ec sr-el');\n\n\tconst argv = yopts.argv;\n\tif (argv.help) {\n\t\tyopts.showHelp();\n\t\treturn;\n\t}\n\n\tif (argv.file) {\n\t\tprocessOne(argv.file, argv.output, argv.brackets);\n\t} else if (argv.language) {\n\t\tconst convertLang = argv.language[0];\n\t\tconst inverseLangs = argv.language.slice(1);\n\t\tconst baseDir = path.join(__dirname, '..', 'lib', 'language', 'fst');\n\t\tfor (const f of [\n\t\t\t`trans-${ convertLang }`,\n\t\t\t`brack-${ convertLang }-noop`,\n\t\t].concat(inverseLangs.map(inv => `brack-${ convertLang }-${ inv }`))) {\n\t\t\tif (argv.verbose) {\n\t\t\t\tconsole.log(f);\n\t\t\t}\n\t\t\tprocessOne(\n\t\t\t\tpath.join(baseDir, `${ f }.att`),\n\t\t\t\tpath.join(baseDir, `${ f }.pfst`),\n\t\t\t\targv.verbose\n\t\t\t);\n\t\t}\n\t} else {\n\t\tyopts.showHelp();\n\t}\n}\n\nif (require.main === module) {\n\tmain();\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/compare.linter.results.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":105,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":105,"endColumn":17},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":108,"column":18,"nodeType":"CallExpression","endLine":108,"endColumn":65},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":112,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":112,"endColumn":17},{"ruleId":"security/detect-non-literal-require","severity":1,"message":"Found non-literal argument in require","line":115,"column":18,"nodeType":"CallExpression","endLine":115,"endColumn":65},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":118,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":118,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\n/* Fetch new results from https://tools.wmflabs.org/wikitext-deprecation/api\n * and save them locally and run this script to compare how things have changed\n * for a particular category */\n\n// ------------------ Console printer ------------------\nfunction pad(n, len) {\n\tif (!len) {\n\t\tlen = 15;\n\t}\n\treturn String(n).padStart(len);\n}\n\nfunction ConsolePrinter() {}\n\nConsolePrinter.printSectionHeader = function(heading) {\n\tconsole.log(heading);\n};\n\nConsolePrinter.printTableHeader = function(columns) {\n\tconsole.log(\"-\".repeat(80));\n\tconsole.log(columns.map(function(c) {\n\t\treturn pad(c);\n\t}).join('\\t'));\n\tconsole.log(\"-\".repeat(80));\n};\n\nConsolePrinter.printTableRow = function(columns) {\n\tconsole.log(columns.map(function(c) {\n\t\treturn pad(c);\n\t}).join('\\t'));\n};\n\nConsolePrinter.printTableFooter = function() {\n\tconsole.log(\"-\".repeat(80));\n\tconsole.log(\"\\n\");\n};\n\n// ------------------ Wikitxt printer ------------------\nfunction WikitextPrinter() {}\n\nWikitextPrinter.printSectionHeader = function(heading) {\n\tconsole.log('==' + heading + '==');\n};\n\nWikitextPrinter.printTableHeader = function(columns) {\n\tconsole.log('{| class=\"wikitable sortable\" style=\"width:60%\"');\n\tconsole.log('|-');\n\tconsole.log('!' + columns.join('!!'));\n};\n\nWikitextPrinter.printTableRow = function(columns) {\n\tconsole.log('|-');\n\tconsole.log('|' + columns.join('||'));\n};\n\nWikitextPrinter.printTableFooter = function() {\n\tconsole.log('|}\\n');\n};\n\n// ------------------------------------------------------\nrequire('../core-upgrade.js');\nvar path = require('path');\nvar yargs = require('yargs');\n\nvar opts = yargs\n.usage(\"Usage $0 [options] old-json-file new-json-file\")\n.options({\n\thelp: {\n\t\tdescription: 'Show this message',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\twikify: {\n\t\tdescription: 'Emit report in wikitext format for a wiki',\n\t\t'boolean': true,\n\t\t'default': false,\n\t},\n\tbaseline_count: {\n\t\tdescription: 'Baseline count for determinining remex-readiness',\n\t\t'boolean': false,\n\t\t'default': 25,\n\t},\n});\n\nvar highPriorityCats = [\n\t\"deletable-table-tag\",\n\t\"pwrap-bug-workaround\",\n\t\"self-closed-tag\",\n\t\"tidy-whitespace-bug\",\n\t\"html5-misnesting\",\n\t\"tidy-font-bug\",\n\t\"multiline-html-table-in-list\",\n\t\"multiple-unclosed-formatting-tags\",\n\t\"unclosed-quotes-in-heading\",\n];\n\nvar argv = opts.argv;\nvar numArgs = argv._.length;\nif (numArgs < 2) {\n\topts.showHelp();\n\tprocess.exit(1);\n}\n\nvar oldResults = require(path.resolve(process.cwd(), argv._[0]));\nvar wikis = Object.keys(oldResults);\nif (wikis.length === 0) {\n\tconsole.log(\"Old results from \" + argv._[0] + \" seems empty?\");\n\tprocess.exit(1);\n}\n\nvar newResults = require(path.resolve(process.cwd(), argv._[1]));\nif (Object.keys(newResults).length === 0) {\n\tconsole.log(\"New results from \" + argv._[1] + \" seems empty?\");\n\tprocess.exit(1);\n}\n\nvar printer = argv.wikify ? WikitextPrinter : ConsolePrinter;\n\nfunction printStatsForCategory(cat, p) {\n\tvar changes = wikis.reduce(function(accum, w) {\n\t\t// Skip wikis that don't have results for both wikis\n\t\tif (!newResults[w]) {\n\t\t\treturn accum;\n\t\t}\n\t\t// Record changes\n\t\tvar o = oldResults[w].linter_info[cat];\n\t\tvar n = newResults[w].linter_info[cat];\n\t\tif (n !== o) {\n\t\t\taccum.push({\n\t\t\t\twiki: w,\n\t\t\t\told: o,\n\t\t\t\tnew: n,\n\t\t\t\tchange: n - o,\n\t\t\t\tpercentage: o > 0 ? Math.round((n - o) / o * 1000) / 10 : 0,\n\t\t\t});\n\t\t}\n\t\treturn accum;\n\t}, []);\n\n\t// Most improved wikis first\n\tchanges.sort(function(a, b) {\n\t\treturn a.change > b.change ? 1 : (a.change < b.change ? -1 : 0);\n\t});\n\n\tp.printSectionHeader(\"Changes in \" + cat + \" counts for wikis\");\n\tp.printTableHeader([\"WIKI\", \"OLD\", \"NEW\", \"CHANGE\", \"PERCENTAGE\"]);\n\tfor (var i = 0; i < changes.length; i++) {\n\t\tvar d = changes[i];\n\t\tp.printTableRow([d.wiki, d.old, d.new, d.change, d.percentage]);\n\t}\n\tp.printTableFooter();\n}\n\n// Dump stats for each high-priority category\nhighPriorityCats.forEach(function(cat) {\n\tprintStatsForCategory(cat, printer);\n});\n\n// If count is below this threshold for all high priority categories,\n// we deem those wikis remex-ready. For now, hard-coded to zero, but\n// could potentially rely on a CLI option.\nvar maxCountPerHighPriorityCategory = parseInt(argv.baseline_count, 10);\nvar remexReadyWikis = [];\nwikis.forEach(function(w) {\n\tif (!newResults[w]) {\n\t\treturn;\n\t}\n\n\t// Check if this wiki is remex-ready\n\tvar remexReady = highPriorityCats.every(function(c) {\n\t\treturn newResults[w].linter_info[c] <= maxCountPerHighPriorityCategory;\n\t});\n\tif (remexReady) {\n\t\tremexReadyWikis.push({\n\t\t\tname: w,\n\t\t\tchanged: highPriorityCats.some(function(c) {\n\t\t\t\treturn oldResults[w].linter_info[c] > maxCountPerHighPriorityCategory;\n\t\t\t}),\n\t\t});\n\t}\n});\n\nif (remexReadyWikis.length > 0) {\n\tconsole.log('\\n');\n\tprinter.printSectionHeader('Wikis with < ' + argv.baseline_count + ' errors in all high priority categories');\n\tprinter.printTableHeader(['New', 'Changed?']);\n\tfor (var i = 0; i < remexReadyWikis.length; i++) {\n\t\tprinter.printTableRow([remexReadyWikis[i].name, remexReadyWikis[i].changed]);\n\t}\n\tprinter.printTableFooter();\n}\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/fetch-parserTests.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n/**\n * Fetch new sync'ed parserTests from upstream repositories.\n */\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\n// UPDATE THESE when upstream repository includes new parsoid-relevant tests\n// This ensures that our knownFailures list is in sync.\n//\n// ==> Use \"./fetch-parserTests.js <target>\" to download latest\n//     parserTests and update these hashes automatically.\n// ==> Use \"./fetch-parserTests.js --all\" to download all the latest\n//     parserTests and update all the hashes.\n//\n\nconst fs = require('pn/fs');\nconst path = require('path');\nconst https = require('https');\nconst crypto = require('crypto');\nconst Buffer = require('buffer').Buffer;\nconst Promise = require('../lib/utils/promise.js');\nconst testDir = path.join(__dirname, '../tests/parser/');\nconst testFilesPath = path.join(testDir, '../parserTests.json');\nconst testFiles = require(testFilesPath);\n\nconst computeSHA1 = Promise.async(function *(targetName) {\n\tconst targetPath = path.join(testDir, targetName);\n\tif (!(yield fs.exists(targetPath))) {\n\t\treturn \"<file not present>\";\n\t}\n\tconst contents = yield fs.readFile(targetPath);\n\treturn crypto.createHash('sha1').update(contents).digest('hex')\n\t\t.toLowerCase();\n});\n\nconst fetch = async function(repo, testFile, gitCommit, skipCheck) {\n\tconst repoInfo = testFiles[repo];\n\tconst targets = repoInfo.targets;\n\tfor (const targetName in targets) {\n\t\tif (testFile && (testFile !== targetName)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst file = targets[targetName];\n\t\tconst filePath = '/r/plugins/gitiles/' + testFiles[repo].project + '/+/' + (gitCommit || repoInfo.latestCommit) + '/' + file.path + '?format=TEXT';\n\n\t\tconsole.log('Fetching ' + targetName + ' history from ' + filePath);\n\n\t\tconst url = {\n\t\t\thost: 'gerrit.wikimedia.org',\n\t\t\tpath: filePath,\n\t\t\theaders: { 'user-agent': 'wikimedia-parsoid' },\n\t\t};\n\t\tawait new Promise(function(resolve, reject) {\n\t\t\thttps.get(url, function(result) {\n\t\t\t\tconst targetPath = path.join(testDir, targetName);\n\t\t\t\tconst out = fs.createWriteStream(targetPath);\n\t\t\t\tconst rs = [];\n\t\t\t\tresult.on('data', function(data) {\n\t\t\t\t\trs.push(data);\n\t\t\t\t});\n\t\t\t\tresult.on('end', function() {\n\t\t\t\t\t// Gitiles raw files are base64 encoded\n\t\t\t\t\tout.write(Buffer.from(rs.join(''), 'base64'));\n\t\t\t\t\tout.end();\n\t\t\t\t\tout.destroySoon();\n\t\t\t\t});\n\t\t\t\tout.on('close', resolve);\n\t\t\t}).on('error', function(err) {\n\t\t\t\tconsole.error(err);\n\t\t\t\treject(err);\n\t\t\t});\n\t\t}).then(Promise.async(function *() {\n\t\t\tif (!skipCheck) {\n\t\t\t\tconst sha1 = yield computeSHA1(targetName);\n\t\t\t\tif (file.expectedSHA1 !== sha1) {\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t'Parsoid expected sha1sum', file.expectedSHA1,\n\t\t\t\t\t\t'but got', sha1\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}));\n\t}\n};\n\nconst isUpToDate = Promise.async(function *(targetRepo) {\n\tconst repoInfo = testFiles[targetRepo];\n\tconst targets = repoInfo.targets;\n\tfor (const targetName in targets) {\n\t\tconst expectedSHA1 = targets[targetName].expectedSHA1;\n\t\tif (expectedSHA1 !== (yield computeSHA1(targetName))) {\n\t\t\treturn false;\n\t\t}\n\t}\n\treturn true;\n});\n\nconst forceUpdate = Promise.async(function *(targetRepo, branch) {\n\tconst repoInfo = testFiles[targetRepo];\n\tconst gerritPath = '/r/plugins/gitiles/' + repoInfo.project + '/+log/refs/heads/' + branch + '?format=JSON';\n\tconsole.log('Fetching ' + targetRepo + ' history from ' + gerritPath);\n\n\t// fetch the history page\n\tconst url = {\n\t\thost: 'gerrit.wikimedia.org',\n\t\tpath: gerritPath,\n\t\theaders: { 'user-agent': 'wikimedia-parsoid' },\n\t};\n\tconst gitCommit = JSON.parse(yield new Promise(function(resolve, reject) {\n\t\thttps.get(url, function(result) {\n\t\t\tvar res = '';\n\t\t\tresult.setEncoding('utf8');\n\t\t\tresult.on('data', function(data) {\n\t\t\t\tres += data;\n\t\t\t});\n\t\t\t// The slice on the result is because gitiles is returning\n\t\t\t// JSON starting with extraneous characters, \")]}'\\n\"\n\t\t\tresult.on('end', function() {\n\t\t\t\tresolve(res.slice(5));\n\t\t\t});\n\t\t}).on('error', function(err) {\n\t\t\tconsole.error(err);\n\t\t\treject(err);\n\t\t});\n\t})).log[0].commit;\n\n\tconst targets = repoInfo.targets;\n\tfor (const targetName in targets) {\n\t\t// download latest file\n\t\tyield fetch(targetRepo, targetName, gitCommit, true);\n\t\tconst fileHash = yield computeSHA1(targetName);\n\n\t\t// now rewrite this file!\n\t\ttargets[targetName].expectedSHA1 = fileHash;\n\t}\n\trepoInfo.latestCommit = gitCommit;\n\tyield fs.writeFile(testFilesPath, JSON.stringify(testFiles, null, '\\t') + '\\n', 'utf8');\n\tconsole.log('Updated', testFilesPath);\n});\n\nPromise.async(function *() {\n\tconst usage = 'Usage: $0 <repo-key-from-parserTests.json>';\n\tconst yargs = require('yargs');\n\tconst opts = yargs\n\t.usage(usage)\n\t.options({\n\t\t'all': {\n\t\t\tdescription: 'Fetch files for all targets defined in the parserTests.json.'\n\t\t},\n\t\t'branch': {\n\t\t\tdescription: 'Branch on which to fetch the latest parserTests files.',\n\t\t\tdefault: 'master',\n\t\t},\n\t\t'help': { description: 'Show this message' },\n\t});\n\tconst argv = opts.argv;\n\tconst enoughArgs = (argv.all || argv._.length >= 1);\n\tif (argv.help || !enoughArgs) {\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\tif (argv.force) {\n\t\t// Allow this for back-compat, but we don't need this argument\n\t\t// any more.\n\t}\n\tconst targetRepos = argv.all ? Object.keys(testFiles) : [ argv._[0] ];\n\tfor (const targetRepo of targetRepos) {\n\t\tif (!testFiles.hasOwnProperty(targetRepo)) {\n\t\t\tconsole.warn(targetRepo + ' not defined in parserTests.json');\n\t\t\tcontinue;\n\t\t}\n\t\tif (targetRepo === 'parsoid') {\n\t\t\tconsole.warn('Nothing to sync for parsoid files');\n\t\t\tcontinue;\n\t\t}\n\t\tif (yield isUpToDate(targetRepo)) {\n\t\t\tconsole.warn(\"Files not locally modified.\");\n\t\t}\n\t\tyield forceUpdate(targetRepo, argv.branch);\n\t}\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/fetch-revision-data.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/tools'","line":20,"column":31,"nodeType":"Literal","messageId":"notFound","endLine":20,"endColumn":56},{"ruleId":"n/no-missing-require","severity":1,"message":"Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/tools'","line":22,"column":35,"nodeType":"Literal","messageId":"notFound","endLine":22,"endColumn":73}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\n\nrequire('../core-upgrade.js');\n\n/*\n * Given a title (and an optional revision id), fetch:\n * 1. the wikitext for a page from the MW API\n * 2. latest matching HTML and data-parsoid for the revision from RESTBase\n */\n\nvar fs = require('pn/fs');\nvar path = require('path');\nvar yargs = require('yargs');\nvar yaml = require('js-yaml');\n\nvar Promise = require('../lib/utils/promise.js');\n\nvar TemplateRequest = require('../lib/mw/ApiRequest.js').TemplateRequest;\nvar ParsoidConfig = require('../lib/config/ParsoidConfig.js').ParsoidConfig;\nvar MWParserEnvironment = require('../lib/config/MWParserEnvironment.js').MWParserEnvironment;\nvar Util = require('../lib/utils/Util.js').Util;\nvar ScriptUtils = require('./ScriptUtils.js').ScriptUtils;\n\nvar fetch = Promise.async(function *(page, revid, opts) {\n\tvar prefix = opts.prefix || null;\n\tvar domain = opts.domain || null;\n\tif (!prefix && !domain) {\n\t\tdomain = \"en.wikipedia.org\";\n\t}\n\n\tvar parsoidOptions = {};\n\n\tif (ScriptUtils.booleanOption(opts.config)) {\n\t\tvar p = (typeof (opts.config) === 'string') ?\n\t\t\tpath.resolve('.', opts.config) :\n\t\t\tpath.resolve(__dirname, '../config.yaml');\n\t\t// Assuming Parsoid is the first service in the list\n\t\tparsoidOptions = yaml.load(yield fs.readFile(p, 'utf8')).services[0].conf;\n\t}\n\n\tScriptUtils.setTemplatingAndProcessingFlags(parsoidOptions, opts);\n\tScriptUtils.setDebuggingFlags(parsoidOptions, opts);\n\n\tif (parsoidOptions.localsettings) {\n\t\tparsoidOptions.localsettings = path.resolve(__dirname, parsoidOptions.localsettings);\n\t}\n\n\tvar pc = new ParsoidConfig(null, parsoidOptions);\n\tif (!prefix) {\n\t\t// domain has been provided\n\t\tprefix = pc.getPrefixFor(domain);\n\t} else if (!domain) {\n\t\t// prefix has been set\n\t\tdomain = pc.mwApiMap.get(prefix).domain;\n\t}\n\tpc.defaultWiki = prefix;\n\n\tvar outputPrefix = prefix + \".\" + Util.phpURLEncode(page);\n\tvar rbOpts = {\n\t\turi: null,\n\t\tmethod: 'GET',\n\t\theaders: {\n\t\t\t'User-Agent': pc.userAgent,\n\t\t},\n\t};\n\n\tvar env = yield MWParserEnvironment.getParserEnv(pc, {\n\t\tprefix: prefix,\n\t\tdomain: domain,\n\t\tpageName: page,\n\t});\n\n\t// Fetch wikitext from mediawiki API\n\tvar target = page ?\n\t\tenv.normalizeAndResolvePageTitle() : null;\n\tyield TemplateRequest.setPageSrcInfo(env, target, revid);\n\tyield fs.writeFile(outputPrefix + \".wt\", env.page.src, 'utf8');\n\n\t// Fetch HTML from RESTBase\n\trbOpts.uri = \"https://\" + domain + \"/api/rest_v1/page/html/\" + Util.phpURLEncode(page) + (revid ? \"/\" + revid : \"\");\n\tvar resp = yield ScriptUtils.retryingHTTPRequest(2, rbOpts);\n\tyield fs.writeFile(outputPrefix + \".html\", resp[1], 'utf8');\n\tvar etag = resp[0].headers.etag.replace(/^W\\//, '').replace(/\"/g, '');\n\n\t// Fetch matching data-parsoid form RESTBase\n\trbOpts.uri = \"https://\" + domain + \"/api/rest_v1/page/data-parsoid/\" + Util.phpURLEncode(page) + \"/\" + etag;\n\tresp = yield ScriptUtils.retryingHTTPRequest(2, rbOpts);\n\n\t// RESTBase doesn't have the outer wrapper\n\t// that the parse.js script expects\n\tvar pb = '{\"parsoid\":' + resp[1] + \"}\";\n\tyield fs.writeFile(outputPrefix + \".pb.json\", pb, 'utf8');\n\n\tconsole.log(\"If you are debugging a bug report on a VE edit, make desired edit to the HTML file and save to a new file.\");\n\tconsole.log(\"Then run the following script to generated edited wikitext\");\n\tconsole.log(\"parse.js --html2wt --selser --oldtextfile \"\n\t\t+ outputPrefix + \".wt\"\n\t\t+ \" --oldhtmlfile \" + outputPrefix + \".html\"\n\t\t+ \" --pbinfile \" + outputPrefix + \".pb.json\"\n\t\t+ \" < edited.html > edited.wt\");\n});\n\nvar usage = 'Usage: $0 [options] --title <page-title> [--revid <rev-id>]\\n';\nvar yopts = yargs\n.usage(usage)\n.options({\n\t'config': {\n\t\tdescription: \"Path to a config.yaml file. Defaults to the server's config.yaml\",\n\t\t'default': true,\n\t},\n\t'prefix': {\n\t\tdescription: 'Which wiki prefix to use; e.g. \"enwiki\" for English wikipedia, \"eswiki\" for Spanish, \"mediawikiwiki\" for mediawiki.org',\n\t\t'boolean': false,\n\t\t'default': null,\n\t},\n\t'domain': {\n\t\tdescription: 'Which wiki to use; e.g. \"en.wikipedia.org\" for English wikipedia, \"es.wikipedia.org\" for Spanish, \"www.mediawiki.org\" for mediawiki.org',\n\t\t'boolean': false,\n\t\t'default': null,\n\t},\n\t'revid': {\n\t\tdescription: 'Page revision to fetch',\n\t\t'boolean': false,\n\t},\n\t'title': {\n\t\tdescription: 'Page title to fetch',\n\t\t'boolean': false,\n\t},\n});\n\nPromise.async(function *() {\n\tvar argv = yopts.argv;\n\tvar title = argv.title;\n\tvar error;\n\tif (!title) {\n\t\terror = \"Must specify a title.\";\n\t}\n\n\tif (argv.help || error) {\n\t\tif (error) {\n\t\t\t// Make the error standout in the output\n\t\t\tvar buf = [\"-------\"];\n\t\t\tfor (var i = 0; i < error.length; i++) {\n\t\t\t\tbuf.push(\"-\");\n\t\t\t}\n\t\t\tbuf = buf.join('');\n\t\t\tconsole.error(buf);\n\t\t\tconsole.error('ERROR:', error);\n\t\t\tconsole.error(buf);\n\t\t}\n\t\tyopts.showHelp();\n\t\treturn;\n\t}\n\n\tyield fetch(title, argv.revid, argv);\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/fetch_stray_toc_edits.js","messages":[{"ruleId":"security/detect-non-literal-regexp","severity":1,"message":"Found non-literal argument to RegExp Constructor","line":138,"column":13,"nodeType":"NewExpression","endLine":138,"endColumn":46}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use strict';\n\nrequire('../core-upgrade.js');\nconst Promise = require('../lib/utils/promise.js');\nconst ScriptUtils = require('./ScriptUtils.js').ScriptUtils;\nconst processRCForWikis = require('./RCUtils.js').RCUtils.processRCForWikis;\nconst wmfSiteMatrix = require('./data/wmf.sitematrix.json').sitematrix;\n\n// This list of wikis and TOC magicword strings ar derived\n// by processing the language files in\n// $MW_CORE/languages/languages/messages/Messages*.php\nconst tocWikiMap = {\n\t'afwiki': [ '__IO__', '__TOC__' ],\n\t'arwiki': [ '__فهرس__', '__TOC__' ],\n\t'arzwiki': [ '__فهرس__', '__TOC__' ],\n\t'be_taraskwiki': [ '__ЗЬМЕСТ__', '__TOC__' ],\n\t'bgwiki': [ '__СЪДЪРЖАНИЕ__', '__TOC__' ],\n\t'bnwiki': [ '__বিষয়বস্তুর_ছক__', '__বিষয়বস্তুরছক__', '__বিষয়বস্তুর_টেবিল__', '__বিষয়বস্তুরটেবিল__', '__TOC__' ],\n\t'bswiki': [ '__SADRŽAJ__', '__TOC__' ],\n\t'cawiki': [ '__TAULA__', '__RESUM__', '__TDM__', '__TOC__' ],\n\t'cewiki': [ '__ЧУЛАЦАМ__', '__ЧУЛ__', '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'cswiki': [ '__OBSAH__', '__TOC__' ],\n\t'dewiki': [ '__INHALTSVERZEICHNIS__', '__TOC__' ],\n\t'diqwiki': [ '__ESTEN__', '__TOC__' ],\n\t'elwiki': [ '__ΠΠ__', '__ΠΙΝΑΚΑΣΠΕΡΙΕΧΟΜΕΝΩΝ__', '__TOC__' ],\n\t'enwiki': [ '__TOC__' ],\n\t'eowiki': [ '__I__', '__T__', '__INDEKSO__', '__TOC__' ],\n\t'eswiki': [ '__TDC__', '__TOC__' ],\n\t'etwiki': [ '__SISUKORD__', '__TOC__' ],\n\t'fawiki': [ '__فهرست__', '__TOC__' ],\n\t'fiwiki': [ '__SISÄLLYSLUETTELO__', '__TOC__' ],\n\t'frwiki': [ '__SOMMAIRE__', '__TDM__', '__TOC__' ],\n\t'frpwiki': [ '__SOMÈRO__', '__TRÂBLA__', '__SOMMAIRE__', '__TDM__', '__TOC__' ],\n\t'gawiki': [ '__CÁ__', '__TOC__' ],\n\t'glwiki': [ '__ÍNDICE__', '__TDC__', '__SUMÁRIO__', '__SUMARIO__', '__TOC__' ],\n\t'hewiki': [ '__תוכן_עניינים__', '__תוכן__', '__TOC__' ],\n\t'hiwiki': [ '__अनुक्रम__', '__विषय_सूची__', '__TOC__' ],\n\t'hrwiki': [ '__SADRŽAJ__', '__TOC__' ],\n\t'huwiki': [ '__TARTALOMJEGYZÉK__', '__TJ__', '__TOC__' ],\n\t'hywiki': [ '__ԲՈՎ__', '__TOC__' ],\n\t'idwiki': [ '__DAFTARISI__', '__DASI__', '__TOC__' ],\n\t'jawiki': [ '__目次__', '__目次__', '__TOC__' ],\n\t'kk_arabwiki': [ '__مازمۇنى__', '__مزمن__', '__МАЗМҰНЫ__', '__МЗМН__', '__TOC__' ],\n\t'kk_cyrlwiki': [ '__МАЗМҰНЫ__', '__МЗМН__', '__TOC__' ],\n\t'kk_latnwiki': [ '__MAZMUNI__', '__MZMN__', '__МАЗМҰНЫ__', '__МЗМН__', '__TOC__' ],\n\t'kmwiki': [ '__មាតិកា__', '__បញ្ជីអត្ថបទ__', '__TOC__' ],\n\t'kowiki': [ '__목차__', '__TOC__' ],\n\t'kshwiki': [ '__ENHALLT__', '__INHALTSVERZEICHNIS__', '__TOC__' ],\n\t'ku_latnwiki': [ '_NAVEROK_', '__TOC__' ],\n\t'ltwiki': [ '__TURINYS__', '__TOC__' ],\n\t'mgwiki': [ '__LAHATRA__', '__LAHAT__', '__SOMMAIRE__', '__TDM__', '__TOC__' ],\n\t'mkwiki': [ '__СОДРЖИНА__', '__TOC__' ],\n\t'mlwiki': [ '__ഉള്ളടക്കം__', '__TOC__' ],\n\t'mrwiki': [ '__अनुक्रमणिका__', '__TOC__' ],\n\t'mtwiki': [ '__WERREJ__', '__TOC__' ],\n\t'mznwiki': [ '__فهرست__', '__TOC__' ],\n\t'nbwiki': [ '__INNHOLDSFORTEGNELSE__', '__TOC__' ],\n\t'nds_nlwiki': [ '__ONDERWARPEN__', '__INHOUD__', '__TOC__' ],\n\t'ndswiki': [ '__INHOLTVERTEKEN__', '__INHALTSVERZEICHNIS__', '__TOC__' ],\n\t'newiki': [ '__अनुक्रम__', '__विषय_सूची__', '__TOC__' ],\n\t'nlwiki': [ '__INHOUD__', '__TOC__' ],\n\t'nnwiki': [ '__INNHALDSLISTE__', '__INNHOLDSLISTE__', '__TOC__' ],\n\t'ocwiki': [ '__TAULA__', '__SOMARI__', '__TDM__', '__TOC__' ],\n\t'oswiki': [ '__СÆРТÆ__', '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'plwiki': [ '__SPIS__', '__TOC__' ],\n\t'pswiki': [ '__نيوليک__', '__TOC__' ],\n\t'pt_brwiki': [ '__TDC__', '__SUMARIO__', '__SUMÁRIO__', '__TOC__' ],\n\t'ptwiki': [ '__TDC__', '__SUMÁRIO__', '__SUMARIO__', '__TOC__' ],\n\t'quwiki': [ '__YUYARINA__', '__TDC__', '__TOC__' ],\n\t'rowiki': [ '__CUPRINS__', '__TOC__' ],\n\t'ruwiki': [ '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'sahwiki': [ '__ИҺИНЭЭҔИТЭ__', '__ИҺН__', '__TOC__' ],\n\t'sawiki': [ '__अनुक्रमणी__', '__विषयसूची__', '__TOC__' ],\n\t'sewiki': [ '__SISDOALLU__', ' __SIS__', '__TOC__' ],\n\t'sh_latnwiki': [ '__SADRŽAJ__', '__TOC__' ],\n\t'skwiki': [ '__OBSAH__', '__TOC__' ],\n\t'slwiki': [ '__POGLAVJE__', '__TOC__' ],\n\t'sqwiki': [ '__TP__', '__TOC__' ],\n\t'sr_ecwiki': [ '__САДРЖАЈ__', '__TOC__' ],\n\t'sr_elwiki': [ '__SADRŽAJ__', '__TOC__' ],\n\t'srnwiki': [ '__INOT__', '__INHOUD__', '__TOC__' ],\n\t'svwiki': [ '__INNEHÅLLSFÖRTECKNING__', '__TOC__' ],\n\t'tewiki': [ '__విషయసూచిక__', '__TOC__' ],\n\t'tg_cyrlwiki': [ '__ФЕҲРИСТ__', '__TOC__' ],\n\t'tlywiki': [ '__MINDƏRİCOT__', '__TOC__' ],\n\t'trwiki': [ '__İÇİNDEKİLER__', '__TOC__' ],\n\t'tt_cyrlwiki': [ '__ЭЧТЕЛЕК__', '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'tt_latnwiki': [ '__ET__', '__TOC__' ],\n\t'tyvwiki': [ '__ДОПЧУ__', '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'ukwiki': [ '__ЗМІСТ__', '__ОГЛАВЛЕНИЕ__', '__ОГЛ__', '__TOC__' ],\n\t'urwiki': [ '__فہرست__', '__TOC__' ],\n\t'uzwiki': [ '__ICHIDAGILARI__', '__ICHIDAGILAR__', '__TOC__' ],\n\t'viwiki': [ '__MỤC_LỤC__', '__MỤCLỤC__', '__TOC__' ],\n\t'yiwiki': [ '__אינהאלט__', '__תוכן_עניינים__', '__תוכן__', '__TOC__' ],\n\t'zh_hanswiki': [ '__目录__', '__TOC__' ],\n\t'zh_hantwiki': [ '__目錄__', '__目录__', '__TOC__' ],\n\t'zhwiki': [  '__TOC__' ],\n};\n\nfunction arrayEquals(a, b) {\n\treturn Array.isArray(a) && Array.isArray(b) && a.length === b.length &&\n\t\ta.every((val, index) => val === b[index]);\n}\n\nconst fetchTOCMWs = async function(wiki) {\n\tconst requestOpts = {\n\t\tmethod: 'GET',\n\t\tfollowRedirect: true,\n\t\turi: wiki.url + '/w/api.php?action=query&meta=siteinfo&siprop=magicwords&format=json&formatversion=2',\n\t\ttimeout: 5000 // 5 sec\n\n\t};\n\tconst resp = await ScriptUtils.retryingHTTPRequest(3, requestOpts);\n\tconst body = JSON.parse(resp[1]);\n\tlet tocMws = null;\n\tbody.query.magicwords.forEach(function(mw) {\n\t\tif (mw.name === 'toc') {\n\t\t\ttocMws = mw.aliases;\n\t\t}\n\t});\n\n\twiki.toc = tocMws || [ \"__TOC__\" ]; // FIXME\n};\n\nconst processDiff = Promise.async(function *(fetchArgs, diffUrl) {\n\t// Fetch the diff\n\tconst requestOpts = {\n\t\tmethod: 'GET',\n\t\tfollowRedirect: true,\n\t\turi: diffUrl,\n\t\ttimeout: 5000 // 5 sec\n\n\t};\n\tconst resp = yield ScriptUtils.retryingHTTPRequest(3, requestOpts);\n\n\t// Check if a new TOC magicword got introduced in the diff\n\tconst tocMWs = fetchArgs.wiki.toc;\n\tconst re = new RegExp(tocMWs.join('|'), 'g');\n\tconst matches = resp[1].match(re);\n\tconst ret = matches && matches.length % 2 === 1;\n\tif (ret) {\n\t\tconsole.log(\"\\nSTRAY TOC added in \" + diffUrl);\n\t} else {\n\t\tprocess.stderr.write(\".\");\n\t}\n\treturn ret;\n});\n\n// Process the site matrix\nconst defaultTocMW = [ '__TOC__' ];\nconst wikis = [];\nObject.keys(wmfSiteMatrix).forEach(function(k) {\n\tconst e = wmfSiteMatrix[k];\n\n\tfor (const j in e.site) {\n\t\tconst w = e.site[j];\n\t\t// skip closed or private wkis\n\t\tif (w.closed === undefined && w.private === undefined ) {\n\t\t\twikis.push({ prefix: w.dbname, url: w.url });\n\t\t}\n\t}\n});\n\n// DiscussionTools\nprocessRCForWikis(\n\twikis,\n\t{\n\t\trcstart: '2023-05-05T00:00:00Z',\n\t\trcend: '2023-05-08T11:59:59Z',\n\t\trctag: 'discussiontools-reply', // discussiontools\n\t\trcnamespace: \"*\" // any namespace\n\t},\n\t{\n\t\tfileSuffix: '_dt_toc',\n\t\tprocessDiff: processDiff,\n\t\tfetchMWs: fetchTOCMWs\n\t}\n);\n\n// VisualEditor\nprocessRCForWikis(\n\twikis,\n\t{\n\t\trcstart: '2023-05-05T00:00:00Z',\n\t\trcend: '2023-05-08T11:59:59Z',\n\t\trctag: 'visualeditor', // visualeditor\n\t\trcnamespace: \"*\" // any namespace\n\t},\n\t{\n\t\tfileSuffix: '_ve_toc',\n\t\tprocessDiff: processDiff,\n\t\tfetchMWs: fetchTOCMWs\n\t}\n);\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/fetch_ve_nowiki_edits.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/gen_visualdiff_titles.js","messages":[{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":12,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":12,"endColumn":17},{"ruleId":"security/detect-non-literal-fs-filename","severity":1,"message":"Found readFileSync from package \"fs\" with non literal argument at index 0","line":31,"column":3,"nodeType":"CallExpression","endLine":31,"endColumn":43}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* ----------------------------------------------------------------------------------------\n * - Run this query in quarry:\n *   select page_title,page_namespace from page where page_is_redirect=0 and mod(page_namespace,2) = 1;\n * - Download those results as a json file in $FILE\n * - Run this as node tools/gen.titles.js $WIKIPREFIX $FILE $COUNT (however many entries you want)\n *   and dump the output in a sql file.\n * - Import to the visual diff server and update the test database.\n * - Run visual diff tests and profit!\n * ---------------------------------------------------------------------------------------- */\nif (process.argv.length < 5) {\n\tconsole.error(\"USAGE: node \" + process.argv[1] + \" <wikiprefix> <file> <count>\");\n\tprocess.exit(1);\n}\n\nconst nsMap = {\n\t\"0\":\"\",\n\t\"2\":\"User\",\n\t\"4\":\"Project\",\n\t\"6\":\"File\",\n\t\"8\":\"MediaWiki\",\n\t\"10\":\"Template\",\n\t\"12\":\"Help\",\n\t\"14\":\"Category\",\n\t\"828\":\"Module\",\n};\nconst wikiPrefix = process.argv[2];\nconst file = process.argv[3];\nconst numEntries = Number(process.argv[4]);\nconst data =\n\tJSON.parse(\n\t\trequire('fs').readFileSync(file, \"utf8\")\n\t)\n\t.rows\n\t.sort(function(a,b) {\n\t\treturn 0.5 - Math.random();\n\t})\n\t.map(function(e) {\n\t\tconst nsId = Number(e[1]);\n\t\tlet ns;\n\t\tif (nsId % 2 === 0) {\n\t\t\tns = nsMap[String(nsId)];\n\t\t\tns = ns ? ns + \":\" : \"\";\n\t\t} else {\n\t\t\tns = nsMap[String(nsId - 1)];\n\t\t\tns = ns + (ns ? \"_\" : \"\") + \"Talk:\";\n\t\t}\n\t\treturn ns + e[0];\n\t})\n\t.slice(0,Number(numEntries))\n\t.map(function(e) {\n\t\treturn `INSERT IGNORE INTO pages(prefix, title) VALUES(\"${ wikiPrefix }\", \"${ e.replace(/\"/g, '\\\\\"') }\");`;\n\t});\nconsole.log(data.join(\"\\n\"));\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/runRtTests.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n'use strict';\n\nrequire('../core-upgrade.js');\n\nconst fs = require('pn/fs');\nconst yargs = require('yargs');\n\nconst Promise = require('../lib/utils/promise.js');\nconst rtTest = require('../bin/roundtrip-test.js');\n\nconst usage = 'Usage: $0 --parsoidURL <url> -f <file>';\nconst opts = yargs\n.usage(usage)\n.options({\n\thelp: {\n\t\tdescription: 'Show this message.',\n\t\t'boolean': true,\n\t\t'default': false,\n\t\talias: 'h',\n\t},\n\tfile: {\n\t\tdescription: 'List of pages to test. (format dbname:Title\\\\n)',\n\t\t'boolean': false,\n\t\talias: 'f',\n\t},\n\toutfile: {\n\t\tdescription: '(OPTIONAL) Output file to store JSON results blob',\n\t\t'boolean': false,\n\t\talias: 'o',\n\t},\n\toutputContentVersion: {\n\t\tdescription: '(OPTIONAL) The acceptable content version.',\n\t\tboolean: false,\n\t},\n\tparsoidURL: {\n\t\tdescription: 'The URL for the Parsoid API',\n\t\tboolean: false,\n\t\tdefault: null,\n\t},\n\tproxyURL: {\n\t\tdescription: '(OPTIONAL) URL (with protocol and port, if any) for the proxy fronting Parsoid',\n\t\tboolean: false,\n\t\tdefault: null,\n\t},\n\t// FIXME: Add an option for the regression url.\n});\n\nPromise.async(function *() {\n\tconst argv = opts.argv;\n\n\tif (argv.help) {\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\n\tif (!argv.f) {\n\t\tconsole.error('Supplying a file is required!\\n');\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\n\tif (!argv.parsoidURL) {\n\t\tconsole.error('Please provide the API URL of a running Parsoid instance.\\n');\n\t\topts.showHelp();\n\t\treturn;\n\t}\n\n\tconst titles = fs.readFileSync(argv.f, 'utf8').trim().split('\\n').map(function(l) {\n\t\tconst ind = l.indexOf(':');\n\t\treturn {\n\t\t\tprefix: l.slice(0, Math.max(0, ind)),\n\t\t\ttitle: l.slice(ind + 1).replace(/ \\|.*$/, ''),\n\t\t};\n\t});\n\n\tconst results = yield Promise.async(function *() {\n\t\t// Do this serially for now.\n\t\tyield Promise.reduce(titles, function(_, t) {\n\t\t\tconst parsoidURLOpts = { baseUrl: argv.parsoidURL };\n\t\t\tif (argv.proxyURL) {\n\t\t\t\tparsoidURLOpts.proxy = { host: argv.proxyURL };\n\t\t\t}\n\t\t\treturn rtTest.runTests(\n\t\t\t\tt.title,\n\t\t\t\t{\n\t\t\t\t\tprefix: t.prefix,\n\t\t\t\t\tparsoidURLOpts: parsoidURLOpts,\n\t\t\t\t\toutputContentVersion: argv.outputContentVersion,\n\t\t\t\t},\n\t\t\t\trtTest.jsonFormat\n\t\t\t).then(function(ret) {\n\t\t\t\tif (ret.output.error) {\n\t\t\t\t\tt.results = { \"html2wt\":{ \"error\":1 },\"selser\":{ \"error\":1 } };\n\t\t\t\t} else {\n\t\t\t\t\tt.results = ret.output.results;\n\t\t\t\t}\n\t\t\t});\n\t\t}, null);\n\t})();\n\tif (argv.o) {\n\t\tfs.writeFileSync(argv.o, JSON.stringify(titles));\n\t} else {\n\t\tconsole.error(JSON.stringify(titles));\n\t}\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tools/sync-parserTests.js","messages":[{"ruleId":"es-x/no-hashbang","severity":1,"message":"ES2023 Hashbang comments are forbidden.","line":1,"column":1,"nodeType":"Shebang","messageId":"forbidden","endLine":1,"endColumn":20},{"ruleId":"n/no-process-exit","severity":1,"message":"Don't use process.exit(); throw an error instead.","line":261,"column":2,"nodeType":"CallExpression","messageId":"noProcessExit","endLine":261,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n\"use strict\";\n\nrequire('../core-upgrade.js');\n\n/**\n * == USAGE ==\n *\n * Script to synchronize parsoid parserTests with parserTests in other repos.\n *\n * Basic use:\n * $PARSOID is the path to a checked out git copy of Parsoid\n * $TARGET_REPO identifies which set of parserTests we're synchronizing.\n *   This should be one of the top-level keys in tests/parserTests.json,\n *   like 'core' or 'Cite'.\n *   The `path` key under that gives the gerrit project name for the repo; see\n *       https://gerrit.wikimedia.org/r/admin/repos/$project\n * $REPO_PATH is the path to a checked out git copy of the repo containing\n *   the parserTest file on your local machine.\n * $BRANCH is a branch name for the patch to $TARGET_REPO (ie, 'ptsync-<date>')\n *\n *   $ cd $PARSOID\n *   $ tools/sync-parserTests.js $TARGET_REPO $REPO_PATH $BRANCH\n *   $ cd $REPO_PATH\n *   $ git rebase --keep-empty master origin/master\n *     ... resolve conflicts, sigh ...\n *   $ php tests/parser/parserTests.php\n *     ... fix any failures by marking tests parsoid-only, etc ...\n *\n *   For extension repos, for every file with parsoid-compatible\n *   flags set, you may need to adjust tests appropriately in some cases\n *   and/or update known failures as below:\n *   $ php tests/parser/parserTests.php <all-enabled-parsoid-mode-flags> --updateKnownFailures --dir $PARSOID/tests/parser\n *\n *   $ git review  (only if the patch is not empty, see below)\n *\n *     ... time passes, eventually your patch is merged to $TARGET_REPO ...\n *\n *   (You might be tempted to skip the following step if the previous\n *   patch was empty, or the merged parser tests file is now identical\n *   to Parsoid's copy and you think \"there is nothing to pull from\n *   core/the extension\".  But don't; see \"Empty Patches\" below. On\n *   the other hand, if you need to sync core and multiple extensions,\n *   it's fine to do the steps above for them all and get them all\n *   merged before continuing.)\n *\n *   $ cd $PARSOID\n *   $ tools/fetch-parserTests.js $TARGET_REPO\n *   $ php bin/parserTests.php --updateKnownFailures\n *\n *   For the core repo, you also need to update integrated mode failures\n *   $ cd $TARGET_REPO\n *   $ php tests/parser/parserTests.php --wt2html --wt2wt --updateKnownFailures --dir $PARSOID/tests/parser\n *\n *   $ git add -u\n *   $ git commit -m \"Sync parserTests with core\"\n *   $ git review\n *\n *   Simple, right?\n *\n * == WHY ==\n *\n * There are two copies of parserTests files.\n *\n * Since Parsoid & core are in different repositories and both Parsoid\n * and the legacy parser are still operational, we need a parserTests\n * file in each repository. They are usually in sync but since folks\n * are hacking both wikitext engines simultaneously, the two copies\n * might be modified independently. So, we need to periodically sync\n * them (which is just a multi-repo rebase).\n *\n * We detect incompatible divergence of the two copies via CI. We run the\n * legacy parser against Parsoid's copy of the test file and test failures\n * indicate a divergence and necessitates a sync. Core also runs Parsoid\n * against core's copy of the test file in certain circumstances (and\n * this uses the version of Parsoid from mediawiki-vendor, which is\n * \"the latest deployed version\" not \"the latest version\").\n *\n * This discussion only touched upon tests/parser/parserTests.txt but\n * all of the same considerations apply to the parser test file for\n * extensions since we have a Parsoid-version and a legacy-parser version\n * of many extensions at this time.  When CI runs tests on extension\n * repositories it runs them through both the legacy parser and\n * Parsoid (but only if you opt-in by adding a 'parsoid-compatible'\n * flag to the parser test file).\n * https://codesearch.wmcloud.org/search/?q=parsoid-compatible&i=nope\n *\n * == THINKING ==\n *\n * The \"thinking\" part of the sync is to look at the patches created and\n * make sure that whatever change was made upstream (as shown in the diff\n * of the sync patch) doesn't require a corresponding change in Parsoid\n * and file a phab task and regenerate the known-differences list if that\n * happens to be the case.\n *\n * == EMPTY PATCHES ==\n *\n * If the patch to core (or to the extension) is empty, it's not\n * necessary to push it to Gerrit. (We use `--keep-empty` in the\n * suggested commands so that the process doesn't seem to \"crash\" in\n * this case, and because it's possible you still need to tweak tests\n * in order to make core happy after the sync.) Don't stop there,\n * though: you obviously don't need to wait for \"time passes,\n * eventually your patch is merged to $REPO_PATH\", but that means you can\n * and should just continue immediately to do the Parsoid side of the\n * sync. Just because core/the extension already had all Parsoid's\n * changes doesn't mean Parsoid has all of core's/the extension's, and\n * you'll need to update the commit hashes in Parsoid as well.\n *\n * In the other direction (when you pushed some Parsoid changes to\n * core/an extension, but the result is then identical to Parsoid's\n * copy of the parser tests), don't be tempted to skip the second half\n * of the sync either, because the Parsoid commit won't actually be\n * empty: it updates the commit hashes and effectively changes the\n * rebase source for the next sync. The sync point is recorded in\n * parserTests.json via the fetch-parserTests.js script. Since the\n * hash in the json file is checked out and rebased, without this\n * update, the next sync from Parsoid will start from an older\n * baseline and introduce pointless merge conflicts to resolve.\n */\n\nconst yargs = require('yargs');\nconst childProcess = require('pn/child_process');\nconst path = require('path');\nconst fs = require('pn/fs');\nconst Promise = require('../lib/utils/promise.js');\nconst testDir = path.join(__dirname, '../tests/');\nconst testFilesPath = path.join(testDir, 'parserTests.json');\nconst testFiles = require(testFilesPath);\n\nconst strip = function(s) {\n\treturn s.replace(/(^\\s+)|(\\s+$)/g, '');\n};\n\nPromise.async(function *() {\n\t// Option parsing and helpful messages.\n\tconst usage = 'Usage: $0  <target-repo-key> <repo-path> <branch>';\n\tconst opts = yargs\n\t.usage(usage)\n\t.help(false)\n\t.options({\n\t\t'help': { description: 'Show this message' },\n\t});\n\tconst argv = opts.argv;\n\tif (argv.help || argv._.length !== 3) {\n\t\topts.showHelp();\n\t\tlet morehelp = yield fs.readFile(__filename, 'utf8');\n\t\tmorehelp = strip(morehelp.split(/== [A-Z]* ==/, 2)[1]);\n\t\tconsole.log(morehelp.replace(/^ {3}/mg, ''));\n\t\treturn;\n\t}\n\n\t// Ok, let's do this thing!\n\tconst targetRepo = argv._[0];\n\tif (!testFiles.hasOwnProperty(targetRepo)) {\n\t\tconsole.warn(targetRepo + ' not defined in parserTests.json');\n\t\treturn;\n\t}\n\n\tif (targetRepo === 'parsoid') {\n\t\tconsole.warn('Nothing to sync for Parsoid-only files.');\n\t\treturn;\n\t}\n\n\tconst mwexec = function(cmd) {\n\t\t// Execute `cmd` in the mwpath directory.\n\t\treturn new Promise(function(resolve, reject) {\n\t\t\tconsole.log('>>>', cmd.join(' '));\n\t\t\tchildProcess.spawn(cmd[0], cmd.slice(1), {\n\t\t\t\tcwd: mwpath,\n\t\t\t\tenv: process.env,\n\t\t\t\tstdio: 'inherit',\n\t\t\t}).on('close', function(code) {\n\t\t\t\tif (code === 0) {\n\t\t\t\t\tresolve(code);\n\t\t\t\t} else {\n\t\t\t\t\treject(code);\n\t\t\t\t}\n\t\t\t}).on('error', reject);\n\t\t});\n\t};\n\n\tlet phash = null;\n\tlet firstTarget = true;\n\tconst mwpath = path.resolve(argv._[1]);\n\tconst repoInfo = testFiles[targetRepo];\n\tconst oldCommitHash = repoInfo.latestCommit;\n\tconst targets = repoInfo.targets;\n\tconst branch = argv._[2];\n\tconst changedFiles = [];\n\tfor (const targetName in targets) {\n\t\tconsole.log(\"Processing \" + targetName);\n\n\t\t// A bit of user-friendly logging.\n\t\tif (firstTarget) {\n\t\t\t// Fetch current Parsoid git hash.\n\t\t\tconst result = yield childProcess.execFile(\n\t\t\t\t'git', ['log', '--max-count=1', '--pretty=format:%H'], {\n\t\t\t\t\tcwd: __dirname,\n\t\t\t\t\tenv: process.env,\n\t\t\t\t}).promise;\n\t\t\tphash = strip(result.stdout);\n\t\t\tconsole.log('Parsoid git HEAD is', phash);\n\t\t\tconsole.log('>>> cd', mwpath);\n\t\t\tyield mwexec('git fetch origin'.split(' '));\n\n\t\t\t// Create/checkout a branch, based on the previous sync point.\n\t\t\tyield mwexec(['git', 'checkout', '-b', branch, oldCommitHash]);\n\t\t}\n\n\t\t// Copy our locally-modified parser tests over to the target repo\n\t\tconst targetInfo = targets[targetName];\n\t\tconst parsoidFile = path.join(__dirname, '..', 'tests', 'parser', targetName);\n\t\tconst targetFile = path.join(mwpath, targetInfo.path);\n\t\t// Support file renaming!\n\t\tif (targetInfo.oldPath) {\n\t\t\tconst targetOldFile = path.join(mwpath, targetInfo.oldPath);\n\t\t\t// If this exists, do a git-mv to the new path before copying\n\t\t\t// the Parsoid file over.\n\t\t\tif (yield fs.exists(targetOldFile)) {\n\t\t\t\tyield mwexec(['git', 'mv', targetInfo.oldPath, targetInfo.path]);\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\tconst data = yield fs.readFile(parsoidFile);\n\t\t\tconsole.log('>>>', 'cp', parsoidFile, targetFile);\n\t\t\tyield fs.writeFile(targetFile, data);\n\t\t\tchangedFiles.push(targetInfo.path);\n\t\t\tfirstTarget = false;\n\t\t} catch (e) {\n\t\t\t// cleanup\n\t\t\tyield mwexec('git checkout master'.split(' '));\n\t\t\tyield mwexec(['git', 'branch', '-d', branch]);\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t// Make a new commit with an appropriate message.\n\tlet commitmsg = 'Sync up ' + targetRepo + ' repo with Parsoid';\n\tcommitmsg += '\\n\\nThis now aligns with Parsoid commit ' + phash;\n\t// Note the --allow-empty, because sometimes there are no parsoid-side\n\t// changes to merge. (We just need to get changes from upstream.)\n\tyield mwexec(['git', 'add'].concat(changedFiles));\n\tyield mwexec(['git', 'commit', '-m', commitmsg, '--allow-empty']);\n\n\t// ok, we were successful at making the commit.  Give further instructions.\n\tconsole.log();\n\tconsole.log('Success!  Now:');\n\tconsole.log(' cd', mwpath);\n\tconsole.log(' git rebase --keep-empty origin/master');\n\tconsole.log(' .. fix any conflicts .. ');\n\tconsole.log(' php tests/parser/parserTests.php');\n\tconsole.log(' git review');\n\n\t// XXX to rebase semi-automatically, we might do something like:\n\t//  yield mwexec('git rebase origin/master'.split(' '));\n\t// XXX but it seems rather confusing to do it this way, since the\n\t// current working directory when we finish is still parsoid.\n\n\tprocess.exit(0);\n})().done();\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}]

--- end ---
$ /usr/bin/npm ci
--- stderr ---
npm WARN skipping integrity check for git dependency ssh://git@github.com/arlolra/mocha.git 
npm WARN deprecated har-validator@5.1.3: this library is no longer supported
npm WARN deprecated formidable@1.2.2: Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
npm WARN deprecated superagent@5.1.0: Please upgrade to v7.0.2+ of superagent.  We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing.  See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>.
npm WARN deprecated core-js@2.6.11: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
--- stdout ---

added 408 packages, and audited 409 packages in 7s

85 packages are looking for funding
  run `npm fund` for details

4 vulnerabilities (2 moderate, 2 high)

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.

--- end ---
$ /usr/bin/npm test
--- stdout ---

> parsoid@0.11.0 test
> npm run eslint


> parsoid@0.11.0 eslint
> eslint --cache --ext .js,.json .


/src/repo/bin/benchmark.js
   84:4   warning  Don't use process.exit(); throw an error instead                            n/no-process-exit
  132:9   warning  Found existsSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  132:35  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  190:4   warning  Found writeFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  252:32  warning  Found existsSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  269:26  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  285:15  warning  Found readFileSync from package "fs" with non literal argument at index 0   security/detect-non-literal-fs-filename
  353:4   warning  Don't use process.exit(); throw an error instead                            n/no-process-exit

/src/repo/bin/diff.html.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                     es-x/no-hashbang
   34:22  warning  Unsafe Regular Expression                                                  security/detect-unsafe-regex
  150:16  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  151:17  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  177:27  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/bin/domdiff.test.js
   1:1   warning  ES2023 Hashbang comments are forbidden                             es-x/no-hashbang
   7:23  warning  Can't resolve '../lib/html2wt/DOMDiff.js' in '/src/repo/bin'       n/no-missing-require
  10:29  warning  Can't resolve '../lib/logger/ParsoidLogger.js' in '/src/repo/bin'  n/no-missing-require
  88:2   warning  Don't use process.exit(); throw an error instead                   n/no-process-exit

/src/repo/bin/inspectTokenizer.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                          es-x/no-hashbang
   70:10  warning  Found createWriteStream from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
   77:22  warning  Found readFileSync from package "fs" with non literal argument at index 0       security/detect-non-literal-fs-filename
  218:2   warning  Don't use process.exit(); throw an error instead                                n/no-process-exit

/src/repo/bin/langconv-test.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                   es-x/no-hashbang
   12:51  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'               n/no-missing-require
   17:41  warning  Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/bin'  n/no-missing-require
   20:37  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/bin'               n/no-missing-require
  405:5   warning  Found non-literal argument in require                                    security/detect-non-literal-require
  625:4   warning  Don't use process.exit(); throw an error instead                         n/no-process-exit

/src/repo/bin/normalize.test.js
   1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  64:2  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/bin/roundtrip-test.js
    1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  996:4  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/lib/config/ParsoidConfig.js
  157:19  warning  Found non-literal argument in require                                     security/detect-non-literal-require
  630:8   warning  Found statSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  636:14  warning  Found readdirSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  643:9   warning  Found statSync from package "fs" with non literal argument at index 0     security/detect-non-literal-fs-filename
  656:12  warning  Found non-literal argument in require                                     security/detect-non-literal-require

/src/repo/lib/html2wt/DOMNormalizer.js
  284:17  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  372:44  warning  Unsafe Regular Expression                         security/detect-unsafe-regex

/src/repo/lib/html2wt/WTSUtils.js
  13:20  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/Diff.js
  142:24  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/TokenUtils.js
  26:10  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/Util.js
  159:10  warning  Unsafe Regular Expression  security/detect-unsafe-regex
  456:25  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/WTUtils.js
  137:37  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/lib/utils/jsutils.js
  280:10  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tests/TestUtils.js
  150:12  warning  Unsafe Regular Expression  security/detect-unsafe-regex

/src/repo/tests/api-testing/Parsoid.js
  668:26  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp
  680:26  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tests/testreduce/rtTestWrapper.js
  13:9  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/ScriptUtils.js
  57:4  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/tools/build-langconv-fst.js
    1:1   warning  ES2023 Hashbang comments are forbidden                                 es-x/no-hashbang
   84:21  warning  Can't resolve '../lib/language/FST.js' in '/src/repo/tools'            n/no-missing-require
  109:13  warning  Found openSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename
  302:14  warning  Found openSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/compare.linter.results.js
    1:1   warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  105:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit
  108:18  warning  Found non-literal argument in require             security/detect-non-literal-require
  112:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit
  115:18  warning  Found non-literal argument in require             security/detect-non-literal-require
  118:2   warning  Don't use process.exit(); throw an error instead  n/no-process-exit

/src/repo/tools/fetch-parserTests.js
  1:1  warning  ES2023 Hashbang comments are forbidden  es-x/no-hashbang

/src/repo/tools/fetch-revision-data.js
   1:1   warning  ES2023 Hashbang comments are forbidden                                     es-x/no-hashbang
  20:31  warning  Can't resolve '../lib/mw/ApiRequest.js' in '/src/repo/tools'               n/no-missing-require
  22:35  warning  Can't resolve '../lib/config/MWParserEnvironment.js' in '/src/repo/tools'  n/no-missing-require

/src/repo/tools/fetch_stray_toc_edits.js
  138:13  warning  Found non-literal argument to RegExp Constructor  security/detect-non-literal-regexp

/src/repo/tools/gen_visualdiff_titles.js
  12:2  warning  Don't use process.exit(); throw an error instead                           n/no-process-exit
  31:3  warning  Found readFileSync from package "fs" with non literal argument at index 0  security/detect-non-literal-fs-filename

/src/repo/tools/runRtTests.js
  1:1  warning  ES2023 Hashbang comments are forbidden  es-x/no-hashbang

/src/repo/tools/sync-parserTests.js
    1:1  warning  ES2023 Hashbang comments are forbidden            es-x/no-hashbang
  261:2  warning  Don't use process.exit(); throw an error instead  n/no-process-exit

✖ 70 problems (0 errors, 70 warnings)


--- end ---
Upgrading c:php-parallel-lint/php-parallel-lint from 1.3.2 -> 1.4.0
Upgrading c:mediawiki/minus-x from 1.1.1 -> 1.1.3
$ /usr/bin/composer update
--- stderr ---
Loading composer repositories with package information
Updating dependencies
Lock file operations: 0 installs, 4 updates, 1 removal
  - Removing symfony/polyfill-php73 (v1.30.0)
  - Upgrading mediawiki/minus-x (1.1.1 => 1.1.3)
  - Upgrading php-parallel-lint/php-parallel-lint (v1.3.2 => v1.4.0)
  - Upgrading symfony/console (v5.4.41 => v6.4.9)
  - Upgrading symfony/string (v6.4.9 => v7.1.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 0 installs, 4 updates, 1 removal
    0 [>---------------------------]    0 [->--------------------------]
  - Removing symfony/polyfill-php73 (v1.30.0)
  - Upgrading symfony/string (v6.4.9 => v7.1.2): Extracting archive
  - Upgrading symfony/console (v5.4.41 => v6.4.9): Extracting archive
  - Upgrading mediawiki/minus-x (1.1.1 => 1.1.3): Extracting archive
  - Upgrading php-parallel-lint/php-parallel-lint (v1.3.2 => v1.4.0): Extracting archive
 0/4 [>---------------------------]   0%
 4/4 [============================] 100%
Generating optimized autoload files
43 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found
--- stdout ---

--- end ---
$ /usr/bin/composer install
--- stderr ---
Installing dependencies from lock file (including require-dev)
Verifying lock file contents can be installed on current platform.
Nothing to install, update or remove
Generating optimized autoload files
43 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---

--- end ---
$ /usr/bin/composer test
--- stderr ---
> parallel-lint . --exclude vendor --exclude node_modules
> phpcs -p -s
> minus-x check .
> covers-validator
> bash bin/nodenamecheck.sh

# Ensure lint output (exactly 1 line) is present!
x=`git grep "$grepPhrase" $BIN/.. | wc -l`
if [ ! $x -eq 1 ]
then
    echo "Uses of $badWords found! :("
    git grep "$grepPhrase" $BIN/..
    exit -1
fi
echo "No new uses of $badWords found! :)"
> bash bin/getattributecheck.sh

# exclude DOMCompat
FILES=$(git grep -l '[-]>')

# Ensure exactly 1 line is present (in DOMCompat::getAttribute itself)
x=`git grep "$grepPhrase" $FILES | wc -l`
if [ ! $x -eq 1 ]
then
    echo "Uses of $badWords found! :("
    git grep -P "$grepPhrase" $FILES
    exit -1
fi
echo "No new uses of $badWords found! :)"
> phan -p --allow-polyfill-parser --config-file=.phan/standalone.php --long-progress-bar
Parsing files...
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░   54 / 2620 (  2%) 45MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  108 / 2620 (  4%) 59MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  162 / 2620 (  7%) 81MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  216 / 2620 (  8%) 95MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  270 / 2620 ( 11%) 121MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  324 / 2620 ( 13%) 139MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  378 / 2620 ( 14%) 149MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  432 / 2620 ( 16%) 158MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  486 / 2620 ( 20%) 177MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  540 / 2620 ( 23%) 184MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  594 / 2620 ( 23%) 184MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  648 / 2620 ( 27%) 191MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  702 / 2620 ( 27%) 191MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  756 / 2620 ( 31%) 209MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  810 / 2620 ( 31%) 209MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  864 / 2620 ( 36%) 240MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  918 / 2620 ( 36%) 240MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  972 / 2620 ( 37%) 248MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1026 / 2620 ( 40%) 257MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1080 / 2620 ( 44%) 267MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1134 / 2620 ( 44%) 267MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1188 / 2620 ( 47%) 278MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1242 / 2620 ( 50%) 285MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1296 / 2620 ( 50%) 285MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1350 / 2620 ( 53%) 295MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1404 / 2620 ( 53%) 295MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1458 / 2620 ( 55%) 306MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1512 / 2620 ( 59%) 314MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1566 / 2620 ( 62%) 323MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1620 / 2620 ( 62%) 323MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1674 / 2620 ( 65%) 334MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1728 / 2620 ( 65%) 334MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1782 / 2620 ( 68%) 345MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1836 / 2620 ( 70%) 353MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1890 / 2620 ( 72%) 363MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1944 / 2620 ( 75%) 376MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1998 / 2620 ( 77%) 380MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2052 / 2620 ( 78%) 385MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2106 / 2620 ( 80%) 398MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2160 / 2620 ( 82%) 431MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2214 / 2620 ( 87%) 452MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2268 / 2620 ( 87%) 452MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2322 / 2620 ( 91%) 457MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2376 / 2620 ( 91%) 457MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2430 / 2620 ( 95%) 462MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2484 / 2620 ( 95%) 462MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2538 / 2620 ( 97%) 483MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2592 / 2620 ( 99%) 495MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░                           2620 / 2620 (100%) 499MB
Analyzing classes...
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 587MB
Analyzing functions...
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 587MB
Analyzing methods...
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 604MB
Analyzing files...
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░  54 / 275 ( 20%) 678MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 108 / 275 ( 39%) 690MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 162 / 275 ( 59%) 700MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 216 / 275 ( 78%) 718MB
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 270 / 275 ( 98%) 753MB
░░░░░                                                  275 / 275 (100%) 753MB

> phpunit
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:16
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: tbody s:2 ; cs:7
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:23
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:11
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:32
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:28
[info/dsr/inconsistent] DSR inconsistency: cs/s mismatch for node: table s:2 ; cs:23
Script phpunit handling the phpunit event returned with error code 1
Script @phpunit was called via test
--- stdout ---
PHP 8.2.7 | 10 parallel jobs
............................................................  60/368 ( 16%)
............................................................ 120/368 ( 32%)
............................................................ 180/368 ( 48%)
............................................................ 240/368 ( 65%)
............................................................ 300/368 ( 81%)
............................................................ 360/368 ( 97%)
........                                                     368/368 (100%)


Checked 368 files in 1.5 seconds
No syntax error found
............................................................ 60 / 61 (98%)
.                                                            61 / 61 (100%)


Time: 6.44 secs; Memory: 8MB

MinusX
======
Processing /src/repo...
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
.............................................................
......................................................
All good!
CoversValidator 1.6.0

Validation complete. All @covers tags are valid.
No new uses of $node->nodeName found! :)
No new uses of $node->getAttribute( found! :)
PHPUnit 9.6.16 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.7
Configuration: /src/repo/phpunit.xml.dist

.............................................................   61 / 9191 (  0%)
.............................................................  122 / 9191 (  1%)
.............................................................  183 / 9191 (  1%)
.............................................................  244 / 9191 (  2%)
.............................................................  305 / 9191 (  3%)
.............................................................  366 / 9191 (  3%)
.........................................F...................  427 / 9191 (  4%)
................................F............................  488 / 9191 (  5%)
.............................................................  549 / 9191 (  5%)
.............................................................  610 / 9191 (  6%)
.............................................................  671 / 9191 (  7%)
.............................................................  732 / 9191 (  7%)
.............................................................  793 / 9191 (  8%)
.............................................................  854 / 9191 (  9%)
.............................................................  915 / 9191 (  9%)
.............................................................  976 / 9191 ( 10%)
............................................................. 1037 / 9191 ( 11%)
............................................................. 1098 / 9191 ( 11%)
............................................................. 1159 / 9191 ( 12%)
............................................................. 1220 / 9191 ( 13%)
............................................................. 1281 / 9191 ( 13%)
............................................................. 1342 / 9191 ( 14%)
............................................................. 1403 / 9191 ( 15%)
............................................................. 1464 / 9191 ( 15%)
............................................................. 1525 / 9191 ( 16%)
............................................................. 1586 / 9191 ( 17%)
............................................................. 1647 / 9191 ( 17%)
............................................................. 1708 / 9191 ( 18%)
............................................................. 1769 / 9191 ( 19%)
............................................................. 1830 / 9191 ( 19%)
............................................................. 1891 / 9191 ( 20%)
............................................................. 1952 / 9191 ( 21%)
............................................................. 2013 / 9191 ( 21%)
............................................................. 2074 / 9191 ( 22%)
............................................................. 2135 / 9191 ( 23%)
............................................................. 2196 / 9191 ( 23%)
............................................................. 2257 / 9191 ( 24%)
............................................................. 2318 / 9191 ( 25%)
............................................................. 2379 / 9191 ( 25%)
............................................................. 2440 / 9191 ( 26%)
............................................................. 2501 / 9191 ( 27%)
............................................................. 2562 / 9191 ( 27%)
............................................................. 2623 / 9191 ( 28%)
............................................................. 2684 / 9191 ( 29%)
............................................................. 2745 / 9191 ( 29%)
............................................................. 2806 / 9191 ( 30%)
............................................................. 2867 / 9191 ( 31%)
............................................................. 2928 / 9191 ( 31%)
............................................................. 2989 / 9191 ( 32%)
............................................................. 3050 / 9191 ( 33%)
............................................................. 3111 / 9191 ( 33%)
............................................................. 3172 / 9191 ( 34%)
............................................................. 3233 / 9191 ( 35%)
............................................................. 3294 / 9191 ( 35%)
............................................................. 3355 / 9191 ( 36%)
............................................................. 3416 / 9191 ( 37%)
............................................................. 3477 / 9191 ( 37%)
............................................................. 3538 / 9191 ( 38%)
............................................................. 3599 / 9191 ( 39%)
............................................................. 3660 / 9191 ( 39%)
............................................................. 3721 / 9191 ( 40%)
............................................................. 3782 / 9191 ( 41%)
............................................................. 3843 / 9191 ( 41%)
............................................................. 3904 / 9191 ( 42%)
............................................................. 3965 / 9191 ( 43%)
............................................................. 4026 / 9191 ( 43%)
............................................................. 4087 / 9191 ( 44%)
............................................................. 4148 / 9191 ( 45%)
............................................................. 4209 / 9191 ( 45%)
............................................................. 4270 / 9191 ( 46%)
............................................................. 4331 / 9191 ( 47%)
............................................................. 4392 / 9191 ( 47%)
............................................................. 4453 / 9191 ( 48%)
............................................................. 4514 / 9191 ( 49%)
............................................................. 4575 / 9191 ( 49%)
............................................................. 4636 / 9191 ( 50%)
............................................................. 4697 / 9191 ( 51%)
............................................................. 4758 / 9191 ( 51%)
............................................................. 4819 / 9191 ( 52%)
............................................................. 4880 / 9191 ( 53%)
............................................................. 4941 / 9191 ( 53%)
............................................................. 5002 / 9191 ( 54%)
............................................................. 5063 / 9191 ( 55%)
............................................................. 5124 / 9191 ( 55%)
............................................................. 5185 / 9191 ( 56%)
............................................................. 5246 / 9191 ( 57%)
............................................................. 5307 / 9191 ( 57%)
............................................................. 5368 / 9191 ( 58%)
............................................................. 5429 / 9191 ( 59%)
............................................................. 5490 / 9191 ( 59%)
............................................................. 5551 / 9191 ( 60%)
............................................................. 5612 / 9191 ( 61%)
............................................................. 5673 / 9191 ( 61%)
............................................................. 5734 / 9191 ( 62%)
............................................................. 5795 / 9191 ( 63%)
............................................................. 5856 / 9191 ( 63%)
............................................................. 5917 / 9191 ( 64%)
............................................................. 5978 / 9191 ( 65%)
............................................................. 6039 / 9191 ( 65%)
............................................................. 6100 / 9191 ( 66%)
............................................................. 6161 / 9191 ( 67%)
............................................................. 6222 / 9191 ( 67%)
............................................................. 6283 / 9191 ( 68%)
............................................................. 6344 / 9191 ( 69%)
............................................................. 6405 / 9191 ( 69%)
............................................................. 6466 / 9191 ( 70%)
............................................................. 6527 / 9191 ( 71%)
............................................................. 6588 / 9191 ( 71%)
............................................................. 6649 / 9191 ( 72%)
............................................................. 6710 / 9191 ( 73%)
............................................................. 6771 / 9191 ( 73%)
............................................................. 6832 / 9191 ( 74%)
............................................................. 6893 / 9191 ( 74%)
............................................................. 6954 / 9191 ( 75%)
............................................................. 7015 / 9191 ( 76%)
............................................................. 7076 / 9191 ( 76%)
............................................................. 7137 / 9191 ( 77%)
............................................................. 7198 / 9191 ( 78%)
............................................................. 7259 / 9191 ( 78%)
............................................................. 7320 / 9191 ( 79%)
............................................................. 7381 / 9191 ( 80%)
............................................................. 7442 / 9191 ( 80%)
............................................................. 7503 / 9191 ( 81%)
............................................................. 7564 / 9191 ( 82%)
............................................................. 7625 / 9191 ( 82%)
............................................................. 7686 / 9191 ( 83%)
............................................................. 7747 / 9191 ( 84%)
............................................................. 7808 / 9191 ( 84%)
............................................................. 7869 / 9191 ( 85%)
............................................................. 7930 / 9191 ( 86%)
............................................................. 7991 / 9191 ( 86%)
............................................................. 8052 / 9191 ( 87%)
............................................................. 8113 / 9191 ( 88%)
............................................................. 8174 / 9191 ( 88%)
............................................................. 8235 / 9191 ( 89%)
............................................................. 8296 / 9191 ( 90%)
............................................................. 8357 / 9191 ( 90%)
............................................................. 8418 / 9191 ( 91%)
............................................................. 8479 / 9191 ( 92%)
............................................................. 8540 / 9191 ( 92%)
............................................................. 8601 / 9191 ( 93%)
............................................................. 8662 / 9191 ( 94%)
............................................................. 8723 / 9191 ( 94%)
............................................................. 8784 / 9191 ( 95%)
............................................................. 8845 / 9191 ( 96%)
............................................................. 8906 / 9191 ( 96%)
............................................................. 8967 / 9191 ( 97%)
............................................................. 9028 / 9191 ( 98%)
............................................................. 9089 / 9191 ( 98%)
............................................................. 9150 / 9191 ( 99%)
.........................................                     9191 / 9191 (100%)

Time: 00:02.890, Memory: 126.42 MB

There were 2 failures:

1) Test\Parsoid\Utils\DOMCompatTest::testGetElementById
Failed asserting that Wikimedia\Parsoid\DOM\Element Object &00000000000070320000000000000000 (
    'schemaTypeInfo' => null
) is null.

/src/repo/tests/phpunit/Parsoid/Utils/DOMCompatTest.php:155

2) Test\Parsoid\Utils\DOMCompatTest::testRemove
Failed asserting that Wikimedia\Parsoid\DOM\Element Object &0000000000006eb90000000000000000 (
    'schemaTypeInfo' => null
) is null.

/src/repo/tests/phpunit/Parsoid/Utils/DOMCompatTest.php:480

FAILURES!
Tests: 9191, Assertions: 13151, Failures: 2.

--- end ---
Traceback (most recent call last):
  File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1789, in main
    libup.run(args.repo, args.output, args.branch)
  File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 1729, in run
    self.composer_upgrade(plan)
  File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 978, in composer_upgrade
    self.composer_test()
  File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/__init__.py", line 335, in composer_test
    self.check_call(["composer", "test"])
  File "/venv/lib/python3.11/site-packages/runner-0.1.0-py3.11.egg/runner/shell2.py", line 59, in check_call
    res.check_returncode()
  File "/usr/lib/python3.11/subprocess.py", line 502, in check_returncode
    raise CalledProcessError(self.returncode, self.args, self.stdout,
subprocess.CalledProcessError: Command '['/usr/bin/composer', 'test']' returned non-zero exit status 1.
Source code is licensed under the AGPL.