mediawiki/libs/LangConv: main (log #2230527)

sourcepatches

This run took 58 seconds.

From 4186c6f5e37453f2f941c91f1d0413965d72279c Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Wed, 26 Nov 2025 04:21:31 +0000
Subject: [PATCH] build: Updating eslint-config-wikimedia to 0.32.2

Change-Id: I08855581b8bb617a368a1dc27533ee681b8fc476
---
 package-lock.json | 14 +++++++-------
 package.json      |  2 +-
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f8e6138..0d4ecd9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
 				"babybird": "^0.0.1",
 				"chai": "^4.2.0",
 				"domino": "^2.1.6",
-				"eslint-config-wikimedia": "0.32.1",
+				"eslint-config-wikimedia": "0.32.2",
 				"jsdoc": "4.0.5",
 				"jsdoc-wmf-theme": "1.1.0",
 				"mocha": "11.1.0",
@@ -1955,9 +1955,9 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.32.1",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
-			"integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+			"version": "0.32.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+			"integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
 			"dev": true,
 			"dependencies": {
 				"@stylistic/eslint-plugin": "^3.1.0",
@@ -6890,9 +6890,9 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.32.1",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
-			"integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+			"version": "0.32.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+			"integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
 			"dev": true,
 			"requires": {
 				"@stylistic/eslint-plugin": "^3.1.0",
diff --git a/package.json b/package.json
index 6dd45d0..ec8ac12 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
 		"babybird": "^0.0.1",
 		"chai": "^4.2.0",
 		"domino": "^2.1.6",
-		"eslint-config-wikimedia": "0.32.1",
+		"eslint-config-wikimedia": "0.32.2",
 		"jsdoc": "4.0.5",
 		"jsdoc-wmf-theme": "1.1.0",
 		"mocha": "11.1.0",
-- 
2.47.3

$ date
--- stdout ---
Wed Nov 26 04:20:52 UTC 2025

--- end ---
$ git clone file:///srv/git/mediawiki-libs-LangConv.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
Updating files:  71% (125/175)
Updating files:  72% (126/175)
Updating files:  73% (128/175)
Updating files:  74% (130/175)
Updating files:  75% (132/175)
Updating files:  76% (133/175)
Updating files:  77% (135/175)
Updating files:  78% (137/175)
Updating files:  79% (139/175)
Updating files:  80% (140/175)
Updating files:  81% (142/175)
Updating files:  82% (144/175)
Updating files:  83% (146/175)
Updating files:  84% (147/175)
Updating files:  85% (149/175)
Updating files:  86% (151/175)
Updating files:  87% (153/175)
Updating files:  88% (154/175)
Updating files:  89% (156/175)
Updating files:  90% (158/175)
Updating files:  91% (160/175)
Updating files:  92% (161/175)
Updating files:  93% (163/175)
Updating files:  94% (165/175)
Updating files:  95% (167/175)
Updating files:  96% (168/175)
Updating files:  97% (170/175)
Updating files:  98% (172/175)
Updating files:  99% (174/175)
Updating files: 100% (175/175)
Updating files: 100% (175/175), done.
--- 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 ---
7d6923d8ca3bad4ee7562ba4d1ad617d210990d9 refs/heads/master

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {},
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 0,
      "high": 0,
      "critical": 0,
      "total": 0
    },
    "dependencies": {
      "prod": 1,
      "dev": 448,
      "optional": 3,
      "peer": 1,
      "peerOptional": 0,
      "total": 448
    }
  }
}

--- 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: 67 installs, 0 updates, 0 removals
  - Locking composer/pcre (3.3.2)
  - Locking composer/semver (3.4.4)
  - Locking composer/spdx-licenses (1.5.9)
  - Locking composer/xdebug-handler (3.0.5)
  - Locking dealerdirect/phpcodesniffer-composer-installer (v1.2.0)
  - Locking doctrine/deprecations (1.1.5)
  - Locking felixfbecker/advanced-json-rpc (v3.2.1)
  - Locking mediawiki/mediawiki-codesniffer (v48.0.0)
  - Locking mediawiki/mediawiki-phan-config (0.17.0)
  - Locking mediawiki/minus-x (1.1.3)
  - Locking mediawiki/phan-taint-check-plugin (7.0.0)
  - Locking microsoft/tolerant-php-parser (v0.1.2)
  - Locking myclabs/deep-copy (1.13.4)
  - Locking netresearch/jsonmapper (v4.5.0)
  - Locking nikic/php-parser (v5.6.2)
  - Locking ockcyp/covers-validator (v1.7.0)
  - Locking phan/phan (5.5.1)
  - 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.4.0)
  - Locking phpcsstandards/phpcsextra (1.4.0)
  - Locking phpcsstandards/phpcsutils (1.1.1)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.6.4)
  - Locking phpdocumentor/type-resolver (1.12.0)
  - Locking phpstan/phpdoc-parser (2.3.0)
  - Locking phpunit/php-code-coverage (10.1.16)
  - Locking phpunit/php-file-iterator (4.1.0)
  - Locking phpunit/php-invoker (4.0.0)
  - Locking phpunit/php-text-template (3.0.1)
  - Locking phpunit/php-timer (6.0.0)
  - Locking phpunit/phpunit (10.5.58)
  - Locking psr/container (2.0.2)
  - Locking psr/log (3.0.2)
  - Locking sabre/event (5.1.7)
  - Locking sebastian/cli-parser (2.0.1)
  - Locking sebastian/code-unit (2.0.0)
  - Locking sebastian/code-unit-reverse-lookup (3.0.0)
  - Locking sebastian/comparator (5.0.4)
  - Locking sebastian/complexity (3.2.0)
  - Locking sebastian/diff (5.1.1)
  - Locking sebastian/environment (6.1.0)
  - Locking sebastian/exporter (5.1.4)
  - Locking sebastian/global-state (6.0.2)
  - Locking sebastian/lines-of-code (2.0.2)
  - Locking sebastian/object-enumerator (5.0.0)
  - Locking sebastian/object-reflector (3.0.0)
  - Locking sebastian/recursion-context (5.0.1)
  - Locking sebastian/type (4.0.0)
  - Locking sebastian/version (4.0.1)
  - Locking squizlabs/php_codesniffer (3.13.2)
  - Locking symfony/console (v6.4.27)
  - Locking symfony/deprecation-contracts (v3.6.0)
  - Locking symfony/polyfill-ctype (v1.33.0)
  - Locking symfony/polyfill-intl-grapheme (v1.33.0)
  - Locking symfony/polyfill-intl-normalizer (v1.33.0)
  - Locking symfony/polyfill-mbstring (v1.33.0)
  - Locking symfony/polyfill-php80 (v1.33.0)
  - Locking symfony/service-contracts (v3.6.1)
  - Locking symfony/string (v7.3.4)
  - Locking theseer/tokenizer (1.3.1)
  - Locking tysonandre/var_representation_polyfill (0.1.3)
  - Locking webmozart/assert (1.12.1)
  - Locking wikimedia/assert (v0.5.1)
  - Locking wikimedia/update-history (1.0.2)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 67 installs, 0 updates, 0 removals
    0 [>---------------------------]    0 [->--------------------------]
  - Installing squizlabs/php_codesniffer (3.13.2): Extracting archive
  - Installing dealerdirect/phpcodesniffer-composer-installer (v1.2.0): Extracting archive
  - Installing composer/pcre (3.3.2): Extracting archive
  - Installing phpcsstandards/phpcsutils (1.1.1): Extracting archive
  - Installing phpcsstandards/phpcsextra (1.4.0): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive
  - Installing composer/spdx-licenses (1.5.9): Extracting archive
  - Installing composer/semver (3.4.4): Extracting archive
  - Installing mediawiki/mediawiki-codesniffer (v48.0.0): Extracting archive
  - Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
  - Installing symfony/polyfill-php80 (v1.33.0): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.33.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.33.0): Extracting archive
  - Installing symfony/polyfill-ctype (v1.33.0): Extracting archive
  - Installing symfony/string (v7.3.4): Extracting archive
  - Installing symfony/deprecation-contracts (v3.6.0): Extracting archive
  - Installing psr/container (2.0.2): Extracting archive
  - Installing symfony/service-contracts (v3.6.1): Extracting archive
  - Installing symfony/console (v6.4.27): Extracting archive
  - Installing sabre/event (5.1.7): Extracting archive
  - Installing netresearch/jsonmapper (v4.5.0): Extracting archive
  - Installing microsoft/tolerant-php-parser (v0.1.2): Extracting archive
  - Installing webmozart/assert (1.12.1): Extracting archive
  - Installing phpstan/phpdoc-parser (2.3.0): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing doctrine/deprecations (1.1.5): Extracting archive
  - Installing phpdocumentor/type-resolver (1.12.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.6.4): Extracting archive
  - Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
  - Installing psr/log (3.0.2): Extracting archive
  - Installing composer/xdebug-handler (3.0.5): Extracting archive
  - Installing phan/phan (5.5.1): Extracting archive
  - Installing mediawiki/phan-taint-check-plugin (7.0.0): Extracting archive
  - Installing mediawiki/mediawiki-phan-config (0.17.0): Extracting archive
  - Installing mediawiki/minus-x (1.1.3): Extracting archive
  - Installing sebastian/version (4.0.1): Extracting archive
  - Installing sebastian/type (4.0.0): Extracting archive
  - Installing sebastian/recursion-context (5.0.1): Extracting archive
  - Installing sebastian/object-reflector (3.0.0): Extracting archive
  - Installing sebastian/object-enumerator (5.0.0): Extracting archive
  - Installing sebastian/global-state (6.0.2): Extracting archive
  - Installing sebastian/exporter (5.1.4): Extracting archive
  - Installing sebastian/environment (6.1.0): Extracting archive
  - Installing sebastian/diff (5.1.1): Extracting archive
  - Installing sebastian/comparator (5.0.4): Extracting archive
  - Installing sebastian/code-unit (2.0.0): Extracting archive
  - Installing sebastian/cli-parser (2.0.1): Extracting archive
  - Installing phpunit/php-timer (6.0.0): Extracting archive
  - Installing phpunit/php-text-template (3.0.1): Extracting archive
  - Installing phpunit/php-invoker (4.0.0): Extracting archive
  - Installing phpunit/php-file-iterator (4.1.0): Extracting archive
  - Installing theseer/tokenizer (1.3.1): Extracting archive
  - Installing nikic/php-parser (v5.6.2): Extracting archive
  - Installing sebastian/lines-of-code (2.0.2): Extracting archive
  - Installing sebastian/complexity (3.2.0): Extracting archive
  - Installing sebastian/code-unit-reverse-lookup (3.0.0): Extracting archive
  - Installing phpunit/php-code-coverage (10.1.16): 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.13.4): Extracting archive
  - Installing phpunit/phpunit (10.5.58): Extracting archive
  - Installing ockcyp/covers-validator (v1.7.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.4.0): Extracting archive
  - Installing wikimedia/assert (v0.5.1): Extracting archive
  - Installing wikimedia/update-history (1.0.2): Extracting archive
  0/65 [>---------------------------]   0%
 27/65 [===========>----------------]  41%
 45/65 [===================>--------]  69%
 60/65 [=========================>--]  92%
 65/65 [============================] 100%
3 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating optimized autoload files
Class Wikimedia\LangConv\Test\FstReplacementMachineTest located in ./tests/phpunit/FstReplacementMachineTest.php does not comply with psr-4 autoloading standard (rule: Wikimedia\LangConv\Tests\ => ./tests/phpunit). Skipping.
Class Wikimedia\LangConv\Test\Construct\GenReplFstTest located in ./tests/phpunit/Construct/GenReplFstTest.php does not comply with psr-4 autoloading standard (rule: Wikimedia\LangConv\Tests\ => ./tests/phpunit). Skipping.
Class Wikimedia\LangConv\Test\NullReplacementMachineTest located in ./tests/phpunit/NullReplacementMachineTest.php does not comply with psr-4 autoloading standard (rule: Wikimedia\LangConv\Tests\ => ./tests/phpunit). Skipping.
41 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.32.1 -> 0.32.2
$ /usr/bin/npm install
--- stderr ---
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
--- stdout ---

added 447 packages, and audited 448 packages in 5s

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

found 0 vulnerabilities

--- end ---
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

--- end ---
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

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

/src/repo/lib/FST.js
  48:1  warning  The type 'Utf8Array' is undefined          jsdoc/no-undefined-types
  52:1  warning  The type 'BracketMachine' is undefined     jsdoc/no-undefined-types
  52:1  warning  The type 'ConversionMachine' is undefined  jsdoc/no-undefined-types

/src/repo/lib/ReplacementMachine.js
   17:1  warning  Missing JSDoc @param "baseLanguage" type  jsdoc/require-param-type
   36:2  warning  Missing JSDoc @return declaration         jsdoc/require-returns
   37:1  warning  Missing JSDoc @param "filename" type      jsdoc/require-param-type
   38:1  warning  Missing JSDoc @param "bracket" type       jsdoc/require-param-type
   59:2  warning  Found more than one @return declaration   jsdoc/require-returns
   59:2  warning  Found more than one @return declaration   jsdoc/require-returns-check
   70:1  warning  Missing JSDoc @param "s" type             jsdoc/require-param-type
   71:1  warning  Missing JSDoc @param "destCode" type      jsdoc/require-param-type
   72:1  warning  Missing JSDoc @param "invertCode" type    jsdoc/require-param-type
  104:1  warning  The type 'Node' is undefined              jsdoc/no-undefined-types
  107:1  warning  The type 'Node' is undefined              jsdoc/no-undefined-types
  136:1  warning  The type 'Document' is undefined          jsdoc/no-undefined-types
  144:1  warning  The type 'DocumentFragment' is undefined  jsdoc/no-undefined-types

/src/repo/tools/build-langconv-fst.js
   1:1  warning  ES2023 Hashbang comments are forbidden       es-x/no-hashbang
  12:1  warning  Invalid JSDoc tag name "0@"                  jsdoc/check-tag-names
  16:1  warning  Invalid JSDoc tag name "_IDENTITY_SYMBOL_@"  jsdoc/check-tag-names

✖ 19 problems (0 errors, 19 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-parens","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":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","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-parens","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":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]}]},{"filePath":"/src/repo/jsdoc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]}]},{"filePath":"/src/repo/lib/FST.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Utf8Array' is undefined.","line":48,"column":1,"nodeType":"Block","endLine":48,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'BracketMachine' is undefined.","line":52,"column":1,"nodeType":"Block","endLine":52,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'ConversionMachine' is undefined.","line":52,"column":1,"nodeType":"Block","endLine":52,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Load and execute a finite-state transducer (FST) based converter or\n * bracketing machine from a compact JSON description.\n *\n * @module\n */\n\n'use strict';\n\nconst { StringDecoder } = require('string_decoder');\n\nconst MAGIC_BYTES   = 8; // 8 byte header w/ magic bytes\n\n// These pseudo-characters appear in the \"output\" side of the FST only.\nconst BYTE_IDENTITY = 0xFF;\nconst BYTE_RBRACKET = 0xFE;\nconst BYTE_LBRACKET = 0xFD;\nconst BYTE_FAIL     = 0xFC;\n// These pseudo-characters appear in the \"input\" side of the FST.\nconst BYTE_EOF      = 0xF8; // The highest possible input char\nconst BYTE_EPSILON  = 0x00; // Always appears first in sorted order\n\n/**\n * A FST conversion machine.\n *\n * @callback module:language/FST~ConversionMachine\n * @param {Buffer} buffer UTF-8 encoded input buffer.\n * @param {number} [start] Start position in the buffer, default 0.\n * @param {number} [end] End position in the buffer, defaults to\n *   `buffer.length`.\n * @return {string} The converted string.\n */\n\n/**\n * A FST bracket machine.\n *\n * @callback module:language/FST~BracketMachine\n * @param {Buffer} buffer UTF-8 encoded input buffer.\n * @param {number} [start] Start position in the buffer, default 0.\n * @param {number} [end] End position in the buffer, defaults to\n *   `buffer.length`.\n * @return {number[]} An array of bracket locations in the input buffer.\n */\n\n/**\n * Load an FST description and return a function which runs the machine.\n *\n * @param {Buffer|Utf8Array|string} file The FST description, either as a\n *  filename (to be loaded synchronously) or a loaded byte array.\n * @param {boolean} [justBrackets] The machine will return an array of\n *  bracket locations in the input buffer, instead of a converted buffer.\n * @return {BracketMachine|ConversionMachine}\n */\nfunction compile(file, justBrackets) {\n\tif (typeof file === 'string') {\n\t\tfile = require('fs').readFileSync(file);\n\t}\n\t// Verify the magic number\n\tif (\n\t\tfile.length < (MAGIC_BYTES + 2/* states, min*/) ||\n\t\t\tfile.slice(0,8).toString('utf8') !== 'pFST\\0WM\\0'\n\t) {\n\t\tthrow new Error(\"Invalid pFST file.\");\n\t}\n\tif (justBrackets === 'split') {\n\t\t// Debugging helper: instead of an array of positions, split the\n\t\t// input at the bracket locations and return an array of strings.\n\t\tconst bfunc = compile(file, true);\n\t\treturn (buf,start,end) => {\n\t\t\tend = end === undefined ? buf.length : end;\n\t\t\tconst r = bfunc(buf,start,end);\n\t\t\tr.push(end);\n\t\t\tlet i = 0;\n\t\t\treturn r.map((j) => {\n\t\t\t\tconst b = buf.slice(i,j);\n\t\t\t\ti = j;\n\t\t\t\treturn b.toString('utf8');\n\t\t\t});\n\t\t};\n\t}\n\treturn (buf, start, end, unicode) => {\n\t\tstart = start === undefined ? 0 : start;\n\t\tend = end === undefined ? buf.length : end;\n\t\tconsole.assert(start >= 0 && end <= buf.length, \"Bad start/end\");\n\t\tconst countCodePoints = justBrackets && unicode;\n\t\tconst STATE_INITIAL = MAGIC_BYTES + 2/* eof state*/;\n\t\tlet state = STATE_INITIAL;\n\t\tlet idx = start;\n\t\tlet outpos = 0;\n\t\tconst brackets = [0];\n\t\tconst stack = [];\n\t\tlet chunk = { buf: justBrackets ? null : Buffer.alloc(256), next: null };\n\t\tlet firstChunk = chunk;\n\n\t\t// Read zig-zag encoded variable length integers\n\t\t// (See [en:Variable-length_quantity#Zigzag_encoding])\n\t\tconst readUnsignedV = () => {\n\t\t\tlet b = file[state++];\n\t\t\t/* eslint-disable no-bitwise */\n\t\t\tlet val = b & 127;\n\t\t\twhile (b & 128) {\n\t\t\t\tval += 1;\n\t\t\t\tb = file[state++];\n\t\t\t\tval = (val << 7) + (b & 127);\n\t\t\t}\n\t\t\t/* eslint-enable no-bitwise */\n\t\t\treturn val;\n\t\t};\n\t\tconst readSignedV = () => {\n\t\t\tconst v = readUnsignedV();\n\t\t\t/* eslint-disable no-bitwise */\n\t\t\tif (v & 1) { // sign bit is in LSB\n\t\t\t\treturn -(v >>> 1) - 1;\n\t\t\t} else {\n\t\t\t\treturn (v >>> 1);\n\t\t\t}\n\t\t\t/* eslint-enable no-bitwise */\n\t\t};\n\n\t\t// Add a character to the output.\n\t\tconst emit = justBrackets ? (code) => {\n\t\t\tif (code === BYTE_LBRACKET || code === BYTE_RBRACKET) {\n\t\t\t\tbrackets.push(outpos);\n\t\t\t} else if (countCodePoints && code >= 0x80 && code < 0xC0) {\n\t\t\t\t/* Ignore UTF-8 continuation characters */\n\t\t\t} else {\n\t\t\t\toutpos++;\n\t\t\t}\n\t\t} : (code) => {\n\t\t\t// console.assert(code !== 0 && code < 0xF8, code);\n\t\t\tif (outpos >= chunk.buf.length) {\n\t\t\t\t// Make another chunk, bigger than the last one.\n\t\t\t\tchunk.next = {\n\t\t\t\t\tbuf: Buffer.alloc(chunk.buf.length * 2),\n\t\t\t\t\tnext: null\n\t\t\t\t};\n\t\t\t\tchunk = chunk.next;\n\t\t\t\toutpos = 0;\n\t\t\t}\n\t\t\tchunk.buf[outpos++] = code;\n\t\t};\n\t\t// Save the current machine state before taking a non-deterministic\n\t\t// edge; if the machine fails, restart at the given `state`\n\t\tconst save = (epsEdge) => {\n\t\t\tstack.push({\n\t\t\t\tepsEdge,\n\t\t\t\toutpos,\n\t\t\t\tidx,\n\t\t\t\tchunk,\n\t\t\t\tblen: brackets.length,\n\t\t\t});\n\t\t};\n\t\t// When the machine has failed, restart at the saved state.\n\t\tconst reset = () => {\n\t\t\tconst s = stack.pop();\n\t\t\toutpos = s.outpos;\n\t\t\tchunk = s.chunk;\n\t\t\tchunk.next = null;\n\t\t\tidx = s.idx;\n\t\t\tbrackets.length = s.blen;\n\t\t\t// Get outByte from this edge, then jump to next state\n\t\t\tstate = s.epsEdge + 1/* skip over inByte */;\n\t\t\tconst edgeOut = file[state++];\n\t\t\tif (edgeOut !== BYTE_EPSILON) {\n\t\t\t\temit(edgeOut);\n\t\t\t}\n\t\t\tlet edgeDest = state;\n\t\t\tedgeDest += readSignedV();\n\t\t\tstate = edgeDest;\n\t\t};\n\n\t\t// This runs the machine until we reach the EOF state\n\t\t/* eslint-disable no-labels, no-extra-label */\n\t\tNEXTSTATE:\n\t\twhile (state >= STATE_INITIAL) {\n\t\t\tif (state === STATE_INITIAL) {\n\t\t\t\t// Memory efficiency: since the machine is universal\n\t\t\t\t// we know we'll never fail as long as we're in the\n\t\t\t\t// initial state.\n\t\t\t\tstack.length = 0;\n\t\t\t}\n\t\t\tconst edgeWidth = readUnsignedV();\n\t\t\tlet nEdges = readUnsignedV();\n\t\t\tif (nEdges === 0) {\n\t\t\t\treset();\n\t\t\t\tcontinue NEXTSTATE;\n\t\t\t}\n\t\t\t// Read first edge to see if there are any epsilon edges\n\t\t\tlet edge0 = state;\n\t\t\twhile (file[edge0] === BYTE_EPSILON) {\n\t\t\t\t// If this is an epsilon edge, then save a backtrack state\n\t\t\t\tsave(edge0);\n\t\t\t\tedge0 += edgeWidth;\n\t\t\t\tnEdges--;\n\t\t\t\tif (nEdges === 0) {\n\t\t\t\t\treset();\n\t\t\t\t\tcontinue NEXTSTATE;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Binary search for an edge matching c\n\t\t\tconst c = (idx < end) ? buf[idx++] : /* pseudo-character: */ BYTE_EOF;\n\t\t\tlet minIndex = 0;\n\t\t\tlet maxIndex = nEdges;\n\t\t\twhile (minIndex !== maxIndex) {\n\t\t\t\t/* eslint-disable no-bitwise */\n\t\t\t\tconst currentIndex = (minIndex + maxIndex) >>> 1;\n\t\t\t\tconst targetEdge = edge0 + (edgeWidth * currentIndex);\n\t\t\t\tconst inByte = file[targetEdge];\n\t\t\t\tif (inByte <= c) {\n\t\t\t\t\tminIndex = currentIndex + 1;\n\t\t\t\t} else {\n\t\t\t\t\tmaxIndex = currentIndex;\n\t\t\t\t}\n\t\t\t\t/* eslint-enable no-bitwise */\n\t\t\t}\n\t\t\t// (minIndex-1).inByte <= c, and maxIndex.inByte > c\n\t\t\tconst targetEdge = edge0 + (edgeWidth * (minIndex - 1));\n\t\t\tlet outByte = (minIndex > 0) ? file[targetEdge + 1] : BYTE_FAIL;\n\t\t\tif (outByte === BYTE_FAIL) {\n\t\t\t\treset();\n\t\t\t\tcontinue NEXTSTATE;\n\t\t\t}\n\t\t\tif (outByte !== BYTE_EPSILON) {\n\t\t\t\tif (outByte === BYTE_IDENTITY) {\n\t\t\t\t\toutByte = c; // Copy input byte to output\n\t\t\t\t}\n\t\t\t\temit(outByte);\n\t\t\t}\n\t\t\tstate = targetEdge + 2; // skip over inByte/outByte\n\t\t\tstate = readSignedV() + (targetEdge + 2);\n\t\t}\n\t\t/* eslint-enable no-labels, no-extra-label */\n\n\t\t// Ok, process the final state and return something.\n\t\tif (justBrackets) {\n\t\t\tbrackets.push(outpos);\n\t\t\treturn brackets;\n\t\t}\n\t\t// Convert the chunked UTF-8 output back into a JavaScript string.\n\t\tchunk.buf = chunk.buf.slice(0, outpos);\n\t\tchunk = null; // free memory as we go along\n\t\tvar decoder = new StringDecoder('utf8');\n\t\tvar result = '';\n\t\tfor (; firstChunk; firstChunk = firstChunk.next) {\n\t\t\tresult += decoder.write(firstChunk.buf);\n\t\t}\n\t\tresult += decoder.end();\n\t\treturn result;\n\t};\n}\n\nmodule.exports = {\n\tconstants: {\n\t\tBYTE_IDENTITY,\n\t\tBYTE_RBRACKET,\n\t\tBYTE_LBRACKET,\n\t\tBYTE_FAIL,\n\t\tBYTE_EOF,\n\t\tBYTE_EPSILON,\n\t},\n\tcompile,\n};\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/lib/ReplacementMachine.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"baseLanguage\" type.","line":17,"column":1,"nodeType":"Block","endLine":17,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Missing JSDoc @return declaration.","line":36,"column":2,"nodeType":"Block","endLine":40,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"filename\" type.","line":37,"column":1,"nodeType":"Block","endLine":37,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"bracket\" type.","line":38,"column":1,"nodeType":"Block","endLine":38,"endColumn":1},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Found more than one @return declaration.","line":59,"column":2,"nodeType":"Block","endLine":78,"endColumn":5},{"ruleId":"jsdoc/require-returns-check","severity":1,"message":"Found more than one @return declaration.","line":59,"column":2,"nodeType":"Block","endLine":78,"endColumn":5},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"s\" type.","line":70,"column":1,"nodeType":"Block","endLine":70,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"destCode\" type.","line":71,"column":1,"nodeType":"Block","endLine":71,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"invertCode\" type.","line":72,"column":1,"nodeType":"Block","endLine":72,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Node' is undefined.","line":104,"column":1,"nodeType":"Block","endLine":104,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Node' is undefined.","line":107,"column":1,"nodeType":"Block","endLine":107,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'Document' is undefined.","line":136,"column":1,"nodeType":"Block","endLine":136,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'DocumentFragment' is undefined.","line":144,"column":1,"nodeType":"Block","endLine":144,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":13,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * This is a wrapper around functionality similar to PHP's `ReplacementArray`,\n * but built on (reversible) Finite-State Transducers.\n *\n * @module\n */\n\n'use strict';\n\nconst FST = require('./FST.js');\n\nclass ReplacementMachine {\n\t/**\n\t * Create a new ReplacementArray, which holds a given source->destination\n\t * transformation.\n\t *\n\t * @param baseLanguage\n\t * @param {...any} codes\n\t */\n\tconstructor(baseLanguage, ...codes) {\n\t\tthis.baseLanguage = baseLanguage;\n\t\tthis.codes = codes.slice(0);\n\t\tthis.machine = new Map(codes.map((c) => [c, {\n\t\t\tconvert: ReplacementMachine.loadFST(`trans-${ c }`),\n\t\t\tbracket: new Map(\n\t\t\t\tcodes.filter((cc) => this.validCodePair(c, cc)).map((cc) => [\n\t\t\t\t\tcc,\n\t\t\t\t\tReplacementMachine.loadFST(\n\t\t\t\t\t\t`brack-${ c }-${ c === cc ? 'noop' : cc }`,\n\t\t\t\t\t\t'bracket'\n\t\t\t\t\t)\n\t\t\t\t])),\n\t\t}]));\n\t}\n\n\t/**\n\t * @param filename\n\t * @param bracket\n\t * @private\n\t */\n\tstatic loadFST(filename, bracket) {\n\t\treturn FST.compile(`${ __dirname }/../fst/${ filename }.pfst`, bracket);\n\t}\n\n\t/**\n\t * Override this method in subclass if you want to limit the\n\t * possible code pairs bracketed.  (For example, zh has a large\n\t * number of variants, but we typically want to use only a limited\n\t * number of these as possible invert codes.)\n\t *\n\t * @param {string} destCode\n\t * @param {string} invertCode\n\t * @return {boolean} whether this is a valid bracketing pair.\n\t */\n\tvalidCodePair(destCode, invertCode) {\n\t\treturn true;\n\t}\n\n\t/**\n\t * Quantify a guess about the \"native\" language of string `s`.\n\t * We will be converting *to* `destCode`, and our guess is that\n\t * when we round trip we'll want to convert back to `invertCode`\n\t * (so `invertCode` is our guess about the actual language of `s`).\n\t * If we were to make this encoding, the returned value `unsafe` is\n\t * the number of codepoints we'd have to specially-escape, `safe` is\n\t * the number of codepoints we wouldn't have to escape, and `len` is\n\t * the total number of codepoints in `s`.  Generally lower values of\n\t * `nonsafe` indicate a better guess for `invertCode`.\n\t *\n\t * @param s\n\t * @param destCode\n\t * @param invertCode\n\t * @return {Object} Statistics about the given guess.\n\t * @return {number} return.safe\n\t * @return {number} return.unsafe\n\t * @return {number} return.length (Should be `safe+unsafe`.)\n\t * @private\n\t */\n\tcountBrackets(s, destCode, invertCode) {\n\t\tconsole.assert(\n\t\t\tthis.validCodePair(destCode, invertCode),\n\t\t\t`Invalid code pair: ${ destCode }/${ invertCode }`\n\t\t);\n\t\tconst m = this.machine.get(destCode).bracket.get(invertCode);\n\t\tconst buf = Buffer.from(s, 'utf8');\n\t\tconst brackets = m(buf, 0, buf.length, 'unicode'/* codepoints*/);\n\t\tlet safe = 0;\n\t\tlet unsafe = 0;\n\t\tfor (let i = 1; i < brackets.length; i++) {\n\t\t\tsafe += (brackets[i] - brackets[i - 1]);\n\t\t\tif (++i < brackets.length) {\n\t\t\t\tunsafe += (brackets[i] - brackets[i - 1]);\n\t\t\t}\n\t\t}\n\t\t// Note that this is counting codepoints, not UTF-8 code units.\n\t\treturn { safe, unsafe, length: brackets[brackets.length - 1] };\n\t}\n\n\t/**\n\t * Replace the given text Node with converted text, protecting any\n\t * markup which can't be round-tripped back to `invertCode` with\n\t * appropriate synthetic language-converter markup.\n\t *\n\t * @param {Node} textNode\n\t * @param {string} destCode\n\t * @param {string} invertCode\n\t * @return {Node|null} the next sibling of textNode (for traversal)\n\t */\n\treplace(textNode, destCode, invertCode) {\n\t\tconst fragment = this.convert(\n\t\t\ttextNode.ownerDocument, textNode.textContent, destCode, invertCode\n\t\t);\n\t\t// Was a change made?\n\t\tconst next = textNode.nextSibling;\n\t\tif (\n\t\t\t// `fragment` has exactly 1 child.\n\t\t\tfragment.firstChild && !fragment.firstChild.nextSibling &&\n\t\t\t// `fragment.firstChild` is a DOM text node\n\t\t\tfragment.firstChild.nodeType === 3 &&\n\t\t\t// `textNode` is a DOM text node\n\t\t\ttextNode.nodeType === 3 &&\n\t\t\ttextNode.textContent === fragment.textContent\n\t\t) {\n\t\t\treturn next; // No change.\n\t\t}\n\t\ttextNode.replaceWith(fragment);\n\t\treturn next;\n\t}\n\n\t/**\n\t * Convert the given string, protecting any\n\t * markup which can't be round-tripped back to `invertCode` with\n\t * appropriate synthetic language-converter markup.  Returns\n\t * a DocumentFragment.\n\t *\n\t * @param {Document} document\n\t *   Owner of the resulting DocumentFragment.\n\t * @param {string} s\n\t *   Text to convert.\n\t * @param {string} destCode\n\t *   Target language code for the conversion.\n\t * @param {string} invertCode\n\t *   Language code which will be used to round-trip the result.\n\t * @return {DocumentFragment}\n\t */\n\tconvert(document, s, destCode, invertCode) {\n\t\tconst machine = this.machine.get(destCode);\n\t\tconst convertM = machine.convert;\n\t\tconst bracketM = machine.bracket.get(invertCode);\n\t\tconst result = document.createDocumentFragment();\n\t\tconst buf = Buffer.from(s, 'utf8');\n\t\tconst brackets = bracketM(buf);\n\t\tfor (let i = 1; i < brackets.length; i++) {\n\t\t\t// A safe string\n\t\t\tconst safe = convertM(buf, brackets[i - 1], brackets[i]);\n\t\t\tif (safe.length > 0) {\n\t\t\t\tresult.appendChild(document.createTextNode(safe));\n\t\t\t}\n\t\t\tif (++i < brackets.length) {\n\t\t\t\t// An unsafe string\n\t\t\t\tconst orig = buf.toString('utf8', brackets[i - 1], brackets[i]);\n\t\t\t\tconst unsafe = convertM(buf, brackets[i - 1], brackets[i]);\n\t\t\t\tconst span = document.createElement('span');\n\t\t\t\tspan.textContent = unsafe;\n\t\t\t\tspan.setAttribute('typeof', 'mw:LanguageVariant');\n\t\t\t\t// If this is an anomalous piece of text in a paragraph\n\t\t\t\t// otherwise written in destCode, then it's possible\n\t\t\t\t// invertCode === destCode.  In this case try to pick a\n\t\t\t\t// more appropriate invertCode !== destCode.\n\t\t\t\tlet ic = invertCode;\n\t\t\t\tif (ic === destCode) {\n\t\t\t\t\tconst cs = this.codes.filter((c) => c !== destCode).map((code) => ({\n\t\t\t\t\t\tcode,\n\t\t\t\t\t\tstats: this.countBrackets(orig, code, code)\n\t\t\t\t\t})).sort((a,b) => a.stats.unsafe - b.stats.unsafe);\n\t\t\t\t\tif (cs.length === 0) {\n\t\t\t\t\t\tic = '-';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tic = cs[0].code;\n\t\t\t\t\t\tspan.setAttribute('data-mw-variant-lang', ic);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tspan.setAttribute('data-mw-variant', JSON.stringify({\n\t\t\t\t\ttwoway: [\n\t\t\t\t\t\t{ l: ic, t: orig },\n\t\t\t\t\t\t{ l: destCode, t: unsafe },\n\t\t\t\t\t],\n\t\t\t\t\trt: true, /* Synthetic markup used for round-tripping */\n\t\t\t\t}));\n\t\t\t\tif (unsafe.length > 0) {\n\t\t\t\t\tresult.appendChild(span);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Merge Text nodes, just in case we had zero-length brackets.\n\t\tresult.normalize();\n\t\treturn result;\n\t}\n}\n\nmodule.exports.ReplacementMachine = ReplacementMachine;\n","usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/lib/ZhReplacementMachine.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/lib/index.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"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-parens","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":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]}]},{"filePath":"/src/repo/tests/mocha/foma.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/tests/mocha/language/crh.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/tests/mocha/language/en.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/tests/mocha/language/ku.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/tests/mocha/language/sr.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"filePath":"/src/repo/tests/mocha/language/zh.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"no-buffer-constructor","replacedBy":[]},{"ruleId":"arrow-parens","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":[]}]},{"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":"jsdoc/check-tag-names","severity":1,"message":"Invalid JSDoc tag name \"0@\".","line":12,"column":1,"nodeType":"Block","endLine":12,"endColumn":1},{"ruleId":"jsdoc/check-tag-names","severity":1,"message":"Invalid JSDoc tag name \"_IDENTITY_SYMBOL_@\".","line":16,"column":1,"nodeType":"Block","endLine":16,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"#!/usr/bin/env node\n\n'use strict';\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 *\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/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) { break; }\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) { continue; }\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_@') { return BYTE_IDENTITY; }\n\t\tif (sym === '@0@') { return BYTE_EPSILON; }\n\t\tif (sym === '[[') { return BYTE_LBRACKET; }\n\t\tif (sym === ']]') { return BYTE_RBRACKET; }\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}, (fs) => {\n\t\tfinalStates = new Set(fs);\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++) { alphabet.delete(i); }\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) { e = { outByte: BYTE_FAIL, to: state }; }\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 out = (i === e.outByte) ? BYTE_IDENTITY : e.outByte;\n\t\t\tif (out !== lastEdge.outByte || e.to !== lastEdge.to) {\n\t\t\t\tlastEdge = { inByte: i, outByte: out, 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) { break; /* nothing left to do */ }\n\t\tif (verbose) { console.log('.'); }\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.usage(\n\t\t'Usage: $0 [options] <conversion> <inverse>\\n' +\n\t\t'Converts a finite-state transducer in .att format.', {\n\t\t\t'output': {\n\t\t\t\tdescription: 'Output filename (or base name)',\n\t\t\t\talias: 'o',\n\t\t\t\tnargs: 1,\n\t\t\t\tnormalize: true,\n\t\t\t},\n\t\t\t'file': {\n\t\t\t\tdescription: 'Input .att filename',\n\t\t\t\talias: 'f',\n\t\t\t\tconflicts: 'language',\n\t\t\t\timplies: 'output',\n\t\t\t\tnargs: 1,\n\t\t\t\tnormalize: true,\n\t\t\t},\n\t\t\t'language': {\n\t\t\t\tdescription: 'Converts trans-{conversion}, brack-{conversion}-noop, and brack-{conversion}-{inverse} in default locations',\n\t\t\t\talias: 'l',\n\t\t\t\tconflicts: 'file',\n\t\t\t\tarray: true,\n\t\t\t},\n\t\t\t'brackets': {\n\t\t\t\tdescription: 'Emit a bracket-location machine',\n\t\t\t\talias: 'b',\n\t\t\t\tboolean: true,\n\t\t\t\tdefault: undefined,\n\t\t\t},\n\t\t\t'verbose': {\n\t\t\t\tdescription: 'Show progress',\n\t\t\t\talias: 'v',\n\t\t\t\tboolean: true,\n\t\t\t},\n\t\t}).example('$0 -l sr-ec sr-el');\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, '..', '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-parens","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":[]}]}]

--- end ---
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported
--- stdout ---

added 447 packages, and audited 448 packages in 8s

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

found 0 vulnerabilities

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

> wikimedia-langconv@0.4.1+git test
> npm run eslint && npm run mocha


> wikimedia-langconv@0.4.1+git eslint
> eslint --cache lib


/src/repo/lib/FST.js
  48:1  warning  The type 'Utf8Array' is undefined          jsdoc/no-undefined-types
  52:1  warning  The type 'BracketMachine' is undefined     jsdoc/no-undefined-types
  52:1  warning  The type 'ConversionMachine' is undefined  jsdoc/no-undefined-types

/src/repo/lib/ReplacementMachine.js
   17:1  warning  Missing JSDoc @param "baseLanguage" type  jsdoc/require-param-type
   36:2  warning  Missing JSDoc @return declaration         jsdoc/require-returns
   37:1  warning  Missing JSDoc @param "filename" type      jsdoc/require-param-type
   38:1  warning  Missing JSDoc @param "bracket" type       jsdoc/require-param-type
   59:2  warning  Found more than one @return declaration   jsdoc/require-returns
   59:2  warning  Found more than one @return declaration   jsdoc/require-returns-check
   70:1  warning  Missing JSDoc @param "s" type             jsdoc/require-param-type
   71:1  warning  Missing JSDoc @param "destCode" type      jsdoc/require-param-type
   72:1  warning  Missing JSDoc @param "invertCode" type    jsdoc/require-param-type
  104:1  warning  The type 'Node' is undefined              jsdoc/no-undefined-types
  107:1  warning  The type 'Node' is undefined              jsdoc/no-undefined-types
  136:1  warning  The type 'Document' is undefined          jsdoc/no-undefined-types
  144:1  warning  The type 'DocumentFragment' is undefined  jsdoc/no-undefined-types

✖ 16 problems (0 errors, 16 warnings)


> wikimedia-langconv@0.4.1+git mocha
> nyc --reporter text-summary _mocha --recursive --timeout 10000 --check-leaks tests/mocha



  Foma FST verification
    - LANGCONV_TEST_FOMA is not set, skipping

  Language/CRH tests
    ✔ general words, covering more of the alphabet (1) [crh-cyrl]
    ✔ general words, covering more of the alphabet (2) [crh-cyrl]
    ✔ general words, covering more of the alphabet (3) [crh-cyrl]
    ✔ general words, covering more of the alphabet (4) [crh-cyrl]
    ✔ exception words [crh-cyrl]
    ✔ recent problem words, part 1 [crh-cyrl]
    ✔ recent problem words, part 2 [crh-cyrl]
    ✔ recent problem words, part 3 [crh-cyrl]
    ✔ recent problem words, part 4 [crh-cyrl]
    ✔ recent problem words, part 5 [crh-cyrl]
    ✔ recent problem words, part 6 [crh-cyrl]
    ✔ recent problem words, part 7 [crh-cyrl]
    ✔ regex pattern words [crh-cyrl]
    ✔ multi part words [crh-cyrl]
    ✔ affix patterns [crh-cyrl]
    ✔ Roman numerals and quotes, esp. single-letter Roman numerals at the end of a string [crh-cyrl]
    ✔ Roman numerals vs Initials, part 1 - Roman numeral initials without spaces [crh-cyrl]
    ✔ Roman numerals vs Initials, part 2 - Roman numeral initials with spaces [crh-cyrl]
    ✔ ALL CAPS, made up acronyms [crh-cyrl]
    ✔ Many-to-one mappings: many Cyrillic to one Latin [crh-cyrl]
    ✔ Many-to-one mappings: many Latin to one Cyrillic [crh-cyrl]
    ✔ general words, covering more of the alphabet (1) [crh-latn]
    ✔ general words, covering more of the alphabet (2) [crh-latn]
    ✔ general words, covering more of the alphabet (3) [crh-latn]
    ✔ general words, covering more of the alphabet (4) [crh-latn]
    ✔ exception words [crh-latn]
    ✔ recent problem words, part 1 [crh-latn]
    ✔ recent problem words, part 2 [crh-latn]
    ✔ recent problem words, part 3 [crh-latn]
    ✔ recent problem words, part 4 [crh-latn]
    ✔ recent problem words, part 5 [crh-latn]
    ✔ recent problem words, part 6 [crh-latn]
    ✔ recent problem words, part 7 [crh-latn]
    ✔ regex pattern words [crh-latn]
    ✔ multi part words [crh-latn]
    ✔ affix patterns [crh-latn]
    ✔ Roman numerals and quotes, esp. single-letter Roman numerals at the end of a string [crh-latn]
    ✔ Roman numerals vs Initials, part 1 - Roman numeral initials without spaces [crh-latn]
    ✔ Roman numerals vs Initials, part 2 - Roman numeral initials with spaces [crh-latn]
    ✔ ALL CAPS, made up acronyms [crh-latn]
    ✔ Many-to-one mappings: many Cyrillic to one Latin [crh-latn]
    ✔ Many-to-one mappings: many Latin to one Cyrillic [crh-latn]

  LanguageEn tests
    ✔ Converting to Pig Latin [en]
    ✔ Converting from Pig Latin [en]
    ✔ Converting to Pig Latin [en-x-piglatin]
    ✔ Converting from Pig Latin [en-x-piglatin]

  LanguageKu tests
    ✔ Test (1) [ku-arab]
    ✔ Test (3) [ku-arab]
    ✔ Test (1) [ku-latn]
    ✔ Test (2) [ku-latn]
    ✔ Test (3) [ku-latn]

  LanguageSr tests
    ✔ A simple conversion of Latin to Cyrillic [sr-ec]

  LanguageZh tests
    ✔ Plain hant -> hans [zh-cn]
    ✔ Plain hans -> hant [zh-cn]
    ✔ zh-cn specific [zh-cn]
    ✔ zh-hk specific [zh-cn]
    ✔ zh-tw specific [zh-cn]
    ✔ zh-tw overrides zh-hant [zh-cn]
    ✔ zh-hk overrides zh-hant [zh-cn]
    ✔ Plain hant -> hans [zh-sg]
    ✔ Plain hans -> hant [zh-sg]
    ✔ zh-cn specific [zh-sg]
    ✔ zh-hk specific [zh-sg]
    ✔ zh-tw specific [zh-sg]
    ✔ zh-tw overrides zh-hant [zh-sg]
    ✔ zh-hk overrides zh-hant [zh-sg]
    ✔ Plain hant -> hans [zh-my]
    ✔ Plain hans -> hant [zh-my]
    ✔ zh-cn specific [zh-my]
    ✔ zh-hk specific [zh-my]
    ✔ zh-tw specific [zh-my]
    ✔ zh-tw overrides zh-hant [zh-my]
    ✔ zh-hk overrides zh-hant [zh-my]
    ✔ Plain hant -> hans [zh-hans]
    ✔ Plain hans -> hant [zh-hans]
    ✔ zh-cn specific [zh-hans]
    ✔ zh-hk specific [zh-hans]
    ✔ zh-tw specific [zh-hans]
    ✔ zh-tw overrides zh-hant [zh-hans]
    ✔ zh-hk overrides zh-hant [zh-hans]
    ✔ Plain hant -> hans [zh-tw]
    ✔ Plain hans -> hant [zh-tw]
    ✔ zh-cn specific [zh-tw]
    ✔ zh-hk specific [zh-tw]
    ✔ zh-tw specific [zh-tw]
    ✔ zh-tw overrides zh-hant [zh-tw]
    ✔ zh-hk overrides zh-hant [zh-tw]
    ✔ Plain hant -> hans [zh-hk]
    ✔ Plain hans -> hant [zh-hk]
    ✔ zh-cn specific [zh-hk]
    ✔ zh-hk specific [zh-hk]
    ✔ zh-tw specific [zh-hk]
    ✔ zh-tw overrides zh-hant [zh-hk]
    ✔ zh-hk overrides zh-hant [zh-hk]
    ✔ Plain hant -> hans [zh-mo]
    ✔ Plain hans -> hant [zh-mo]
    ✔ zh-cn specific [zh-mo]
    ✔ zh-hk specific [zh-mo]
    ✔ zh-tw specific [zh-mo]
    ✔ zh-tw overrides zh-hant [zh-mo]
    ✔ zh-hk overrides zh-hant [zh-mo]
    ✔ Plain hant -> hans [zh-hant]
    ✔ Plain hans -> hant [zh-hant]
    ✔ zh-cn specific [zh-hant]
    ✔ zh-hk specific [zh-hant]
    ✔ zh-tw specific [zh-hant]
    ✔ zh-tw overrides zh-hant [zh-hant]
    ✔ zh-hk overrides zh-hant [zh-hant]


  108 passing (111ms)
  1 pending


=============================== Coverage summary ===============================
Statements   : 87.37% ( 173/198 )
Branches     : 80.64% ( 75/93 )
Functions    : 83.33% ( 20/24 )
Lines        : 86.77% ( 164/189 )
================================================================================

--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
  "auditReportVersion": 2,
  "vulnerabilities": {},
  "metadata": {
    "vulnerabilities": {
      "info": 0,
      "low": 0,
      "moderate": 0,
      "high": 0,
      "critical": 0,
      "total": 0
    },
    "dependencies": {
      "prod": 1,
      "dev": 448,
      "optional": 3,
      "peer": 1,
      "peerOptional": 0,
      "total": 448
    }
  }
}

--- end ---
$ package-lock-lint /src/repo/package-lock.json
--- stdout ---
Checking /src/repo/package-lock.json

--- end ---
build: Updating eslint-config-wikimedia to 0.32.2

$ git add .
--- stdout ---

--- end ---
$ git commit -F /tmp/tmp0gxu9hn3
--- stdout ---
[master 4186c6f] build: Updating eslint-config-wikimedia to 0.32.2
 2 files changed, 8 insertions(+), 8 deletions(-)

--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From 4186c6f5e37453f2f941c91f1d0413965d72279c Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Wed, 26 Nov 2025 04:21:31 +0000
Subject: [PATCH] build: Updating eslint-config-wikimedia to 0.32.2

Change-Id: I08855581b8bb617a368a1dc27533ee681b8fc476
---
 package-lock.json | 14 +++++++-------
 package.json      |  2 +-
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index f8e6138..0d4ecd9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,7 @@
 				"babybird": "^0.0.1",
 				"chai": "^4.2.0",
 				"domino": "^2.1.6",
-				"eslint-config-wikimedia": "0.32.1",
+				"eslint-config-wikimedia": "0.32.2",
 				"jsdoc": "4.0.5",
 				"jsdoc-wmf-theme": "1.1.0",
 				"mocha": "11.1.0",
@@ -1955,9 +1955,9 @@
 			}
 		},
 		"node_modules/eslint-config-wikimedia": {
-			"version": "0.32.1",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
-			"integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+			"version": "0.32.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+			"integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
 			"dev": true,
 			"dependencies": {
 				"@stylistic/eslint-plugin": "^3.1.0",
@@ -6890,9 +6890,9 @@
 			}
 		},
 		"eslint-config-wikimedia": {
-			"version": "0.32.1",
-			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
-			"integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+			"version": "0.32.2",
+			"resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+			"integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
 			"dev": true,
 			"requires": {
 				"@stylistic/eslint-plugin": "^3.1.0",
diff --git a/package.json b/package.json
index 6dd45d0..ec8ac12 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
 		"babybird": "^0.0.1",
 		"chai": "^4.2.0",
 		"domino": "^2.1.6",
-		"eslint-config-wikimedia": "0.32.1",
+		"eslint-config-wikimedia": "0.32.2",
 		"jsdoc": "4.0.5",
 		"jsdoc-wmf-theme": "1.1.0",
 		"mocha": "11.1.0",
-- 
2.47.3


--- end ---
Source code is licensed under the AGPL.